diff --git a/.ddev/docker-compose.wt-do-bot.yaml b/.ddev/docker-compose.wt-do-bot.yaml new file mode 100644 index 0000000..d75a8b4 --- /dev/null +++ b/.ddev/docker-compose.wt-do-bot.yaml @@ -0,0 +1,11 @@ +services: + do-bot: + build: + context: ../wisetrout-do-bot + networks: ["default", "ddev_default"] + env_file: ../wisetrout-do-bot/.env + volumes: + - type: bind + source: ../wisetrout-do-bot + target: /app + # command: "sh -c npm install && npm start" diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d13ff4c --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +BOT_TOKEN=123 \ No newline at end of file diff --git a/.gitignore b/.gitignore index ad896fa..9f2d818 100644 --- a/.gitignore +++ b/.gitignore @@ -203,3 +203,9 @@ test-kanban-login.js # Ignore local development code scratchpad .code/ + +private +.ddev/docker-compose.phpmyadmin_norouter.yaml +.ddev/docker-compose.phpmyadmin.yaml +.ddev/addon-metadata/phpmyadmin/manifest.yaml +.ddev/commands/host/phpmyadmin diff --git a/composer.json b/composer.json index a71a676..a5cf6de 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,11 @@ "drupal/drupal_cms_starter": "~1.1.0", "drupal/project_browser": "@beta", "drupal/recipe_installer_kit": "^1-alpha3@alpha", + "drupal/restui": "^1.22", + "drupal/simple_oauth": "^6.1", "drupal/webform": "@beta", - "drush/drush": "^13" + "drush/drush": "^13", + "vlucas/phpdotenv": "^5.6" }, "conflict": { "drupal/drupal": "*" diff --git a/composer.lock b/composer.lock index fdeea01..51ba42b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c16dbf61bb5a2b4719cea2dac35115c", + "content-hash": "76f95f39ca716879dd01db9deb4ffdc5", "packages": [ { "name": "asm89/stack-cors", @@ -1001,39 +1001,35 @@ "time": "2022-11-25T16:30:20+00:00" }, { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.1.2", + "name": "defuse/php-encryption", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1" + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", - "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", "shasum": "" }, "require": { - "composer-plugin-api": "^2.2", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" }, "require-dev": { - "composer/composer": "^2.2", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^9.0", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", "autoload": { "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "Defuse\\Crypto\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1042,59 +1038,34 @@ ], "authors": [ { - "name": "Franck Nijhof", - "email": "opensource@frenck.dev", - "homepage": "https://frenck.dev", - "role": "Open source developer" + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" } ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "description": "Secure PHP Encryption Library", "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" ], "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", - "source": "https://github.com/PHPCSStandards/composer-installer" + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-07-17T20:45:56+00:00" + "time": "2023-06-19T06:10:36+00:00" }, { "name": "dflydev/dot-access-data", @@ -2571,57 +2542,6 @@ "issues": "https://www.drupal.org/project/issues/checklistapi" } }, - { - "name": "drupal/coder", - "version": "8.3.30", - "source": { - "type": "git", - "url": "https://github.com/pfrenssen/coder.git", - "reference": "6b2edffac77582b1beb36ac155bfda5e7e055aff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pfrenssen/coder/zipball/6b2edffac77582b1beb36ac155bfda5e7e055aff", - "reference": "6b2edffac77582b1beb36ac155bfda5e7e055aff", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", - "ext-mbstring": "*", - "php": ">=7.2", - "sirbrillig/phpcs-variable-analysis": "^2.11.7", - "slevomat/coding-standard": "^8.11", - "squizlabs/php_codesniffer": "^3.13", - "symfony/yaml": ">=3.4.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.7.12", - "phpunit/phpunit": "^8.0" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "Drupal\\": "coder_sniffer/Drupal/", - "DrupalPractice\\": "coder_sniffer/DrupalPractice/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "description": "Coder is a library to review Drupal code.", - "homepage": "https://www.drupal.org/project/coder", - "keywords": [ - "code review", - "phpcs", - "standards" - ], - "support": { - "issues": "https://www.drupal.org/project/issues/coder", - "source": "https://www.drupal.org/project/coder" - }, - "time": "2025-05-25T09:52:20+00:00" - }, { "name": "drupal/coffee", "version": "2.0.1", @@ -2755,6 +2675,58 @@ "issues": "http://drupal.org/project/config_ignore" } }, + { + "name": "drupal/consumers", + "version": "1.24.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/consumers.git", + "reference": "8.x-1.24" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/consumers-8.x-1.24.zip", + "reference": "8.x-1.24", + "shasum": "63acc012badaec2dd15cb4dd3e9cab9cb8a83eef" + }, + "require": { + "drupal/core": "^10.3 || ^11" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.24", + "datestamp": "1770824862", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "bojan_dev", + "homepage": "https://www.drupal.org/user/2801849" + }, + { + "name": "e0ipso", + "homepage": "https://www.drupal.org/user/550110" + }, + { + "name": "eojthebrave", + "homepage": "https://www.drupal.org/user/79230" + } + ], + "description": "Declare all the consumers of your API.", + "homepage": "https://www.drupal.org/project/consumers", + "support": { + "source": "https://git.drupalcode.org/project/consumers" + } + }, { "name": "drupal/core", "version": "11.2.2", @@ -6447,6 +6419,66 @@ "source": "https://git.drupalcode.org/project/redirect" } }, + { + "name": "drupal/restui", + "version": "1.22.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/restui.git", + "reference": "8.x-1.22" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/restui-8.x-1.22.zip", + "reference": "8.x-1.22", + "shasum": "7c9fb14c574f8a4090b77b1bcbc5be141409a383" + }, + "require": { + "drupal/core": "^9.5 || ^10 || ^11" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.22", + "datestamp": "1721134189", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "-enzo-", + "homepage": "https://www.drupal.org/user/294937" + }, + { + "name": "clemens.tolboom", + "homepage": "https://www.drupal.org/user/125814" + }, + { + "name": "juampynr", + "homepage": "https://www.drupal.org/user/682736" + }, + { + "name": "kamkejj", + "homepage": "https://www.drupal.org/user/81043" + }, + { + "name": "vipin.mittal18", + "homepage": "https://www.drupal.org/user/319716" + } + ], + "description": "Provides a user interface to manage REST resources.", + "homepage": "https://www.drupal.org/project/restui", + "support": { + "source": "https://git.drupalcode.org/project/restui" + } + }, { "name": "drupal/robotstxt", "version": "1.6.0", @@ -7016,6 +7048,75 @@ "issues": "https://www.drupal.org/project/issues/seo_checklist" } }, + { + "name": "drupal/simple_oauth", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/simple_oauth.git", + "reference": "6.1.0" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/simple_oauth-6.1.0.zip", + "reference": "6.1.0", + "shasum": "3c345e42cf41a17b2922b0b69129ac2697cf566a" + }, + "require": { + "drupal/consumers": "^1.17", + "drupal/core": "^10.3 || ^11", + "league/oauth2-server": "^9.0", + "php": ">=8.1", + "steverhoades/oauth2-openid-connect-server": "^3.0" + }, + "require-dev": { + "drupal/simple_oauth_static_scope": "*", + "phpspec/prophecy-phpunit": "^2" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "6.1.0", + "datestamp": "1765191669", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Mateu Aguiló Bosch", + "homepage": "https://www.drupal.org/user/2801849", + "email": "mateu.aguilo.bosch@gmail.com" + }, + { + "name": "bradjones1", + "homepage": "https://www.drupal.org/user/405824" + }, + { + "name": "e0ipso", + "homepage": "https://www.drupal.org/user/550110" + }, + { + "name": "kingdutch", + "homepage": "https://www.drupal.org/user/1868952" + }, + { + "name": "pcambra", + "homepage": "https://www.drupal.org/user/122101" + } + ], + "description": "The Simple OAuth module for Drupal", + "homepage": "https://www.drupal.org/project/simple_oauth", + "support": { + "source": "https://git.drupalcode.org/project/simple_oauth" + } + }, { "name": "drupal/simple_search_form", "version": "1.6.0", @@ -8206,39 +8307,30 @@ "time": "2018-01-19T23:11:36+00:00" }, { - "name": "grasmash/expander", - "version": "3.0.1", + "name": "graham-campbell/result-type", + "version": "v1.1.4", "source": { "type": "git", - "url": "https://github.com/grasmash/expander.git", - "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", - "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^3.0.0", - "php": ">=8.0", - "psr/log": "^2 | ^3" + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.3" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Grasmash\\Expander\\": "src/" + "GrahamCampbell\\ResultType\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8247,7 +8339,78 @@ ], "authors": [ { - "name": "Matthew Grasmick" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "grasmash/expander", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.0", + "php": ">=8.0", + "psr/log": "^2 | ^3" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" } ], "description": "Expands internal property references in PHP arrays file.", @@ -8781,6 +8944,143 @@ }, "time": "2025-07-07T14:17:42+00:00" }, + { + "name": "lcobucci/clock", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "a3139d9e97d47826f27e6a17bb63f13621f86058" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/a3139d9e97d47826f27e6a17bb63f13621f86058", + "reference": "a3139d9e97d47826f27e6a17bb63f13621f86058", + "shasum": "" + }, + "require": { + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.31", + "lcobucci/coding-standard": "^11.2.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^2.0.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", + "phpunit/phpunit": "^12.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.5.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-27T09:03:17+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" + }, { "name": "league/commonmark", "version": "2.7.1", @@ -9052,6 +9352,65 @@ ], "time": "2025-05-20T12:55:37+00:00" }, + { + "name": "league/event", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.3" + }, + "time": "2024-09-04T16:06:53+00:00" + }, { "name": "league/html-to-markdown", "version": "5.1.1", @@ -9142,35 +9501,52 @@ "time": "2023-07-12T21:21:09+00:00" }, { - "name": "masterminds/html5", - "version": "2.9.0", + "name": "league/oauth2-server", + "version": "9.3.0", "source": { "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "d8e2f39f645a82b207bbac441694d6e6079357cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/d8e2f39f645a82b207bbac441694d6e6079357cb", + "reference": "d8e2f39f645a82b207bbac441694d6e6079357cb", "shasum": "" }, "require": { - "ext-dom": "*", - "php": ">=5.3.0" + "defuse/php-encryption": "^2.4", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/clock": "^2.3 || ^3.0", + "lcobucci/jwt": "^5.0", + "league/event": "^3.0", + "league/uri": "^7.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^2.0", + "psr/http-server-middleware": "^1.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + "laminas/laminas-diactoros": "^3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.12|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.1.4|^2.0", + "phpstan/phpstan-phpunit": "^1.3.15|^2.0", + "phpstan/phpstan-strict-rules": "^1.5.2|^2.0", + "phpunit/phpunit": "^10.5|^11.5|^12.0", + "roave/security-advisories": "dev-master", + "slevomat/coding-standard": "^8.14.1", + "squizlabs/php_codesniffer": "^3.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, "autoload": { "psr-4": { - "Masterminds\\": "src" + "League\\OAuth2\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -9179,81 +9555,342 @@ ], "authors": [ { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" }, { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" } ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" - ], - "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/9.3.0" }, - "time": "2024-03-31T07:05:07+00:00" + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2025-11-25T22:51:15+00:00" }, { - "name": "mck89/peast", - "version": "v1.17.2", + "name": "league/uri", + "version": "7.8.1", "source": { "type": "git", - "url": "https://github.com/mck89/peast.git", - "reference": "465810689c477fbba17f4f949b75e4d0bdab13ea" + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mck89/peast/zipball/465810689c477fbba17f4f949b75e4d0bdab13ea", - "reference": "465810689c477fbba17f4f949b75e4d0bdab13ea", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=5.4.0" + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.17.2-dev" + "dev-master": "7.x-dev" } }, "autoload": { "psr-4": { - "Peast\\": "lib/Peast/" + "League\\Uri\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Marco Marchiò", - "email": "marco.mm89@gmail.com" + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" } ], - "description": "Peast is PHP library that generates AST for JavaScript code", + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], "support": { - "issues": "https://github.com/mck89/peast/issues", - "source": "https://github.com/mck89/peast/tree/v1.17.2" + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, + { + "name": "mck89/peast", + "version": "v1.17.2", + "source": { + "type": "git", + "url": "https://github.com/mck89/peast.git", + "reference": "465810689c477fbba17f4f949b75e4d0bdab13ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mck89/peast/zipball/465810689c477fbba17f4f949b75e4d0bdab13ea", + "reference": "465810689c477fbba17f4f949b75e4d0bdab13ea", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17.2-dev" + } + }, + "autoload": { + "psr-4": { + "Peast\\": "lib/Peast/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marco Marchiò", + "email": "marco.mm89@gmail.com" + } + ], + "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.17.2" }, "time": "2025-07-01T09:30:45+00:00" }, @@ -9607,6 +10244,56 @@ ], "time": "2025-06-24T10:49:48+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "pear/archive_tar", "version": "1.5.0", @@ -10385,6 +11072,81 @@ }, "time": "2025-04-21T15:27:20+00:00" }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, { "name": "phpowermove/docblock", "version": "v4.0", @@ -10438,78 +11200,75 @@ "time": "2021-09-22T16:57:06+00:00" }, { - "name": "phpstan/phpdoc-parser", - "version": "2.2.0", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" + "php": ">=8.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2025-07-13T07:04:09+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.0 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "Psr\\Clock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -10522,16 +11281,20 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for caching libraries", + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "cache", + "clock", + "now", "psr", - "psr-6" + "psr-20", + "time" ], "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2021-02-03T23:26:27+00:00" + "time": "2022-11-25T14:36:26+00:00" }, { "name": "psr/container", @@ -10796,6 +11559,119 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -11168,210 +12044,50 @@ "time": "2024-12-12T15:39:24+00:00" }, { - "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.12.0", - "source": { - "type": "git", - "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "4debf5383d9ade705e0a25121f16c3fecaf433a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/4debf5383d9ade705e0a25121f16c3fecaf433a7", - "reference": "4debf5383d9ade705e0a25121f16c3fecaf433a7", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "phpcsstandards/phpcsdevcs": "^1.1", - "phpstan/phpstan": "^1.7", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", - "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "VariableAnalysis\\": "VariableAnalysis/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Sam Graham", - "email": "php-codesniffer-variableanalysis@illusori.co.uk" - }, - { - "name": "Payton Swick", - "email": "payton@foolord.com" - } - ], - "description": "A PHPCS sniff to detect problems with variables.", - "keywords": [ - "phpcs", - "static analysis" - ], - "support": { - "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", - "source": "https://github.com/sirbrillig/phpcs-variable-analysis", - "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" - }, - "time": "2025-03-17T16:17:38+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "8.19.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/458d665acd49009efebd7e0cb385d71ae9ac3220", - "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", - "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.1.0", - "squizlabs/php_codesniffer": "^3.13.0" - }, - "require-dev": { - "phing/phing": "3.0.1", - "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.17", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.6", - "phpstan/phpstan-strict-rules": "2.0.4", - "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.21|12.1.3" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "8.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "keywords": [ - "dev", - "phpcs" - ], - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.19.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2025-06-09T17:53:57+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.13.2", + "name": "steverhoades/oauth2-openid-connect-server", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + "url": "https://github.com/steverhoades/oauth2-openid-connect-server.git", + "reference": "f20665aeeeec78fb336c1bc89e8152b4b2380221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", - "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "url": "https://api.github.com/repos/steverhoades/oauth2-openid-connect-server/zipball/f20665aeeeec78fb336c1bc89e8152b4b2380221", + "reference": "f20665aeeeec78fb336c1bc89e8152b4b2380221", "shasum": "" }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "require": { + "lcobucci/jwt": "4.1.5|^4.2|^4.3|^5.0", + "league/oauth2-server": "^8.4.2|^9.0", + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "laminas/laminas-diactoros": "^1.3.2 || ^3.3", + "phpunit/phpunit": "^5.0|^9.5" }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "autoload": { + "psr-4": { + "OpenIDConnectServer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Steve Rhoades", + "email": "sedonami@gmail.com" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], + "description": "An OpenID Connect Server that sites on The PHP League's OAuth2 Server", "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "issues": "https://github.com/steverhoades/oauth2-openid-connect-server/issues", + "source": "https://github.com/steverhoades/oauth2-openid-connect-server/tree/v3.0.1" }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-06-17T22:17:01+00:00" + "time": "2024-09-26T16:19:13+00:00" }, { "name": "symfony/console", @@ -14225,6 +14941,90 @@ ], "time": "2025-05-03T07:21:55+00:00" }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", @@ -14602,6 +15402,102 @@ }, "time": "2023-12-09T11:58:45+00:00" }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-07-17T20:45:56+00:00" + }, { "name": "doctrine/instantiator", "version": "2.0.0", @@ -14672,6 +15568,57 @@ ], "time": "2022-12-30T00:23:10+00:00" }, + { + "name": "drupal/coder", + "version": "8.3.30", + "source": { + "type": "git", + "url": "https://github.com/pfrenssen/coder.git", + "reference": "6b2edffac77582b1beb36ac155bfda5e7e055aff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pfrenssen/coder/zipball/6b2edffac77582b1beb36ac155bfda5e7e055aff", + "reference": "6b2edffac77582b1beb36ac155bfda5e7e055aff", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", + "ext-mbstring": "*", + "php": ">=7.2", + "sirbrillig/phpcs-variable-analysis": "^2.11.7", + "slevomat/coding-standard": "^8.11", + "squizlabs/php_codesniffer": "^3.13", + "symfony/yaml": ">=3.4.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.7.12", + "phpunit/phpunit": "^8.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "Drupal\\": "coder_sniffer/Drupal/", + "DrupalPractice\\": "coder_sniffer/DrupalPractice/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Coder is a library to review Drupal code.", + "homepage": "https://www.drupal.org/project/coder", + "keywords": [ + "code review", + "phpcs", + "standards" + ], + "support": { + "issues": "https://www.drupal.org/project/issues/coder", + "source": "https://www.drupal.org/project/coder" + }, + "time": "2025-05-25T09:52:20+00:00" + }, { "name": "instaclick/php-webdriver", "version": "1.4.19", @@ -15265,6 +16212,53 @@ }, "time": "2025-05-13T13:52:32+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + }, + "time": "2025-07-13T07:04:09+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.10", @@ -16580,6 +17574,212 @@ ], "time": "2024-10-09T05:16:32+00:00" }, + { + "name": "sirbrillig/phpcs-variable-analysis", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", + "reference": "4debf5383d9ade705e0a25121f16c3fecaf433a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/4debf5383d9ade705e0a25121f16c3fecaf433a7", + "reference": "4debf5383d9ade705e0a25121f16c3fecaf433a7", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", + "phpcsstandards/phpcsdevcs": "^1.1", + "phpstan/phpstan": "^1.7", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", + "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "VariableAnalysis\\": "VariableAnalysis/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Sam Graham", + "email": "php-codesniffer-variableanalysis@illusori.co.uk" + }, + { + "name": "Payton Swick", + "email": "payton@foolord.com" + } + ], + "description": "A PHPCS sniff to detect problems with variables.", + "keywords": [ + "phpcs", + "static analysis" + ], + "support": { + "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", + "source": "https://github.com/sirbrillig/phpcs-variable-analysis", + "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" + }, + "time": "2025-03-17T16:17:38+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.19.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/458d665acd49009efebd7e0cb385d71ae9ac3220", + "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php": "^7.4 || ^8.0", + "phpstan/phpdoc-parser": "^2.1.0", + "squizlabs/php_codesniffer": "^3.13.0" + }, + "require-dev": { + "phing/phing": "3.0.1", + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpstan/phpstan-phpunit": "2.0.6", + "phpstan/phpstan-strict-rules": "2.0.4", + "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.21|12.1.3" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.19.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2025-06-09T17:53:57+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-17T22:17:01+00:00" + }, { "name": "staabm/side-effects-detector", "version": "1.0.5", @@ -16833,5 +18033,5 @@ "prefer-lowest": false, "platform": {}, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/sync/core.entity_form_display.node.ai_company.default.yml b/config/sync/core.entity_form_display.node.ai_company.default.yml index fcb5973..5f052e0 100644 --- a/config/sync/core.entity_form_display.node.ai_company.default.yml +++ b/config/sync/core.entity_form_display.node.ai_company.default.yml @@ -1,4 +1,4 @@ -uuid: null +uuid: 6967c42f-e7e0-4bd5-af2c-f13f1050622d langcode: en status: true dependencies: @@ -32,6 +32,12 @@ content: settings: display_label: true third_party_settings: { } + field_company_audience: + type: options_buttons + weight: 7 + region: content + settings: { } + third_party_settings: { } field_company_drupal_profile: type: string_textfield weight: 3 @@ -96,12 +102,6 @@ content: region: content settings: { } third_party_settings: { } - field_company_audience: - type: options_buttons - weight: 7 - region: content - settings: { } - third_party_settings: { } hidden: field_company_color: true field_company_logo: true diff --git a/config/sync/core.entity_view_display.node.ai_company.default.yml b/config/sync/core.entity_view_display.node.ai_company.default.yml index 3179582..7201e91 100644 --- a/config/sync/core.entity_view_display.node.ai_company.default.yml +++ b/config/sync/core.entity_view_display.node.ai_company.default.yml @@ -1,4 +1,4 @@ -uuid: null +uuid: ff2d9c37-c96d-4c60-8575-7df47b314cee langcode: en status: true dependencies: diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index ffad7fa..1780b2e 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -28,6 +28,7 @@ module: coffee: 0 config: 0 config_ignore: 0 + consumers: 0 content_moderation: 0 contextual: 0 crop: 0 @@ -94,9 +95,13 @@ module: redirect: 0 redirect_404: 0 responsive_image: 0 + rest: 0 + restui: 0 sam: 0 scheduler: 0 scheduler_content_moderation_integration: 0 + serialization: 0 + simple_oauth: 0 smart_date: 0 svg_image: 0 symfony_mailer_lite: 0 @@ -111,6 +116,7 @@ module: update: 0 user: 0 views_ui: 0 + wisetrout_do_bot: 0 workflows: 0 eca_form: 1 pathauto: 1 diff --git a/config/sync/core.menu.static_menu_link_overrides.yml b/config/sync/core.menu.static_menu_link_overrides.yml index 7294a01..1b33d05 100644 --- a/config/sync/core.menu.static_menu_link_overrides.yml +++ b/config/sync/core.menu.static_menu_link_overrides.yml @@ -8,8 +8,8 @@ definitions: expanded: false enabled: false filter__tips_all: - enabled: true menu_name: tools parent: '' - expanded: false weight: 0 + expanded: false + enabled: true diff --git a/config/sync/field.field.node.ai_company.field_company_ai_maker.yml b/config/sync/field.field.node.ai_company.field_company_ai_maker.yml index 7036cd4..03515e3 100644 --- a/config/sync/field.field.node.ai_company.field_company_ai_maker.yml +++ b/config/sync/field.field.node.ai_company.field_company_ai_maker.yml @@ -1,3 +1,4 @@ +uuid: bf85eeb1-a348-42d1-a77f-cc59a328ff9c langcode: en status: true dependencies: diff --git a/config/sync/field.field.node.ai_company.field_company_drupal_profile.yml b/config/sync/field.field.node.ai_company.field_company_drupal_profile.yml index c4db7c7..5da7654 100644 --- a/config/sync/field.field.node.ai_company.field_company_drupal_profile.yml +++ b/config/sync/field.field.node.ai_company.field_company_drupal_profile.yml @@ -1,3 +1,4 @@ +uuid: e956d50d-813a-4fcc-a2a3-a7673f74c4b4 langcode: en status: true dependencies: diff --git a/config/sync/field.field.node.ai_issue.field_issue_dashboard_category.yml b/config/sync/field.field.node.ai_issue.field_issue_dashboard_category.yml index 7d14aeb..84e8577 100644 --- a/config/sync/field.field.node.ai_issue.field_issue_dashboard_category.yml +++ b/config/sync/field.field.node.ai_issue.field_issue_dashboard_category.yml @@ -1,3 +1,4 @@ +uuid: 4461182d-8822-4af8-a1a4-acdb0019ec53 langcode: en status: true dependencies: diff --git a/config/sync/field.storage.node.field_company_ai_maker.yml b/config/sync/field.storage.node.field_company_ai_maker.yml index a0ad38d..821b3b9 100644 --- a/config/sync/field.storage.node.field_company_ai_maker.yml +++ b/config/sync/field.storage.node.field_company_ai_maker.yml @@ -1,3 +1,4 @@ +uuid: f044f7e8-6f1c-4ff7-b95b-6a40f872d40c langcode: en status: true dependencies: diff --git a/config/sync/field.storage.node.field_company_drupal_profile.yml b/config/sync/field.storage.node.field_company_drupal_profile.yml index 1d6c836..3260d8f 100644 --- a/config/sync/field.storage.node.field_company_drupal_profile.yml +++ b/config/sync/field.storage.node.field_company_drupal_profile.yml @@ -1,3 +1,4 @@ +uuid: d229c08e-d43a-4278-a86b-ff86760f7eb0 langcode: en status: true dependencies: @@ -10,6 +11,7 @@ type: string settings: max_length: 255 case_sensitive: false + is_ascii: false module: core locked: false cardinality: 1 diff --git a/config/sync/field.storage.node.field_issue_dashboard_category.yml b/config/sync/field.storage.node.field_issue_dashboard_category.yml index e858ade..c876c29 100644 --- a/config/sync/field.storage.node.field_issue_dashboard_category.yml +++ b/config/sync/field.storage.node.field_issue_dashboard_category.yml @@ -1,4 +1,4 @@ -uuid: null +uuid: 9f9e584d-cea3-4f81-b34e-d828f3e4efa0 langcode: en status: true dependencies: diff --git a/config/sync/rest.resource.wisetrout_do_bot_modules_list.yml b/config/sync/rest.resource.wisetrout_do_bot_modules_list.yml new file mode 100644 index 0000000..6597760 --- /dev/null +++ b/config/sync/rest.resource.wisetrout_do_bot_modules_list.yml @@ -0,0 +1,18 @@ +uuid: 848f710d-aa0c-441b-b326-3ec264a31f9f +langcode: en +status: true +dependencies: + module: + - serialization + - simple_oauth + - wisetrout_do_bot +id: wisetrout_do_bot_modules_list +plugin_id: wisetrout_do_bot_modules_list +granularity: resource +configuration: + methods: + - GET + formats: + - json + authentication: + - oauth2 diff --git a/config/sync/rest.resource.wisetrout_do_bot_subscribe.yml b/config/sync/rest.resource.wisetrout_do_bot_subscribe.yml new file mode 100644 index 0000000..bd708b0 --- /dev/null +++ b/config/sync/rest.resource.wisetrout_do_bot_subscribe.yml @@ -0,0 +1,18 @@ +uuid: caaaf904-1827-43ab-8d7a-fbc38238e6e7 +langcode: en +status: true +dependencies: + module: + - serialization + - simple_oauth + - wisetrout_do_bot +id: wisetrout_do_bot_subscribe +plugin_id: wisetrout_do_bot_subscribe +granularity: resource +configuration: + methods: + - POST + formats: + - json + authentication: + - oauth2 diff --git a/config/sync/rest.resource.wisetrout_do_bot_update_status.yml b/config/sync/rest.resource.wisetrout_do_bot_update_status.yml new file mode 100644 index 0000000..6dbfb36 --- /dev/null +++ b/config/sync/rest.resource.wisetrout_do_bot_update_status.yml @@ -0,0 +1,18 @@ +uuid: 43d6d24e-9dc0-4bf0-9c2f-077347c4caa2 +langcode: en +status: true +dependencies: + module: + - serialization + - simple_oauth + - wisetrout_do_bot +id: wisetrout_do_bot_update_status +plugin_id: wisetrout_do_bot_update_status +granularity: resource +configuration: + methods: + - POST + formats: + - json + authentication: + - oauth2 diff --git a/config/sync/rest.resource.wisetrout_do_bot_user_info.yml b/config/sync/rest.resource.wisetrout_do_bot_user_info.yml new file mode 100644 index 0000000..e388bf2 --- /dev/null +++ b/config/sync/rest.resource.wisetrout_do_bot_user_info.yml @@ -0,0 +1,18 @@ +uuid: a73f08f2-baf9-4e81-8a29-8d167105a661 +langcode: en +status: true +dependencies: + module: + - serialization + - simple_oauth + - wisetrout_do_bot +id: wisetrout_do_bot_user_info +plugin_id: wisetrout_do_bot_user_info +granularity: resource +configuration: + methods: + - GET + formats: + - json + authentication: + - oauth2 diff --git a/config/sync/rest.resource.wisetrout_do_bot_wipeout.yml b/config/sync/rest.resource.wisetrout_do_bot_wipeout.yml new file mode 100644 index 0000000..17f3678 --- /dev/null +++ b/config/sync/rest.resource.wisetrout_do_bot_wipeout.yml @@ -0,0 +1,18 @@ +uuid: b193610a-e930-4ee6-9a4e-6c60e46283e4 +langcode: en +status: true +dependencies: + module: + - serialization + - simple_oauth + - wisetrout_do_bot +id: wisetrout_do_bot_wipeout +plugin_id: wisetrout_do_bot_wipeout +granularity: resource +configuration: + methods: + - POST + formats: + - json + authentication: + - oauth2 diff --git a/config/sync/simple_oauth.oauth2_scope.tgbot.yml b/config/sync/simple_oauth.oauth2_scope.tgbot.yml new file mode 100644 index 0000000..2e02698 --- /dev/null +++ b/config/sync/simple_oauth.oauth2_scope.tgbot.yml @@ -0,0 +1,22 @@ +uuid: 4284b1cc-eac2-403f-9b94-db47b6132ebe +langcode: en +status: true +dependencies: { } +id: tgbot +name: tgbot +description: tgbot +grant_types: + refresh_token: + status: true + description: '' + client_credentials: + status: true + description: '' + authorization_code: + status: true + description: '' +umbrella: false +parent: _none +granularity_id: role +granularity_configuration: + role: telegram_bot diff --git a/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml b/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml new file mode 100644 index 0000000..933cbc2 --- /dev/null +++ b/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml @@ -0,0 +1,10 @@ +uuid: da136a35-4129-4977-93d0-fee85c78a8b2 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: f7Fq1Sh24P1luZf4RRdeDs4be4diwuU5P-XkpuxjyLk +id: access_token +label: 'Access Token' +description: 'The access token type.' +locked: true diff --git a/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml b/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml new file mode 100644 index 0000000..4353adf --- /dev/null +++ b/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml @@ -0,0 +1,10 @@ +uuid: 4ac210a0-9ca1-4b83-bbef-a70e95efdc12 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: zlxpI1nUDS46lrOkyHiD-72D5OFvlADLiyXT5B1iErk +id: auth_code +label: 'Auth code' +description: 'The auth code type.' +locked: true diff --git a/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml b/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml new file mode 100644 index 0000000..eb9accb --- /dev/null +++ b/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml @@ -0,0 +1,10 @@ +uuid: e46df633-5013-4ae0-9471-e604981f668e +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: dTxawscb7hlymHgGQJP1DO58uotCWthP68yXbmv6LL8 +id: refresh_token +label: 'Refresh token' +description: 'The refresh token type.' +locked: true diff --git a/config/sync/simple_oauth.settings.yml b/config/sync/simple_oauth.settings.yml new file mode 100644 index 0000000..521a856 --- /dev/null +++ b/config/sync/simple_oauth.settings.yml @@ -0,0 +1,7 @@ +_core: + default_config_hash: ci56aVE-Jo4w_Xy5Gkk1coAwO-fj7Q87l0PKc5E9sfo +scope_provider: dynamic +token_cron_batch_size: 0 +public_key: ../private/simple_oauth/public.key +private_key: ../private/simple_oauth/private.key +disable_openid_connect: false diff --git a/config/sync/system.action.user_add_role_action.telegram_bot.yml b/config/sync/system.action.user_add_role_action.telegram_bot.yml new file mode 100644 index 0000000..f5ad4b9 --- /dev/null +++ b/config/sync/system.action.user_add_role_action.telegram_bot.yml @@ -0,0 +1,14 @@ +uuid: f23eae79-6b16-43bf-ad3b-b83a5f456195 +langcode: en +status: true +dependencies: + config: + - user.role.telegram_bot + module: + - user +id: user_add_role_action.telegram_bot +label: 'Add the Telegram bot role to the selected user(s)' +type: user +plugin: user_add_role_action +configuration: + rid: telegram_bot diff --git a/config/sync/system.action.user_remove_role_action.telegram_bot.yml b/config/sync/system.action.user_remove_role_action.telegram_bot.yml new file mode 100644 index 0000000..4a8eb74 --- /dev/null +++ b/config/sync/system.action.user_remove_role_action.telegram_bot.yml @@ -0,0 +1,14 @@ +uuid: 3ff75386-1efc-4a9a-a519-4ebdd9214266 +langcode: en +status: true +dependencies: + config: + - user.role.telegram_bot + module: + - user +id: user_remove_role_action.telegram_bot +label: 'Remove the Telegram bot role from the selected user(s)' +type: user +plugin: user_remove_role_action +configuration: + rid: telegram_bot diff --git a/config/sync/user.role.telegram_bot.yml b/config/sync/user.role.telegram_bot.yml new file mode 100644 index 0000000..5d69971 --- /dev/null +++ b/config/sync/user.role.telegram_bot.yml @@ -0,0 +1,22 @@ +uuid: ee20a0d5-2658-4a72-bfb5-4d6931951d7a +langcode: en +status: true +dependencies: + config: + - rest.resource.wisetrout_do_bot_modules_list + - rest.resource.wisetrout_do_bot_subscribe + - rest.resource.wisetrout_do_bot_update_status + - rest.resource.wisetrout_do_bot_user_info + - rest.resource.wisetrout_do_bot_wipeout + module: + - rest +id: telegram_bot +label: 'Telegram bot' +weight: 4 +is_admin: false +permissions: + - 'restful get wisetrout_do_bot_modules_list' + - 'restful get wisetrout_do_bot_user_info' + - 'restful post wisetrout_do_bot_subscribe' + - 'restful post wisetrout_do_bot_update_status' + - 'restful post wisetrout_do_bot_wipeout' diff --git a/config/sync/views.view.ai_contributors_admin.yml b/config/sync/views.view.ai_contributors_admin.yml index a72af45..7410bba 100644 --- a/config/sync/views.view.ai_contributors_admin.yml +++ b/config/sync/views.view.ai_contributors_admin.yml @@ -1,3 +1,4 @@ +uuid: e4738277-0262-4d7d-b763-8af9058d83f9 langcode: en status: true dependencies: @@ -468,9 +469,6 @@ display: multiple: false remember_roles: authenticated: authenticated - anonymous: '0' - content_editor: '0' - administrator: '0' is_grouped: false group_info: label: '' @@ -509,9 +507,6 @@ display: multiple: false remember_roles: authenticated: authenticated - anonymous: '0' - content_editor: '0' - administrator: '0' is_grouped: false group_info: label: '' @@ -617,11 +612,11 @@ display: type: none title: 'AI Contributors' description: '' + weight: 0 expanded: false + menu_name: main parent: '' - weight: 0 context: '0' - menu_name: main cache_metadata: max-age: -1 contexts: diff --git a/web/modules/custom/wisetrout_do_bot/README.md b/web/modules/custom/wisetrout_do_bot/README.md new file mode 100644 index 0000000..65d437a --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/README.md @@ -0,0 +1,19 @@ +## DO bot custom module +This module creates the neccessary endpoints and hooks to be used with the DO Telegram bot. + +### Functionality +This module has endpoints for saving user Telegram preferences and hooks to notify users about new/updated issues and new modules. + +### Usage + +#### Bot token + +In order for the module to work, it must receive the bot token. This is the token you receive at Telegram bot creation (via BotFather bot). Save the token inside web\sites\default\settings.php, towards the bottom: + +``` +$settings['tgbot_token'] = '...'; // your token here +``` +This token must be the same as the one inside "wisetrout-do-bot\.env". + +#### Cron setup +Run cron regularly (once a day) to send out daily summaries to Telegram subscribers. \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Hook/CronHooks.php b/web/modules/custom/wisetrout_do_bot/src/Hook/CronHooks.php new file mode 100644 index 0000000..27458f0 --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Hook/CronHooks.php @@ -0,0 +1,301 @@ +getRequestTime(); + $last_run = \Drupal::state()->get('wisetrout_do_bot.cron_last_run', 0); + + // Check if today is a different day than the last run. + if (date('Y-m-d', $current_time) !== date('Y-m-d', $last_run)) { + + + $creations = $this->getCreatedIssues(); + $updates = $this->getUpdatedIssues(); + + $moduleSummaries = $this->createModuleSummaries($updates, $creations); + $newModulesSummary = $this->createNewModulesSummary(); + + $chatIds = $this->getActiveDailySubscriberIds(); + + $subscriptions = $this->getDailySubscriptions($chatIds); + + + $moduleSummaryNotifications = $this->createModuleSummaryNotifications($subscriptions, $moduleSummaries); + + $queue = \Drupal::queue('telegram_bot_queue'); + + $items_to_process = $newModulesSummary ? + array_merge($moduleSummaryNotifications, $this->createModuleCreationNotifications($chatIds, $newModulesSummary)): + $moduleSummaryNotifications; + + foreach ($items_to_process as $item_data) { + $queue->createItem($item_data); + } + + } + + // Update the last run time. + \Drupal::state()->set('wisetrout_do_bot.cron_last_run', $current_time); + } + + protected function getActiveDailySubscriberIds(){ + return \Drupal::database() + ->query("SELECT chat_id FROM {telegram_subscribers} WHERE status = 1 AND type = 'daily'") + ->fetchCol(); + } + + protected function getDailySubscriptions($chatIds){ + return \Drupal::database() + ->query("SELECT chat_id, module_id FROM {telegram_subscriptions} WHERE chat_id IN (:chat_ids[])", [ + ':chat_ids[]' => $chatIds, + ]) + ->fetchAll(); + } + + + protected function getCreatedIssues(){ + $yesterday = strtotime('-1 day'); + $storage = \Drupal::entityTypeManager()->getStorage('node'); + $createdIds = \Drupal::entityQuery('node') + ->condition('type', 'ai_issue') + ->condition('created', $yesterday, '>=') + ->accessCheck(FALSE) + ->execute(); + + $data = []; + + $createdIssueNodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($createdIds); + + return $createdIssueNodes; + + } + + protected function getUpdatedIssues(){ + $yesterday = strtotime('-1 day'); + $storage = \Drupal::entityTypeManager()->getStorage('node'); + + $updatedIds = \Drupal::entityQuery('node') + ->condition('type', 'ai_issue') + ->condition('changed', $yesterday, '>=') + ->condition('created', $yesterday, '<') + ->accessCheck(FALSE) + ->execute(); + + $data = []; + + $updatedIssueNodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($updatedIds); + + return $updatedIssueNodes; + } + + + + protected function createModuleSummaries($updatedNodes, $createdNodes){ + $summaries = []; + $storage = \Drupal::entityTypeManager()->getStorage('node'); + $moduleUpdates = []; + + foreach($createdNodes as $createdNode){ + + $moduleId = $createdNode->get('field_issue_module')->entity->id(); + + if($moduleUpdates[$moduleId]){ + $createdIssues = $moduleUpdates[$moduleId]['created']; + if($moduleUpdates[$moduleId]['created']){ + $moduleUpdates[$moduleId]['created'][] = $createdNode; + }else{ + $moduleUpdates[$moduleId]['created'] = [$createdNode]; + } + }else{ + $moduleUpdates[$moduleId] = [ + 'label' => $createdNode->get('field_issue_module')->entity->label(), + 'created' => [$createdNode], + ]; + } + } + + foreach($updatedNodes as $updatedNode){ + + $moduleId = $updatedNode->get('field_issue_module')->entity->id(); + + if($moduleUpdates[$moduleId]){ + if($moduleUpdates[$moduleId]['updated']){ + $moduleUpdates[$moduleId]['updated'][] = $updatedNode; + }else{ + $moduleUpdates[$moduleId]['updated'] = [$updatedNode]; + } + }else{ + $moduleUpdates[$moduleId] = [ + 'label' => $updatedNode->get('field_issue_module')->entity->label(), + 'updated' => [$updatedNode], + ]; + } + + } + + foreach($moduleUpdates as $moduleId => $moduleInfo){ + + $text = "🏷️{$moduleInfo["label"]} updates:\n"; + + if($moduleInfo['created']){ + $text .= "New issue(s):\n"; + foreach($moduleInfo['created'] as $createdNode){ + $text .= "🌱{$createdNode->label()}\n"; + } + } + + if($moduleInfo['updated']){ + $text .= "Issue updates: \n"; + foreach($moduleInfo['updated'] as $issueNode){ + + $changes = $this->findDailyChanges($issueNode); + + $text .= "✏️{$issueNode->label()}:\n"; + + foreach($changes as $change){ + $text .= "{$change['name']}: {$change['old']} -> {$change['new']}\n"; + } + + } + } + + $summaries[$moduleId] = $text; + + } + + return $summaries; + + } + + protected function createNewModulesSummary(){ + $yesterday = strtotime('-1 day'); + $storage = \Drupal::entityTypeManager()->getStorage('node'); + $createdIds = \Drupal::entityQuery('node') + ->condition('type', 'ai_module') + ->condition('created', $yesterday, '>=') + ->accessCheck(FALSE) + ->execute(); + + if(!count($createdIds)) return null; + + $createdModuleNodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($createdIds); + $createdModuleNames = array_map(function($node){ + return $node->field_module_machine_name[0]->value; + }, $createdModuleNodes); + + $modulesList = join(', ', $createdModuleNames); + + $summary = "🏷️ New modules created: {$modulesList}."; + return $summary; + } + + protected function createModuleSummaryNotifications($subscriptions, $moduleSummaries){ + + $notifications = []; + + foreach($subscriptions as $subscription){ + if(isset($moduleSummaries[$subscription->module_id])){ + $notifications[] = [ + "chatId" => $subscription->chat_id, + "message" => $moduleSummaries[$subscription->module_id], + ]; + } + } + + return $notifications; + } + + protected function createModuleCreationNotifications($chatIds, $summary){ + $notifications = array_map(function($chatId) use ($summary){ + return [ + "chatId" => $chatId, + "message" => $summary, + ]; + }, $chatIds); + return $notifications; + } + + protected function findDailyChanges($node){ + + $storage = \Drupal::entityTypeManager()->getStorage('node'); + + $current_time = \Drupal::time()->getRequestTime(); + $yesterday = strtotime('-1 day'); + + $oldVid = $this->getRevisionBeforeTimestamp($node->id(), $yesterday); + $oldNode = $storage->loadRevision($oldVid); + + return $this->getChangedFields($node, $oldNode); + } + + protected function getRevisionBeforeTimestamp($nid, $timestamp) { + $database = \Drupal::database(); + $storage = \Drupal::entityTypeManager()->getStorage('node'); + + return $database->select('node_revision', 'nr') + ->fields('nr', ['vid']) + ->condition('nid', $nid) + ->condition('revision_timestamp', $timestamp, '<') + ->orderBy('revision_timestamp', 'DESC') + ->range(0, 1) + ->execute() + ->fetchField(); + } + + + protected function getChangedFields($newNode, $oldNode){ + $changes = []; + $storage = \Drupal::entityTypeManager()->getStorage('node'); + + foreach ($newNode->getFields() as $fieldName => $fieldItem) { + if (!str_starts_with($fieldName, 'field_')) continue; + + if (!$newNode->get($fieldName)->equals($oldNode->get($fieldName))) { + + $oldValue; + $newValue; + + $oldValue = $oldNode->get($fieldName)->getString(); + $newValue = $fieldItem->getString(); + + if($fieldItem->getFieldDefinition()->getType() === "entity_reference"){ + $oldValue = $this->convertIdsToLabels($oldValue, $storage); + $newValue = $this->convertIdsToLabels($newValue, $storage); + } + + $changes[] = [ + 'name' => $fieldItem->getFieldDefinition()->getLabel(), + 'old' => $oldValue ? $oldValue : '(none)', + 'new' => $newValue ? $newValue : '(none)', + ]; + } + } + + return $changes; + } + + function convertIdsToLabels($string, $storage){ + if($string === "") return null; + $idsArray = explode(',', $string); + $nodesArray = $storage->loadMultiple($idsArray); + $nodeLabels = array_map(function($node){ + return $node->label(); + }, $nodesArray); + $labelsString = implode(', ', $nodeLabels); + return $labelsString; + } +} \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Hook/NodeHooks.php b/web/modules/custom/wisetrout_do_bot/src/Hook/NodeHooks.php new file mode 100644 index 0000000..c4f80cb --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Hook/NodeHooks.php @@ -0,0 +1,176 @@ +isPublished()) { + if ($node->getType() === 'ai_issue') { + $this->notifyAboutIssueCreation($node); + } + if ($node->getType() === 'ai_module') { + $this->notifyAboutModuleCreation($node); + } + } + } + + #[Hook('node_update')] + public function onNodeUpdate(NodeInterface $node): void { + if ($node->isPublished() && $node->getType() === 'ai_issue') { + if ($node->original->isPublished()) { + $this->notifyAboutIssueUpdate($node); + } + else { + $this->notifyAboutIssueCreation($node); + } + } + + if ($node->isPublished() && $node->getType() === 'ai_module' && !($node->original->isPublished())) { + $this->notifyAboutModuleCreation($node); + } + } + + protected function notifyAboutIssueCreation(NodeInterface $node): void { + + $chatIds = $this->getModuleChatIds($node); + + $moduleName = $node->get('field_issue_module')->entity->label(); + + $label = strip_tags($node->label()); + $message = "🌱 New issue created in {$moduleName}: $label + project URL: {$node->get('field_issue_url')->uri}"; + + $this->sendBotNotifications($chatIds, $message); + } + + protected function notifyAboutIssueUpdate(NodeInterface $node): void { + $chatIds = $this->getModuleChatIds($node); + $updates = $this->findNodeChanges($node); + + if (!count($updates)) { + return; + } + + $message = "✏️Issue updated : {$node->label()}"; + + foreach ($updates as $update) { + $message = $message . "\n{$update['title']}: {$update['old_value']} -> {$update['new_value']}"; + } + + $this->sendBotNotifications($chatIds, $message); + } + + protected function notifyAboutModuleCreation(NodeInterface $node): void { + $activeUserIds = $this->getActiveUserIds(); + $message = "🏷️New module created: {$node->label()}";; + $this->sendBotNotifications($activeUserIds, $message); + } + + protected function getModuleChatIds($node) { + $moduleEntity = $node->get('field_issue_module')->entity; + $moduleId = $moduleEntity->id(); + $db = \Drupal::database(); + $chatIds = $db + ->query("SELECT chat_id FROM {telegram_subscriptions} WHERE module_id = :module_id", [ + ':module_id' => $moduleId, + ]) + ->fetchCol(); + $activeChatIds = $db + ->query("SELECT chat_id FROM {telegram_subscribers} WHERE chat_id IN (:chat_ids[]) AND status = 1 AND type = 'instant'", [ + ':chat_ids[]' => $chatIds, + ]) + ->fetchCol(); + return $activeChatIds; + } + + protected function getActiveUserIds() { + $db = \Drupal::database(); + $activeUserIds = $db + ->query("SELECT chat_id FROM {telegram_subscribers} WHERE status = 1 AND type = 'instant'") + ->fetchCol(); + return $activeUserIds; + } + + protected function findNodeChanges($node) { + $changedFields = []; + $original = $node->original; + foreach ($node->getFieldDefinitions() as $fieldName => $fieldDefinition) { + if (str_starts_with($fieldName, 'field_') && !$node->get($fieldName) + ->equals($original->get($fieldName))) { + $changedFields[] = [ + 'title' => (string) $fieldDefinition->getLabel(), + 'old_value' => $this->convertFieldToString($original, $fieldName), + 'new_value' => $this->convertFieldToString($node, $fieldName), + ]; + } + } + + return $changedFields; + } + + protected function convertFieldToString($node, $fieldName) { + $field = $node->get($fieldName); + + if ($field->isEmpty()) { + return ''; + } + + $values = []; + foreach ($field as $item) { + $itemValues = $item->getValue(); + $values[] = strip_tags($itemValues['value'] ?? $item_values['target_id'] ?? (string) reset($itemValues)); + } + + return implode(', ', $values); + } + + protected function sendBotNotifications($chatIds, $message) { + $httpClient = \Drupal::httpClient(); + $bot_token = \Drupal::service('settings')->get('tgbot_token'); + + if (strlen($message) > self::MAX_MESSAGE_LENGTH) { + $message = substr($message, 0, self::MAX_MESSAGE_LENGTH - 4) . '...'; + } + + foreach($chatIds as $chatId){ + + $url = 'https://api.telegram.org/bot' + . $bot_token + . '/sendMessage'; + $payload = [ + 'chat_id' => $chatId, + 'text' => $message, + "parse_mode" => "HTML", + ]; + + try{ + $response = $httpClient + ->post($url, [ + 'body' => json_encode($payload), + 'headers' => [ + 'Content-Type' => 'application/json', + ] + ]); + }catch (RequestException $e) { + \Drupal::logger('wtbot')->warning($e->getMessage() . ': ' . $message); + } + } + + } + +} diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/QueueWorker/DailySummaryProcessor.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/QueueWorker/DailySummaryProcessor.php new file mode 100644 index 0000000..430e45d --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/QueueWorker/DailySummaryProcessor.php @@ -0,0 +1,43 @@ + $data['chatId'], + 'text' => $data['message'], + "parse_mode" => "HTML", + ]; + + $response = $httpClient + ->post($url, [ + 'body' => json_encode($payload), + 'headers' => [ + 'Content-Type' => 'application/json', + ] + ]); + } + +} \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/ModulesList.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/ModulesList.php new file mode 100644 index 0000000..397ef9b --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/ModulesList.php @@ -0,0 +1,39 @@ +condition('type', 'ai_module') + ->accessCheck(FALSE) + ->execute(); + + $nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids); + + $nodeNames = []; + + foreach($nodes as $node){ + $nodeNames[] = $node->field_module_machine_name[0]->value; + } + + return new ResourceResponse($nodeNames); + } +} \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Subscribe.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Subscribe.php new file mode 100644 index 0000000..abb74fb --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Subscribe.php @@ -0,0 +1,151 @@ +database + ->query("SELECT * FROM {telegram_subscribers} WHERE chat_id = :cid", [':cid' => $userInfo["id"]]) + ->fetchField(); + + if($subscriberData){ + $this->database->update('telegram_subscribers') + ->condition('chat_id', $userInfo["id"]) + ->fields([ + 'type' => $userInfo["type"], + ]) + ->execute(); + }else{ + $this->database->insert('telegram_subscribers') + ->fields([ + 'chat_id' => $userInfo["id"], + 'status' => 1, + 'created' => $this->currentRequest->server->get('REQUEST_TIME'), + 'type' => $userInfo["type"], + ]) + ->execute(); + } + } + + private function updateModulesList($cid, $moduleNames){ + + if(!count($moduleNames)){ + $this->deleteAllModules($cid); + + }else{ + $existingSubscriptions = $this->database + ->query("SELECT * FROM {telegram_subscriptions} WHERE chat_id = :cid", [':cid' => $cid]) + ->fetchAll(); + + $moduleIds = \Drupal::entityQuery('node') + ->condition('type', 'ai_module') + ->accessCheck(FALSE) + ->condition('field_module_machine_name', $moduleNames, 'IN') + ->execute(); + + $this->subscribeToNewModules($cid, $moduleIds, $existingSubscriptions); + $this->unsubscribeFromOldModules($cid, $moduleIds, $existingSubscriptions); + } + + + } + + private function subscribeToNewModules($cid, $moduleIds, $existingSubscriptions){ + + $insertQuery = $this->database + ->insert('telegram_subscriptions') + ->fields(['chat_id', 'module_id', 'created']); + + + foreach($moduleIds as $moduleId){ + + $subscriptionExists = array_find($existingSubscriptions, function($existingSubscription) use ($moduleId){ + return $existingSubscription->module_id === $moduleId; + }); + + if(!$subscriptionExists){ + $valuesToInsert = [ + 'chat_id' => $cid, + 'module_id' => $moduleId, + 'created' => $this->currentRequest->server->get('REQUEST_TIME'), + ]; + $insertQuery->values($valuesToInsert); + } + } + + $insertQuery->execute(); + } + + private function unsubscribeFromOldModules($cid, $moduleIds, $existingSubscriptions){ + + $moduleIdsToDelete = []; + + foreach($existingSubscriptions as $existingSubscription){ + $keepSubscription = array_find($moduleIds, function($moduleId) use ($existingSubscription){ + return $moduleId === $existingSubscription->module_id; + }); + if(!$keepSubscription){ + $moduleIdsToDelete[] = $existingSubscription->module_id; + } + } + + if(count($moduleIdsToDelete)) { + $this->database + ->delete('telegram_subscriptions') + ->condition('module_id', $moduleIdsToDelete, 'IN') + ->execute(); + } + + + + } + + private function deleteAllModules($cid){ + $this->database + ->delete('telegram_subscriptions') + ->condition('chat_id', $cid) + ->execute(); + } + + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->currentRequest = $container->get('request_stack')->getCurrentRequest(); + $instance->database = \Drupal::database(); + return $instance; + } + + + public function post() { + $content = $this->currentRequest->getContent(); + + $params = json_decode($content, TRUE); + + $this->upsertUser($params['userInfo']); + + $cid = strval($params['userInfo']['id']); + $moduleNames = $params['modules']; + $this->updateModulesList($cid, $moduleNames); + + return new ResourceResponse(NULL, 204); + } +} \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UpdateStatus.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UpdateStatus.php new file mode 100644 index 0000000..f4603a1 --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UpdateStatus.php @@ -0,0 +1,51 @@ +currentRequest = $container->get('request_stack')->getCurrentRequest(); + return $instance; + } + + + public function post() { + $content = $this->currentRequest->getContent(); + + $params = json_decode($content, TRUE); + + $cid = strval($params['chatId']); + $newStatus = $params['subscribed'] ? 1 : 0; + + $database = \Drupal::database(); + + $database + ->update('telegram_subscribers') + ->fields([ + 'status' => $newStatus, + ]) + ->condition('chat_id', $cid) + ->execute(); + + return new ResourceResponse(NULL, 204); + } +} \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UserInfo.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UserInfo.php new file mode 100644 index 0000000..eaa4630 --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/UserInfo.php @@ -0,0 +1,64 @@ +query("SELECT status, type FROM {telegram_subscribers} WHERE chat_id = :cid", [':cid' => $cid]) + ->fetchAssoc(); + return $queryResult; + } + + private function readModulesList($cid, $database){ + + $moduleIds = $database + ->query("SELECT module_id FROM {telegram_subscriptions} WHERE chat_id = :cid", [':cid' => $cid]) + ->fetchCol(); + + $nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($moduleIds); + + $modules = []; + + foreach($nodes as $node){ + $modules[] = $node->field_module_machine_name[0]->value; + } + + return array_values($modules); + } + + public function get($cid){ + + $database = \Drupal::database(); + + $subscriberInfo = $this->readSubscriberInfo($cid, $database); + + if(!$subscriberInfo) { + return new ResourceResponse(null, 204); + }else{ + $modulesList = $this->readModulesList($cid, $database); + return new ResourceResponse([ + 'subscribed' => !!$subscriberInfo['status'], + 'modules' => $modulesList, + 'type' => $subscriberInfo['type'], + ]); + } + + } +} diff --git a/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Wipeout.php b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Wipeout.php new file mode 100644 index 0000000..d793049 --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/src/Plugin/rest/resource/Wipeout.php @@ -0,0 +1,60 @@ +database + ->delete('telegram_subscribers') + ->condition('chat_id', $cid) + ->execute(); + } + + private function deleteUserSubscriptions($cid){ + $this->database + ->delete('telegram_subscriptions') + ->condition('chat_id', $cid) + ->execute(); + } + + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->currentRequest = $container->get('request_stack')->getCurrentRequest(); + $instance->database = $container->get('database'); + return $instance; + } + + + public function post() { + $content = $this->currentRequest->getContent(); + + $params = json_decode($content, TRUE); + + $cid = $params['chatId']; + + $this->deleteUserInfo($cid); + $this->deleteUserSubscriptions($cid); + + return new ResourceResponse(['message' => 'success!']); + } +} diff --git a/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.info.yml b/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.info.yml new file mode 100644 index 0000000..e9c6707 --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.info.yml @@ -0,0 +1,6 @@ +name: Wisetrout DO bot Module +description: Enables Wisetrout DO bot in Telegram +package: Custom + +type: module +core_version_requirement: ^10.3 || ^11 \ No newline at end of file diff --git a/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.install b/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.install new file mode 100644 index 0000000..9597c6a --- /dev/null +++ b/web/modules/custom/wisetrout_do_bot/wisetrout_do_bot.install @@ -0,0 +1,87 @@ + 'Stores data about subscriptions to the Telegram bot.', + 'fields' => [ + 'id' => [ + 'description' => 'Primary Key: Unique identifier for each entry.', + 'type' => 'serial', + 'not null' => TRUE, + ], + 'chat_id' => [ + 'description' => 'Chat id to be used for sending messages.', + 'type' => 'char', + 'length' => '10', + 'not null' => TRUE, + ], + 'module_id' => [ + 'description' => 'Id of the module user has subscribed to.', + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE + ], + 'created' => [ + 'description' => 'The time that the subscription was created.', + 'type' => 'int', + 'size' => 'big', + 'not null' => TRUE, + 'unsigned' => TRUE, + ] + ], + 'primary key' => ['id'], + ]; + + $schema['telegram_subscribers'] = [ + 'description' => 'Stores data about Telegram users subscribed to the bot.', + 'fields' => [ + 'chat_id' => [ + 'description' => 'Chat id to be used for sending messages.', + 'type' => 'char', + 'length' => '10', + 'not null' => TRUE, + ], + 'type' => [ + 'description' => 'Subscription type (instant or daily)', + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => 255, + 'default' => 'instant', + ], + 'status' => [ + 'description' => 'Status of subscription. 1 = active, 0 = inactive.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE + ], + 'created' => [ + 'description' => 'The time that the entry was created.', + 'type' => 'int', + 'size' => 'big', + 'not null' => TRUE, + 'unsigned' => TRUE, + ] + ], + 'primary key' => ['chat_id'], + ]; + + return $schema; +} + +function wisetrout_do_bot_update_11001(){ + $schema = \Drupal::database()->schema(); + $spec = [ + 'description' => 'Subscription type (instant or daily)', + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'instant', + 'length' => 255, + ]; + + $schema->addField('telegram_subscribers', 'type', $spec); +} + diff --git a/wisetrout-do-bot/.dockerignore b/wisetrout-do-bot/.dockerignore new file mode 100644 index 0000000..5b0e72a --- /dev/null +++ b/wisetrout-do-bot/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.git +.gitignore +.env \ No newline at end of file diff --git a/wisetrout-do-bot/.env.example b/wisetrout-do-bot/.env.example new file mode 100644 index 0000000..d399cfa --- /dev/null +++ b/wisetrout-do-bot/.env.example @@ -0,0 +1,6 @@ +BOT_TOKEN=123 +BASE_URL='http://ddev-drupal-ai-dev-tracker-web:80' +DRUPAL_AUTH_TOKEN_CLIENT_ID='client_id' +DRUPAL_AUTH_TOKEN_CLIENT_SECRET='secretysecret' +DRUPAL_AUTH_TOKEN_SCOPE='token_scope' +PREFERENCES_ENDPOINT_BASE_URL='http://ddev-drupal-ai-dev-tracker-web:80' diff --git a/wisetrout-do-bot/Dockerfile b/wisetrout-do-bot/Dockerfile new file mode 100644 index 0000000..8e35885 --- /dev/null +++ b/wisetrout-do-bot/Dockerfile @@ -0,0 +1,6 @@ +FROM node:lts-alpine +WORKDIR /app +# COPY package*.json ./ +# RUN npm install +COPY . . +CMD ["sh", "-c", "npm install && npm start"] \ No newline at end of file diff --git a/wisetrout-do-bot/README.md b/wisetrout-do-bot/README.md new file mode 100644 index 0000000..54bb41e --- /dev/null +++ b/wisetrout-do-bot/README.md @@ -0,0 +1,18 @@ +# Telegram bot + +The DO Telegram bot allows users to subscribe to AI Dashboard issue updates. They then receive notifications directly in the chat bot. There are two types of subscriptions: getting updates instantly on issue creation/update or receiving summaries once a day. This directory contains the code for the behavior of the Telegram bot, written in Node.js. It contains all of the commands and menus that the user sees once they interact with the bot in Telegram. + +## Usage +To make the bot work, several steps must be done: + +### Create bot user +Create a new user with the telegram_bot role. + +### Set up OAuth for the bot user +The bot uses OAuth to access the api and save user preferences. To set up Oauth, go to /admin/config/people/simple_oauth and set up the keys. After that you will need to create a consumer dedicated to the Telegram bot. Once this is done, you can fill up the DRUPAL_AUTH_TOKEN_CLIENT_ID, DRUPAL_AUTH_TOKEN_CLIENT_SECRET, DRUPAL_AUTH_TOKEN_SCOPE environment variables inside .env. + +### Create bot in Telegram +If you do not have one already, you must create a bot token via the BotFather bot in Telegram. Save the bot token as BOT_TOKEN variable in .env. + +### Restart project +The newly created environment variables will be passed to the bot and the bot will start working. diff --git a/wisetrout-do-bot/api-calls/modules-list.js b/wisetrout-do-bot/api-calls/modules-list.js new file mode 100644 index 0000000..55b4d60 --- /dev/null +++ b/wisetrout-do-bot/api-calls/modules-list.js @@ -0,0 +1,15 @@ +import { sendAuthorizedRequest } from "../oauth.js"; + +export async function getModulesList(){ + + const response = await sendAuthorizedRequest(process.env.BASE_URL + '/api/telegram/modules-list', { + method: 'GET', + }) + + const modules = await response.json(); + + modules.sort(); + + return modules; +} + diff --git a/wisetrout-do-bot/api-calls/oauth.js b/wisetrout-do-bot/api-calls/oauth.js new file mode 100644 index 0000000..4a39eb5 --- /dev/null +++ b/wisetrout-do-bot/api-calls/oauth.js @@ -0,0 +1,41 @@ +const OAUTH_MAX_ATTEMPTS = 5; +const RETRY_INTERVAL_MS = 10000; + +export async function getOAuthToken(){ + + const data = { + grant_type: 'client_credentials', + client_id: process.env.DRUPAL_AUTH_TOKEN_CLIENT_ID || '', + client_secret: process.env.DRUPAL_AUTH_TOKEN_CLIENT_SECRET || '', + scope: process.env.DRUPAL_AUTH_TOKEN_SCOPE || '', + }; + + const body = new URLSearchParams(data); + + let attemptsDone = 0; + + do{ + attemptsDone++; + try{ + const res = await fetch(process.env.BASE_URL + '/oauth/token', { + method: 'POST', + body: body + }); + if(!res.ok) throw new Error(res.status); + const responseData = await res.json(); + return responseData; + }catch(err){ + console.log('Failed attempt No ' + attemptsDone); + console.log(err); + await new Promise(res => { + setTimeout(()=> {res()}, RETRY_INTERVAL_MS); + }); + continue; + } + + + }while(attemptsDone < OAUTH_MAX_ATTEMPTS); + + + +} \ No newline at end of file diff --git a/wisetrout-do-bot/api-calls/subscription.js b/wisetrout-do-bot/api-calls/subscription.js new file mode 100644 index 0000000..2990818 --- /dev/null +++ b/wisetrout-do-bot/api-calls/subscription.js @@ -0,0 +1,89 @@ +import { sendAuthorizedRequest } from "../oauth.js"; + +export async function subscribe(userInfo, modules, type){ + console.log('Creating subscription...'); + console.log(userInfo); + console.log('Modules:'); + console.log(modules); + + const body = JSON.stringify({userInfo: {...userInfo, type}, modules}); + + console.log('Body:'); + console.log(body); + + + const res = await sendAuthorizedRequest(process.env.BASE_URL + '/api/telegram/subscribe', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); + + console.log(res); + +} + +export async function updateStatus(chatId, subscribed){ + console.log('Updating status...'); + + const body = JSON.stringify({ chatId, subscribed }); + + console.log('Body:'); + console.log(body); + + const res = await sendAuthorizedRequest(process.env.BASE_URL + '/api/telegram/update-status', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); + + + console.log(res); + +} + +export async function checkSubscription(chatId){ + const res = await sendAuthorizedRequest(`${process.env.BASE_URL}/api/telegram/user-info/${chatId}`, { + method: 'GET' + }); + + console.log('user Info success!'); + console.log(res); + + if(res.status === 204) return null; + + let data; + try { + data = await res.json(); + } catch (e) { + throw new Error('Invalid JSON response from server'); + } + + console.log('User info:'); + console.log(data); + + return data; +} + +export async function wipeoutData(chatId){ + console.log('Wipeout...'); + + const body = JSON.stringify({ chatId }); + + console.log('Body:'); + console.log(body); + + const res = await sendAuthorizedRequest(process.env.BASE_URL + '/api/telegram/wipeout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); + + console.log('Success!'); + console.log(res); +} diff --git a/wisetrout-do-bot/bot.js b/wisetrout-do-bot/bot.js new file mode 100644 index 0000000..bdcdf08 --- /dev/null +++ b/wisetrout-do-bot/bot.js @@ -0,0 +1,82 @@ +import { Telegraf, session } from 'telegraf'; +import preferencesMiddleware from './middlewares/preferences-middleware.js'; +import { scene as subscrScene } from './scenes/subscription-menu.js'; +import { createActionsKeyboard } from './markup/keyboards.js'; +import { scene as wipeoutScene } from './scenes/wipeout.js'; +import { updateStatus } from './api-calls/subscription.js'; +import { Stage } from 'telegraf/scenes'; +import { activateOAuthToken } from './oauth.js'; + +activateOAuthToken() +.then(() => { + console.log('Oauth complete'); + activateBot(); +}); + + + +function activateBot(){ + + const bot = new Telegraf(process.env.BOT_TOKEN); + + bot.use(session()); + + const stage = new Stage([subscrScene, wipeoutScene]); + + bot.use(preferencesMiddleware); + bot.use(stage.middleware()); + + bot.start(ctx => { + + ctx.reply( + 'Welcome to the Wisetrout Drupal org bot!', + createActionsKeyboard(ctx) + ) + }); + + bot.action('sub', ctx => { + ctx.scene.enter('subscription-menu'); + }) + + bot.action('unsub', async ctx => { + await Promise.all([ + ctx.reply('Cancelling your subscription...'), + updateStatus(ctx.from.id, false) + ]); + + ctx.session.userInfo.subscribed = false; + ctx.reply('Unsubscribed from all updates. We will keep your preferences saved in case you want to renew your subscription.', + createActionsKeyboard(ctx) + ); + }); + + bot.action('resub', async ctx => { + + await Promise.all([ + ctx.reply('Reactivating your subscription...'), + updateStatus(ctx.from.id, true) + ]); + + ctx.session.userInfo.subscribed = true; + ctx.reply('Subscription reactivated', + createActionsKeyboard(ctx) + ); + + }) + + bot.action('wipeout', async ctx => { + ctx.scene.enter('wipeout'); + }); + + bot.help(async ctx => { + ctx.reply('You may use the following commands to manage your subscription:', + createActionsKeyboard(ctx) + ); + }); + + bot.launch(); + + // Enable graceful stop + process.once('SIGINT', () => bot.stop('SIGINT')) + process.once('SIGTERM', () => bot.stop('SIGTERM')) +} diff --git a/wisetrout-do-bot/compose.yaml b/wisetrout-do-bot/compose.yaml new file mode 100644 index 0000000..8cefa43 --- /dev/null +++ b/wisetrout-do-bot/compose.yaml @@ -0,0 +1,22 @@ +services: + do-bot-app: + container_name: do-bot-app + build: + context: . + dockerfile: Dockerfile + env_file: '.env' + extra_hosts: + - drupal-ai-dev-tracker.ddev.site=172.18.0.3 + volumes: + - type: bind + source: . + target: /app + networks: +# ddev-drupal-ai-dev-tracker_default: {} + ddev_default: {} + +networks: +# ddev-drupal-ai-dev-tracker_default: +# external: true + ddev_default: + external: true diff --git a/wisetrout-do-bot/markup/buttons.js b/wisetrout-do-bot/markup/buttons.js new file mode 100644 index 0000000..d2aeff2 --- /dev/null +++ b/wisetrout-do-bot/markup/buttons.js @@ -0,0 +1,8 @@ +import { Markup } from "telegraf"; + +export const subBtn = Markup.button.callback('📬 Subscribe to updates', 'sub'); +export const unsubBtn = Markup.button.callback('📭 Unsubscribe from all updates', 'unsub'); +export const updateSubBtn = Markup.button.callback('⚙️ Change subscription', 'sub'); +export const resubBtn = Markup.button.callback('📬 Resubscribe to updates', 'resub'); +export const cancelBtn = Markup.button.callback("🚫 Cancel", "cancel"); +export const wipeoutBtn = Markup.button.callback('🗑️ Wipe out my data', 'wipeout'); diff --git a/wisetrout-do-bot/markup/keyboards.js b/wisetrout-do-bot/markup/keyboards.js new file mode 100644 index 0000000..c1d134f --- /dev/null +++ b/wisetrout-do-bot/markup/keyboards.js @@ -0,0 +1,18 @@ +import { Markup } from "telegraf"; +import { resubBtn, subBtn, unsubBtn, updateSubBtn, wipeoutBtn } from "./buttons.js"; + +export function createActionsKeyboard(ctx){ + let keyboardRows; + + if(ctx.session.userInfo){ + if(ctx.session.userInfo.subscribed){ + keyboardRows = [[updateSubBtn], [unsubBtn]]; + }else{ + keyboardRows = [[resubBtn, wipeoutBtn]]; + } + }else{ + keyboardRows = [[subBtn]]; + } + + return Markup.inlineKeyboard(keyboardRows); +} \ No newline at end of file diff --git a/wisetrout-do-bot/middlewares/preferences-middleware.js b/wisetrout-do-bot/middlewares/preferences-middleware.js new file mode 100644 index 0000000..f033d28 --- /dev/null +++ b/wisetrout-do-bot/middlewares/preferences-middleware.js @@ -0,0 +1,10 @@ +import { checkSubscription } from "../api-calls/subscription.js"; + + +export default async function preferencesMiddleware(ctx, next){ + if(!ctx.session){ + const userInfo = await checkSubscription(ctx.from.id); + ctx.session = { userInfo }; + } + next(); +} \ No newline at end of file diff --git a/wisetrout-do-bot/oauth.js b/wisetrout-do-bot/oauth.js new file mode 100644 index 0000000..21aa71b --- /dev/null +++ b/wisetrout-do-bot/oauth.js @@ -0,0 +1,44 @@ +import { getOAuthToken } from "./api-calls/oauth.js"; + +let oAuthToken = null; + +export async function activateOAuthToken(){ + + const responseData = await getOAuthToken(); + + oAuthToken = responseData.access_token; + +} + +export async function sendAuthorizedRequest(url, { method, body, headers }){ + + const options = { method }; + + if(body) options.body = body; + options.headers = headers ? + {...headers, 'Authorization': `Bearer ${oAuthToken}`} : + {'Authorization': `Bearer ${oAuthToken}`}; + + try{ + const response = await fetch(url, options); + + if(response.status === 401){ + console.log('Token expired, getting new...'); + + const oauthData = await getOAuthToken(); + oAuthToken = oauthData.access_token; + const newResponse = await sendAuthorizedRequest(url, {method, body}); + return newResponse; + } + + if(!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + + return response; + + }catch(err){ + console.log(`Could not access ${url}: ${err}`); + } +} \ No newline at end of file diff --git a/wisetrout-do-bot/package-lock.json b/wisetrout-do-bot/package-lock.json new file mode 100644 index 0000000..99db303 --- /dev/null +++ b/wisetrout-do-bot/package-lock.json @@ -0,0 +1,540 @@ +{ + "name": "wisetrout_do_bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wisetrout_do_bot", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "telegraf": "^4.16.3" + }, + "devDependencies": { + "nodemon": "^3.1.14" + } + }, + "node_modules/@telegraf/types": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", + "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-timeout": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", + "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-compare": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz", + "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==", + "license": "MIT", + "dependencies": { + "buffer-alloc": "^1.2.0" + } + }, + "node_modules/sandwich-stream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", + "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/telegraf": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz", + "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==", + "license": "MIT", + "dependencies": { + "@telegraf/types": "^7.1.0", + "abort-controller": "^3.0.0", + "debug": "^4.3.4", + "mri": "^1.2.0", + "node-fetch": "^2.7.0", + "p-timeout": "^4.1.0", + "safe-compare": "^1.1.4", + "sandwich-stream": "^2.0.2" + }, + "bin": { + "telegraf": "lib/cli.mjs" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/wisetrout-do-bot/package.json b/wisetrout-do-bot/package.json new file mode 100644 index 0000000..b3a2c13 --- /dev/null +++ b/wisetrout-do-bot/package.json @@ -0,0 +1,17 @@ +{ + "name": "wisetrout_do_bot", + "version": "1.0.0", + "description": "", + "main": "bot.js", + "scripts": { + "start": "nodemon bot.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "telegraf": "^4.16.3" + }, + "devDependencies": { + "nodemon": "^3.1.14" + } +} diff --git a/wisetrout-do-bot/scenes/subscription-menu.js b/wisetrout-do-bot/scenes/subscription-menu.js new file mode 100644 index 0000000..e559698 --- /dev/null +++ b/wisetrout-do-bot/scenes/subscription-menu.js @@ -0,0 +1,197 @@ +import { Markup, Scenes } from "telegraf"; +import { getModulesList } from "../api-calls/modules-list.js"; +import { subscribe } from "../api-calls/subscription.js"; + +const MODULES_PER_PAGE = 10; +const cancelBtn = Markup.button.callback("Cancel", "cancel"); + +export const scene = new Scenes.BaseScene('subscription-menu'); + +scene.enter(async ctx => { + + ctx.reply(`Pick subscription type: + "instant" will send you notifications immediately when updates happen + "Daily" will once a day send you a summary of all changes related to modules you watch`, Markup.inlineKeyboard([ + [ + Markup.button.callback("⚡Instant", "instant"), + Markup.button.callback("☀️ Daily", "daily") + ], + [cancelBtn] + ])); + +}); + +scene.action("instant", ctx => { + ctx.session.type = 'instant'; + offerModulesList(ctx); + +}) + +scene.action("daily", ctx => { + ctx.session.type = 'daily'; + offerModulesList(ctx); + +}) + + + +scene.action("page_back", ctx => { + ctx.session.page--; + refreshMenu(ctx); + +}) + +scene.action("page_next", ctx => { + ctx.session.page++; + refreshMenu(ctx); +}) + +scene.command(/.+/, async ctx => { + const { command } = ctx; + + console.log('Command triggered'); + console.log(command); + + if(ctx.session.selectedModules.includes(command)){ + ctx.session.selectedModules = ctx.session.selectedModules.filter(m => m != command); + }else{ + ctx.session.selectedModules.push(command); + } + + refreshMenu(ctx); + + ctx.deleteMessage(); +}) + +scene.action("select_all", async ctx => { + ctx.session.selectedModules = [...ctx.session.modules]; + + refreshMenu(ctx); + +}) + +scene.action('select_none', ctx => { + ctx.session.selectedModules = []; + refreshMenu(ctx); +}) + + +scene.action("save", async ctx => { + + await Promise.all([ + ctx.reply('Subscribing to updates..'), + subscribe(ctx.from, ctx.session.selectedModules, ctx.session.type) + ]); + ctx.session.userInfo = { + subscribed: true, + modules: ctx.session.selectedModules, + type: ctx.session.type + } + clearSceneData(ctx); + ctx.reply('Subscription successful!'); + ctx.scene.leave(); +}) + +scene.action("cancel", async ctx => { + clearSceneData(ctx); + ctx.reply('Change cancelled'); + ctx.scene.leave(); +}) + +async function offerModulesList(ctx){ + try{ + const [modules] = await Promise.all([ + getModulesList(), + ctx.reply("Loading...") + ]); + + + ctx.session.modules = modules; + ctx.session.selectedModules = ctx.session.userInfo ? ctx.session.userInfo.modules : []; + ctx.session.page = 0; + + await ctx.reply('Please pick the modules you are interested in and click "subscribe"'); + + const messageData = await ctx.reply(createModulesList(ctx), Markup.inlineKeyboard(createMenuRows(ctx))); + + const { message_id } = messageData; + + + ctx.session.menuMessageId = message_id; + + }catch(err){ + ctx.reply('Error'); + ctx.reply(err); + } +} + +function createModulesList(ctx){ + let message = ''; + const firstModuleIndex = ctx.session.page * MODULES_PER_PAGE; + const lastModuleIndex = (ctx.session.page + 1) * MODULES_PER_PAGE; + + + const modulesOnPage = ctx.session.modules + .slice(firstModuleIndex, lastModuleIndex) + .map(m => ({ + name: m, + checked: ctx.session.selectedModules.includes(m) + })) + + modulesOnPage.forEach(m => { + message += `${m.checked ?'✅': '🟡'}/${m.name}\n`; + }) + + return message; + +} + +function createMenuRows(ctx){ + + const backBtn = ctx.session.page === 0 ? + Markup.button.callback(" ", "nothing") : + Markup.button.callback("⬅️", "page_back"); + + const lastPageIndex = Math.ceil(ctx.session.modules.length / MODULES_PER_PAGE) - 1; + + const forwardButton = ctx.session.page === lastPageIndex ? + Markup.button.callback(" ", "nothing") : + Markup.button.callback("➡️", "page_next") + + return [ + [ + Markup.button.callback("✅ Select all", "select_all"), + Markup.button.callback("❌ Select none", "select_none"), + ], + [ + backBtn, + Markup.button.callback("✔️ Save", "save"), + forwardButton + ], + [cancelBtn] + ]; +} + +function refreshMenu(ctx){ + try{ + ctx.editMessageText(createModulesList(ctx), { + message_id: ctx.session.menuMessageId, + reply_markup: { + inline_keyboard: createMenuRows(ctx) + } + }); + }catch(err){ + console.log(err); + + } + +} + +function clearSceneData(ctx){ + delete ctx.session.selectedModules; + delete ctx.session.modules; + delete ctx.session.menuMessageId; + delete ctx.session.page; + delete ctx.session.type; +} + diff --git a/wisetrout-do-bot/scenes/wipeout.js b/wisetrout-do-bot/scenes/wipeout.js new file mode 100644 index 0000000..0e33130 --- /dev/null +++ b/wisetrout-do-bot/scenes/wipeout.js @@ -0,0 +1,32 @@ +import { Markup, Scenes } from "telegraf"; +import { wipeoutData } from "../api-calls/subscription.js"; + +export const scene = new Scenes.BaseScene('wipeout'); + +scene.enter(async ctx => { + ctx.reply("⚠️ Are you sure you would like to wipe out all of your data? This action will clear the list of modules you are interested in. If you decide to resubscribe, you will need to select them again.", + Markup.inlineKeyboard([ + [ + Markup.button.callback("✅ Delete my data", "delete") + ], + [ + Markup.button.callback("🚫 Cancel", "cancel") + ] + ])) +}); + +scene.action("delete", async ctx => { + await Promise.all([ + ctx.reply('Clearing your data...'), + wipeoutData(ctx.from.id) + ]); + + ctx.session.userInfo = null; + ctx.reply('Cleared successfully!'); + ctx.scene.leave(); +}) + +scene.action("cancel", ctx => { + ctx.reply("Deletion cancelled"); + ctx.scene.leave(); +}); \ No newline at end of file