├── .gitmodules ├── logs └── .empty ├── .husky ├── .gitignore └── pre-commit ├── .eslintignore ├── .npmignore ├── .prettierignore ├── .github ├── FUNDING.yml ├── assets │ ├── screenshot.png │ ├── social_discord.png │ ├── social_twitter.png │ ├── screenshot_full.png │ └── social_telegram.png ├── ISSUE_TEMPLATE │ ├── question.md │ ├── paid_support.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── release.yml │ ├── nightly.yml │ ├── beta.yml │ └── main.yml ├── jest.config.js ├── .prettierrc ├── scripts ├── version.sh ├── changelog_release.sh ├── rmdist.ts ├── configs.ts ├── debug.ts ├── changelog_release.ts ├── changelog.ts ├── githash.ts └── version.ts ├── nodemon.json ├── .vscode ├── extensions.json └── settings.json ├── .editorconfig ├── app ├── tests │ └── app.test.ts ├── core │ ├── token.ts │ └── bot.ts ├── routes │ ├── translations.ts │ ├── api │ │ ├── telegram.ts │ │ └── database.ts │ └── commands.ts ├── types │ ├── translate.interfaces.ts │ ├── voters.interfaces.ts │ ├── uservoters.interfaces.ts │ ├── settings.interfaces.ts │ ├── game.interfaces.ts │ ├── question.interfaces.ts │ └── master.interfaces.ts ├── functions │ ├── api │ │ ├── telegram │ │ │ ├── bot.ts │ │ │ └── message.ts │ │ └── database │ │ │ ├── connection.ts │ │ │ ├── settings.ts │ │ │ ├── users.ts │ │ │ ├── master.ts │ │ │ ├── scores.ts │ │ │ └── questions.ts │ ├── commands │ │ ├── launch.ts │ │ ├── version.ts │ │ ├── top10.ts │ │ ├── actions.ts │ │ ├── groups.ts │ │ ├── ping.ts │ │ ├── start.ts │ │ ├── show.ts │ │ ├── top_daily.ts │ │ ├── top_yearly.ts │ │ ├── top_monthly.ts │ │ ├── score.ts │ │ ├── settings.ts │ │ ├── hearsphoto.ts │ │ ├── master.ts │ │ └── hears.ts │ └── utils │ │ ├── logger.ts │ │ ├── admin.ts │ │ ├── utils.ts │ │ └── vote.ts ├── configs │ └── config.js.tpl └── translations │ ├── translate.ts │ ├── it.json │ └── en.json ├── .pm2-process.json ├── Dockerfile ├── Dockerfile.amd64 ├── Dockerfile.i386 ├── tsconfig.json ├── .all-contributorsrc ├── Dockerfile.armv7 ├── Dockerfile.armv8 ├── .gitignore ├── .eslintrc.cjs ├── LICENSE.md ├── CHANGELOG.md ├── .gitattributes ├── package.json ├── .all-shieldsrc └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logs/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /examples -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !/extra/**/* 3 | !*.d.ts 4 | !*.md 5 | !*.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /build 3 | /node_modules 4 | package-lock.json -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ptkdev] 2 | patreon: ptkdev 3 | ko_fi: ptkdev 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run pre-commit 5 | -------------------------------------------------------------------------------- /.github/assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/HEAD/.github/assets/screenshot.png -------------------------------------------------------------------------------- /.github/assets/social_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/HEAD/.github/assets/social_discord.png -------------------------------------------------------------------------------- /.github/assets/social_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/HEAD/.github/assets/social_twitter.png -------------------------------------------------------------------------------- /.github/assets/screenshot_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/HEAD/.github/assets/screenshot_full.png -------------------------------------------------------------------------------- /.github/assets/social_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/HEAD/.github/assets/social_telegram.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | globals: { 5 | "ts-jest": { 6 | tsconfig: "tsconfig.json", 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 120, 4 | "semi": true, 5 | "singleQuote": false, 6 | "tabWidth": 4, 7 | "trailingComma": "all", 8 | "useTabs": true 9 | } 10 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PACKAGE_VERSION=$(cat ./package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]') 3 | echo ::set-output name=pkgversion::$PACKAGE_VERSION -------------------------------------------------------------------------------- /scripts/changelog_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CHANGELOG=$(cat ./CHANGELOG_RELEASE.txt) 3 | CHANGELOG="${CHANGELOG//'%'/'%25'}" 4 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 5 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" 6 | echo ::set-output name=changelog::$CHANGELOG -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": "rs", 3 | "ignore": [".git", "node_modules/**/*"], 4 | "verbose": true, 5 | "watch": ["app/", "translations/", "configs/"], 6 | "env": { 7 | "NODE_ENV": "development" 8 | }, 9 | "ext": "ts,js,json" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "svelte.svelte-vscode", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "gruntfuggly.todo-tree", 7 | "nickdodd79.gulptasks", 8 | "pkief.material-icon-theme" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🗨 Question 3 | about: Ask a question (we recommended use 💬 discussion tab and open questions on repository forum) 4 | --- 5 | 6 | 7 | 8 | ### Question 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = false 9 | trim_trailing_whitespace = true 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.py] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.yml] 18 | indent_style = space 19 | indent_size = 4 -------------------------------------------------------------------------------- /app/tests/app.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Jest Tests 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | test("show hello world", async () => { 12 | const app = () => "hello-world"; 13 | await expect(app()).toBe("hello-world"); 14 | }); 15 | -------------------------------------------------------------------------------- /scripts/rmdist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Delete dist folder 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import * as shell from "shelljs"; 12 | declare const __dirname: string; 13 | 14 | const path = `${__dirname}/../dist`; 15 | 16 | shell.rm("-Rf", path); 17 | -------------------------------------------------------------------------------- /.pm2-process.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quizquickanswer-telegram-game-bot", 3 | "script": "dist/core/bot.js", 4 | "watch": true, 5 | "max_restarts": 9999, 6 | "max_memory_restart": "1024M", 7 | "instances": 1, 8 | "exec_mode": "fork", 9 | "error_file": "./logs/errors_pm2.log", 10 | "out_file": "./logs/debug_pm2.log", 11 | "ignore_watch": [".git", "logs", "databases", "node_modules", "npm-debug.log"], 12 | "watch_options": { 13 | "followSymlinks": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/core/token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Grammy Grammy Telegram API Framework API 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { Bot } from "grammy"; 12 | import configs from "@configs/config"; 13 | 14 | const bot = new Bot(configs.telegram.token); 15 | 16 | export { bot }; 17 | export default bot; 18 | -------------------------------------------------------------------------------- /app/routes/translations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Translations 3 | * ===================== 4 | * Switch translations 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import en from "@translations/en.json"; 13 | import it from "@translations/it.json"; 14 | 15 | const translations = { 16 | en, 17 | it, 18 | }; 19 | 20 | export { it, en }; 21 | export default translations; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/paid_support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎖 Paid support 3 | about: If you need paid support with hight priority donate correct tier on github.com/sponsors/ptkdev or patreon.com/join/ptkdev and send email to support@ptkdev.io 4 | --- 5 | 6 | ## PAID SUPPORT 7 | 8 | If you need paid support with hight priority donate correct tier on: 9 | 10 | - https://github.com/sponsors/ptkdev 11 | - https://www.patreon.com/join/ptkdev 12 | 13 | Please send me an email (support@ptkdev.io) before donation, i try provide correct price quotation for your bug or new feature. 14 | -------------------------------------------------------------------------------- /scripts/configs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check configs.js 3 | * ===================== 4 | * Check if configs/config.js exist, if don't exist rename .tpl 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import * as fs from "fs"; 12 | import * as shell from "shelljs"; 13 | 14 | declare const __dirname: string; 15 | 16 | const path = `${__dirname}/../app/configs/config.js`; 17 | 18 | if (!fs.existsSync(path)) { 19 | shell.cp("-Rf", `${__dirname}/../app/configs/config.js.tpl`, path); 20 | } 21 | -------------------------------------------------------------------------------- /app/routes/api/telegram.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Router Wrapper telegram api (message) 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | 12 | import message from "@app/functions/api/telegram/message"; 13 | import bot from "@app/functions/api/telegram/bot"; 14 | 15 | const telegram = { 16 | api: { 17 | message: message, 18 | bot: bot, 19 | }, 20 | }; 21 | 22 | export { telegram }; 23 | export default telegram; 24 | -------------------------------------------------------------------------------- /app/types/translate.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * TranslateInterface Interfaces 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | 12 | export interface TranslateParamsInterface { 13 | username?: string; 14 | master?: string; 15 | bot_username?: string; 16 | first_name?: string; 17 | master_first_name?: string; 18 | master_username?: string; 19 | emoji?: string; 20 | answer?: string; 21 | tip?: string; 22 | score?: number; 23 | } 24 | -------------------------------------------------------------------------------- /app/functions/api/telegram/bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper telegram api (botInfo) 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import type { Context } from "grammy"; 12 | 13 | const getUsername = (ctx: Context): string => { 14 | return ctx?.me?.username || ""; 15 | }; 16 | 17 | const getInfo = (ctx: Context): Context["me"] => { 18 | return ctx?.me || {}; 19 | }; 20 | 21 | export { getUsername, getInfo }; 22 | export default { getUsername, getInfo }; 23 | -------------------------------------------------------------------------------- /scripts/debug.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disable debug 3 | * ===================== 4 | * Check if configs/config.js has debug to off 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import * as fs from "fs"; 12 | import * as shell from "shelljs"; 13 | import { argv } from "yargs"; 14 | 15 | declare const __dirname: string; 16 | 17 | const path = `${__dirname}/../app/configs/config.js`; 18 | 19 | if (fs.existsSync(path)) { 20 | if (argv.enable) { 21 | shell.sed("-i", 'debug: "disabled"', 'debug: "enabled"', path); 22 | } else { 23 | shell.sed("-i", 'debug: "enabled"', 'debug: "disabled"', path); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/functions/commands/launch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Launch 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | 13 | import logger from "@app/functions/utils/logger"; 14 | 15 | /** 16 | * Run bot 17 | * ===================== 18 | * Send welcome message 19 | * 20 | */ 21 | const launch = async (): Promise => { 22 | logger.info("command: /launch", "launch.ts:launch()"); 23 | 24 | await bot.start(); 25 | }; 26 | 27 | export { launch }; 28 | export default launch; 29 | -------------------------------------------------------------------------------- /app/functions/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Logger 3 | * ===================== 4 | * The best alternative to the console.log statement 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import Logger, { LoggerOptions } from "@ptkdev/logger"; 13 | import config from "@configs/config"; 14 | 15 | const options: LoggerOptions = config.logger as unknown as LoggerOptions; // typescript fuck you https://github.com/microsoft/TypeScript/issues/26552#issuecomment-484124880 16 | const logger = new Logger(options); 17 | 18 | export { logger }; 19 | export default logger; 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Try Release 2 | on: 3 | push: 4 | branches: 5 | - nightly 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | token: ${{ secrets.GIT_TOKEN }} 13 | ref: nightly 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: "16.x" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: git config --global user.name 'Patryk Rzucidlo (@PTKDev)' 19 | - run: git config --global user.email 'support@ptkdev.io' 20 | - run: npm ci 21 | - run: npm run release 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💫 Feature request 3 | about: Suggest an idea or new feature for this project (low priority) 4 | --- 5 | 6 | 7 | 8 | ### Feature description 9 | 10 | 11 | 12 | ### Feature motivation 13 | 14 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /scripts/changelog_release.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Version 3 | * ===================== 4 | * Increment package.json version 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import * as fs from "fs"; 12 | import Logger from "@ptkdev/logger"; 13 | 14 | const logger = new Logger(); 15 | 16 | if (fs.existsSync("./CHANGELOG.md")) { 17 | fs.readFile("./CHANGELOG.md", "utf8", (error, data) => { 18 | if (error) { 19 | logger.error(JSON.stringify(error)); 20 | } 21 | 22 | const changelog = data.match(/\n([\s\S]*)-->\n/gm); 23 | changelog?.forEach((c) => { 24 | fs.writeFile("./CHANGELOG_RELEASE.txt", c, function writeJSON(error) { 25 | if (error) { 26 | logger.error(JSON.stringify(error)); 27 | } 28 | }); 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /scripts/changelog.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /** 3 | * Reset CHANGELOG 4 | * ===================== 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import * as fs from "fs"; 13 | 14 | declare const __dirname: string; 15 | 16 | const changelog = `# v1.0.0 (${new Date().toLocaleString("en-us", { 17 | month: "long", 18 | year: "numeric", 19 | day: "numeric", 20 | })}) 21 | 22 | - First release 23 | 24 | 25 | 26 | `; 27 | 28 | fs.unlinkSync(`${__dirname}/../CHANGELOG.md`); 29 | fs.writeFileSync(`${__dirname}/../CHANGELOG.md`, `${changelog}`, { 30 | encoding: "utf8", 31 | }); 32 | -------------------------------------------------------------------------------- /app/routes/api/database.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Migration script for the database. 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import connection from "@app/functions/api/database/connection"; 12 | import master from "@app/functions/api/database/master"; 13 | import questions from "@app/functions/api/database/questions"; 14 | import scores from "@app/functions/api/database/scores"; 15 | import users from "@app/functions/api/database/users"; 16 | import settings from "@app/functions/api/database/settings"; 17 | 18 | const db = { 19 | connection: connection, 20 | master: master, 21 | questions: questions, 22 | scores: scores, 23 | users: users, 24 | settings: settings, 25 | }; 26 | 27 | export { db }; 28 | export default db; 29 | -------------------------------------------------------------------------------- /app/functions/commands/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Version 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import v from "@app/configs/version.json"; 13 | import telegram from "@routes/api/telegram"; 14 | import logger from "@app/functions/utils/logger"; 15 | 16 | /** 17 | * command: /version 18 | * ===================== 19 | * Get the version of the bot 20 | * 21 | */ 22 | const version = async (): Promise => { 23 | bot.command("version", async (ctx) => { 24 | logger.info("command: /version", "version.ts:version()"); 25 | 26 | await telegram.api.message.send( 27 | ctx, 28 | telegram.api.message.getChatID(ctx), 29 | `v${v?.semver || "0.0.0"} (${v?.hash || ""})`, 30 | ); 31 | }); 32 | }; 33 | 34 | export { version }; 35 | export default version; 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amd64/ubuntu:18.10 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates --assume-yes --no-install-recommends apt-utils 8 | RUN apt-get update && apt-get install -y locales --assume-yes && rm -rf /var/lib/apt/lists/* && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 9 | ENV LANG en_US.utf8 10 | 11 | RUN apt-get update && apt-get install -y xvfb libssl-dev curl xauth --assume-yes --no-install-recommends apt-utils 12 | RUN apt-get update && apt-get install -y build-essential --assume-yes --no-install-recommends apt-utils 13 | RUN apt-get update && apt-get install -y npm --assume-yes --no-install-recommends apt-utils 14 | 15 | # Install pm2 16 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install pm2 -g 17 | 18 | # Install project dependencies 19 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install 20 | 21 | CMD ["pm2-runtime", ".pm2-process.json"] -------------------------------------------------------------------------------- /Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM amd64/ubuntu:18.10 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates --assume-yes --no-install-recommends apt-utils 8 | RUN apt-get update && apt-get install -y locales --assume-yes && rm -rf /var/lib/apt/lists/* && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 9 | ENV LANG en_US.utf8 10 | 11 | RUN apt-get update && apt-get install -y xvfb libssl-dev curl xauth --assume-yes --no-install-recommends apt-utils 12 | RUN apt-get update && apt-get install -y build-essential --assume-yes --no-install-recommends apt-utils 13 | RUN apt-get update && apt-get install -y npm --assume-yes --no-install-recommends apt-utils 14 | 15 | # Install pm2 16 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install pm2 -g 17 | 18 | # Install project dependencies 19 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install 20 | 21 | CMD ["pm2-runtime", ".pm2-process.json"] -------------------------------------------------------------------------------- /Dockerfile.i386: -------------------------------------------------------------------------------- 1 | FROM i386/ubuntu:18.10 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates --assume-yes --no-install-recommends apt-utils 8 | RUN apt-get update && apt-get install -y locales --assume-yes && rm -rf /var/lib/apt/lists/* && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 9 | ENV LANG en_US.utf8 10 | 11 | RUN apt-get update && apt-get install -y xvfb libssl-dev curl xauth --assume-yes --no-install-recommends apt-utils 12 | RUN apt-get update && apt-get install -y build-essential --assume-yes --no-install-recommends apt-utils 13 | RUN apt-get update && apt-get install -y npm --assume-yes --no-install-recommends apt-utils 14 | 15 | # Install pm2 16 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install pm2 -g 17 | 18 | # Install project dependencies 19 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install 20 | 21 | CMD ["pm2-runtime", ".pm2-process.json"] -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ES2019", 5 | "sourceMap": true, 6 | "outDir": "./dist/", 7 | "lib": ["es2017", "es7", "es6", "DOM"], 8 | "baseUrl": ".", 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "noImplicitAny": false, 13 | "noImplicitThis": true, 14 | "declaration": false, 15 | "skipLibCheck": true, 16 | "allowJs": true, 17 | "strict": true, 18 | "strictNullChecks": true, 19 | "strictFunctionTypes": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "paths": { 22 | "@databases/*": ["./database/*"], 23 | "@translations/*": ["./app/translations/*"], 24 | "@configs/*": ["./app/configs/*"], 25 | "@routes/*": ["./app/routes/*"], 26 | "@app/*": ["./app/*"] 27 | }, 28 | "typeRoots": ["app/**/types", "node_modules/@types"], 29 | "plugins": [ 30 | { "transform": "typescript-transform-paths" }, 31 | { "transform": "typescript-transform-paths", "afterDeclarations": true } 32 | ] 33 | }, 34 | "include": ["app/**/*"], 35 | "exclude": ["node_modules/*", "dist/*", "scripts/*", "examples/*"] 36 | } 37 | -------------------------------------------------------------------------------- /app/types/voters.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Voters Interfaces 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import type { UserVotersInterface } from "@app/types/uservoters.interfaces"; 12 | 13 | /** 14 | * Voters Interface 15 | * ===================== 16 | * 17 | * @interface [VotersInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/types/game.interfaces.ts) 18 | * 19 | * @param { string | number } message_id - message id 20 | * @param { string[] } voters - user id of the voters 21 | * 22 | */ 23 | export interface VotersInterface { 24 | /** 25 | * VotersInterface 26 | * ===================== 27 | * 28 | * @param { number } message_id - id of the message/question 29 | * 30 | */ 31 | message_id: number; 32 | /** 33 | * VotersInterface 34 | * ===================== 35 | * 36 | * @param { UserVotersInterface } users - user id of the voters 37 | * 38 | */ 39 | users: UserVotersInterface; 40 | } 41 | -------------------------------------------------------------------------------- /app/types/uservoters.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * UserVoters Interfaces 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | 12 | /** 13 | * UserVoters Interface 14 | * ===================== 15 | * 16 | * @interface [UserVotersInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/types/game.interfaces.ts) 17 | * 18 | * @param { string[] } upvotes - user id of the voters that had upvoted 19 | * @param { string[] } downvotes - user id of the voters that had downvoted 20 | * 21 | */ 22 | export interface UserVotersInterface { 23 | /** 24 | * UserVotersInterface 25 | * ===================== 26 | * 27 | * @param { string[] } upvotes - user id of the voters that had upvoted 28 | * 29 | */ 30 | upvotes: string[]; 31 | /** 32 | * UserVotersInterface 33 | * ===================== 34 | * 35 | * @param { string[] } downvotes - user id of the voters that had downvoted 36 | * 37 | */ 38 | downvotes: string[]; 39 | } 40 | -------------------------------------------------------------------------------- /scripts/githash.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Git Hash 3 | * ===================== 4 | * Get version and hash of commits 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import Logger from "@ptkdev/logger"; 12 | import fs from "fs"; 13 | import path from "path"; 14 | import { execSync } from "child_process"; 15 | import semver from "../package.json"; 16 | 17 | const gitdotfile = `${__dirname}/../.git/config`; 18 | const logger = new Logger(); 19 | let branch = ""; 20 | let hash = ""; 21 | 22 | const execSyncWrapper = (command: string) => { 23 | let stdout = ""; 24 | try { 25 | stdout = execSync(command).toString().trim(); 26 | } catch (error) { 27 | logger.error(JSON.stringify(error)); 28 | } 29 | return stdout; 30 | }; 31 | 32 | if (fs.existsSync(gitdotfile)) { 33 | branch = execSyncWrapper("git rev-parse --abbrev-ref HEAD"); 34 | hash = execSyncWrapper("git rev-parse --short=7 HEAD"); 35 | } 36 | 37 | const obj = { 38 | semver: semver.version.split("-")[0], 39 | branch, 40 | hash, 41 | }; 42 | 43 | fs.writeFileSync(path.resolve("app/configs", "version.json"), JSON.stringify(obj, null, "\t")); 44 | -------------------------------------------------------------------------------- /app/functions/commands/top10.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Top10 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import telegram from "@routes/api/telegram"; 14 | 15 | import logger from "@app/functions/utils/logger"; 16 | 17 | const top10 = async (): Promise => { 18 | bot.command("top10", async (ctx) => { 19 | logger.info("command: /top10", "top10.ts:top10()"); 20 | const lang = await telegram.api.message.getLanguage(ctx); 21 | 22 | if (telegram.api.message.getChatID(ctx) < 0) { 23 | // is group chat 24 | 25 | await telegram.api.message.send( 26 | ctx, 27 | telegram.api.message.getChatID(ctx), 28 | translate(lang.language, "top10_commands"), 29 | ); 30 | } else { 31 | await telegram.api.message.send( 32 | ctx, 33 | telegram.api.message.getChatID(ctx), 34 | translate(lang.language, "command_only_group"), 35 | ); 36 | } 37 | }); 38 | }; 39 | 40 | export { top10 }; 41 | export default top10; 42 | -------------------------------------------------------------------------------- /app/configs/config.js.tpl: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | config(); 4 | 5 | export default { 6 | telegram: { 7 | // from @botfather on telegram 8 | token: process.env.BOT_TOKEN || "1234:asdfghjkl", 9 | }, 10 | 11 | mode: "poll", // or webhook 12 | webhook: { 13 | url: process.env.BOT_WEBHOOK_URL || "https://sample.host.com:8443", 14 | port: process.env.BOT_WEBHOOK_PORT || 8443, 15 | certs_path: process.env.BOT_WEBHOOK_CERTS_PATH || "certs", 16 | self_signed: process.env.BOT_WEBHOOK_SELF_SIGNED || true, 17 | }, 18 | 19 | // mongodb 20 | database: { URL: process.env.MONGODB || "mongodb://localhost:27017/quizquickanswerdb" }, 21 | 22 | // Debug 23 | debug: process.env.DEBUG || "disabled", 24 | 25 | // LOGS 26 | // https://github.com/ptkdev/ptkdev-logger 27 | logger: { 28 | path: { 29 | debug_log: "./logs/debug.log", 30 | error_log: "./logs/errors.log", 31 | }, 32 | language: "en", 33 | colors: true, 34 | debug: process.env.LOGGER || "enabled", 35 | info: process.env.LOGGER || "enabled", 36 | warning: process.env.LOGGER || "enabled", 37 | error: process.env.LOGGER || "enabled", 38 | sponsor: process.env.LOGGER || "enabled", 39 | write: process.env.LOGGER_WRITE || false, 40 | type: "log", 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /app/types/settings.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Settings Interfaces 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | 12 | /** 13 | * SettingsInterface 14 | * ===================== 15 | * 16 | * @interface [SettingsInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/types/game.interfaces.ts) 17 | * 18 | * @param { string } language - language 19 | * @param { boolean } pin_message - pin message to the chat 20 | * @param { number } group_id - group id fron user playing 21 | * 22 | */ 23 | export interface SettingsInterface { 24 | /** 25 | * SettingsInterface 26 | * ===================== 27 | * 28 | * @param { string } language - language 29 | * 30 | */ 31 | language: string; 32 | /** 33 | * SettingsInterface 34 | * ===================== 35 | * 36 | * @param { string } pin_message - pin message to the chat 37 | * 38 | */ 39 | pin_message: boolean; 40 | /** 41 | * SettingsInterface 42 | * ===================== 43 | * 44 | * @param { number } group_id - group id fron user playing 45 | * 46 | */ 47 | group_id: number; 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Release Nightly 2 | on: 3 | schedule: 4 | - cron: "30 23 * * *" 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | if: "!startsWith(github.event.head_commit.message, '[🚀 Release Nightly]')" 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | token: ${{ secrets.GIT_TOKEN }} 13 | ref: nightly 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: "16.x" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: git config --global user.name 'Patryk Rzucidlo (@PTKDev)' 19 | - run: git config --global user.email 'support@ptkdev.io' 20 | - run: npm ci 21 | - run: npm run github-workflow-next-version -- --cmd nightly 22 | - run: npm run pkg-upgrade 23 | - run: npm run release 24 | - run: npm run pre-commit 25 | - id: pkgjson 26 | run: chmod +x ./scripts/version.sh && ./scripts/version.sh 27 | - run: git add . && git commit -m "[🚀 Release Nightly] v${{ steps.pkgjson.outputs.pkgversion }}" && git push 28 | - run: npm publish --tag nightly 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /app/functions/commands/actions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Actions 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import telegram from "@routes/api/telegram"; 13 | import { sendMessageToAllGroups } from "@app/functions/utils/admin"; 14 | import translate from "@translations/translate"; 15 | import logger from "@app/functions/utils/logger"; 16 | 17 | const actions = async (): Promise => { 18 | bot.callbackQuery(["message_all_groups", "set_user_score"], async (ctx) => { 19 | logger.info(`action: ${telegram.api.message.getActionType(ctx)}`, "actions.ts:actions()"); 20 | const lang = await telegram.api.message.getLanguage(ctx); 21 | 22 | switch (telegram.api.message.getActionType(ctx)) { 23 | case "message_all_groups": 24 | await sendMessageToAllGroups(); 25 | break; 26 | case "set_user_score": 27 | await telegram.api.message.send( 28 | ctx, 29 | telegram.api.message.getChatID(ctx), 30 | translate(lang.language, "admin_set_user_score_info_request"), 31 | ); 32 | break; 33 | default: 34 | break; 35 | } 36 | }); 37 | }; 38 | 39 | export { actions }; 40 | export default actions; 41 | -------------------------------------------------------------------------------- /app/types/game.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GameInterface 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import type { MasterInterface } from "@app/types/master.interfaces"; 12 | 13 | /** 14 | * GameInterface 15 | * ===================== 16 | * 17 | * @interface [GameInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/types/game.interfaces.ts) 18 | * 19 | * @param { string } question - game question 20 | * @param { string } description - game tip 21 | * @param { number } group_id - group id fron user playing 22 | * 23 | */ 24 | export interface GameInterface extends MasterInterface { 25 | /** 26 | * GameInterface from MasterInterface 27 | * ===================== 28 | * 29 | * @param { string } question - game question 30 | * 31 | */ 32 | question?: string; 33 | /** 34 | * GameInterface from MasterInterface 35 | * ===================== 36 | * 37 | * @param { string } description - game tip 38 | * 39 | */ 40 | description?: string; 41 | /** 42 | * GameInterface from MasterInterface 43 | * ===================== 44 | * 45 | * @param { number } group_id - group id fron user playing 46 | * 47 | */ 48 | group_id: number; 49 | } 50 | -------------------------------------------------------------------------------- /app/functions/commands/groups.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Groups 3 | * ===================== 4 | * Spam official groups 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import { InlineKeyboard } from "grammy"; 13 | import bot from "@app/core/token"; 14 | import translate from "@translations/translate"; 15 | import telegram from "@routes/api/telegram"; 16 | import logger from "@app/functions/utils/logger"; 17 | 18 | const groups = async (): Promise => { 19 | bot.command("groups", async (ctx) => { 20 | logger.info("command: /groups", "groups.ts:groups()"); 21 | const lang = await telegram.api.message.getLanguage(ctx); 22 | 23 | const buttons = new InlineKeyboard(); 24 | 25 | buttons.url(translate(lang.language, "groups_button_official_english"), "https://t.me/QuizQuickAnswerGroup"); 26 | buttons.row(); 27 | buttons.url(translate(lang.language, "groups_button_official_italian"), "https://t.me/QuizQuickAnswerGroupITA"); 28 | 29 | await telegram.api.message.send( 30 | ctx, 31 | telegram.api.message.getChatID(ctx), 32 | translate(lang.language, "groups_command"), 33 | { 34 | reply_markup: buttons, 35 | parse_mode: "HTML", 36 | }, 37 | ); 38 | }); 39 | }; 40 | 41 | export { groups }; 42 | export default groups; 43 | -------------------------------------------------------------------------------- /scripts/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Version 3 | * ===================== 4 | * Increment package.json version 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import * as fs from "fs"; 12 | import Logger from "@ptkdev/logger"; 13 | import { argv } from "yargs"; 14 | import pkg from "../package.json"; 15 | 16 | const logger = new Logger(); 17 | 18 | const version = pkg.version.split("."); 19 | let next_version, patch; 20 | 21 | switch (argv.cmd) { 22 | case "nightly": 23 | next_version = `${version[0]}.${version[1]}.${version[2]}.${Number(version[3]) + 1}`; 24 | break; 25 | 26 | case "nightly-next": 27 | patch = version[2].split("-"); 28 | next_version = `${version[0]}.${version[1]}.${Number(patch[0]) + 1}-nightly.0`; 29 | break; 30 | 31 | case "beta": 32 | patch = version[2].split("-"); 33 | next_version = `${version[0]}.${version[1]}.${Number(patch[0])}-beta.1`; 34 | break; 35 | 36 | case "main": 37 | patch = version[2].split("-"); 38 | next_version = `${version[0]}.${version[1]}.${Number(patch[0])}`; 39 | break; 40 | 41 | default: 42 | next_version = "0.0.0"; 43 | break; 44 | } 45 | 46 | pkg.version = next_version; 47 | 48 | if (fs.existsSync("./package.json")) { 49 | fs.writeFile("./package.json", JSON.stringify(pkg), function writeJSON(error) { 50 | if (error) { 51 | logger.error(JSON.stringify(error)); 52 | } 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "ptkdev/quizquickanswer-telegram-game-bot", 3 | "projectOwner": "ptkdev", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "contributors": [ 12 | { 13 | "login": "ptkdev", 14 | "name": "Patryk Rzucidło", 15 | "avatar_url": "https://avatars1.githubusercontent.com/u/442844?v=4", 16 | "profile": "https://ptk.dev", 17 | "contributions": [ 18 | "code", 19 | "translation", 20 | "doc", 21 | "bug" 22 | ] 23 | }, 24 | { 25 | "login": "AliShadman95", 26 | "name": "Alì Shadman", 27 | "avatar_url": "https://avatars1.githubusercontent.com/u/50172746?v=4", 28 | "profile": "", 29 | "contributions": [ 30 | "code", 31 | "translation", 32 | "doc", 33 | "bug" 34 | ] 35 | }, 36 | { 37 | "login": "DeniseKarina", 38 | "name": "Denise Scazzari", 39 | "avatar_url": "https://avatars1.githubusercontent.com/u/91738047?v=4", 40 | "profile": "https://instagram.com/den_karina", 41 | "contributions": [ 42 | "translation" 43 | ] 44 | }, 45 | { 46 | "login": "ImAl3x03", 47 | "name": "Alessandro Di Maria", 48 | "avatar_url": "", 49 | "profile": "https://instagram.com/i.m_al3x", 50 | "contributions": [ 51 | "code", 52 | "bug", 53 | "translation" 54 | ] 55 | } 56 | ], 57 | "contributorsPerLine": 6, 58 | "commitConvention": "none" 59 | } 60 | -------------------------------------------------------------------------------- /app/routes/commands.ts: -------------------------------------------------------------------------------- 1 | import start from "@app/functions/commands/start"; 2 | import master from "@app/functions/commands/master"; 3 | import score from "@app/functions/commands/score"; 4 | import groups from "@app/functions/commands/groups"; 5 | import ping from "@app/functions/commands/ping"; 6 | import show from "@app/functions/commands/show"; 7 | import top10 from "@app/functions/commands/top10"; 8 | import topDaily from "@app/functions/commands/top_daily"; 9 | import topMonthly from "@app/functions/commands/top_monthly"; 10 | import topYearly from "@app/functions/commands/top_yearly"; 11 | import launch from "@app/functions/commands/launch"; 12 | import hears from "@app/functions/commands/hears"; 13 | import hearsPhoto from "@app/functions/commands/hearsphoto"; 14 | import settings from "@app/functions/commands/settings"; 15 | import actions from "@app/functions/commands/actions"; 16 | import version from "@app/functions/commands/version"; 17 | 18 | const commands = { 19 | start, 20 | master, 21 | score, 22 | groups, 23 | ping, 24 | show, 25 | top10, 26 | topDaily, 27 | topMonthly, 28 | topYearly, 29 | launch, 30 | hears, 31 | settings, 32 | hearsPhoto, 33 | actions, 34 | version, 35 | }; 36 | 37 | export { 38 | start, 39 | master, 40 | score, 41 | groups, 42 | ping, 43 | show, 44 | top10, 45 | topDaily, 46 | topMonthly, 47 | topYearly, 48 | launch, 49 | hears, 50 | settings, 51 | hearsPhoto, 52 | actions, 53 | version, 54 | }; 55 | export default commands; 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a report to help us improve (low priority) 4 | --- 5 | 6 | 7 | 8 | ### Versions 9 | 10 | 11 | 12 | - **App Version:** v1.0.0 13 | - **Node Version:** v13.0.0 14 | - **Operating System:** Windows 10 15 | - **Browser:** Google Chrome v80 16 | 17 | ### Expected Behavior 18 | 19 | 20 | 21 | ### Actual Behavior 22 | 23 | 25 | 26 | ### Steps to Reproduce 27 | 28 | 30 | 31 | 1. ... 32 | 2. ... 33 | 3. ... 34 | 35 | ### Screenshots (Optional) 36 | 37 | 38 | 39 | 47 | -------------------------------------------------------------------------------- /app/functions/utils/admin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * admin 3 | * ===================== 4 | * Admin 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import db from "@routes/api/database"; 13 | import logger from "./logger"; 14 | 15 | import type { MasterInterface } from "@app/types/master.interfaces"; 16 | 17 | /** 18 | * Send message to all groups 19 | * ===================== 20 | * Send a message to all groups 21 | * 22 | * @param {number} index - index refered to the top 10 position 23 | * @param {any} ctx - Telegram context 24 | */ 25 | const sendMessageToAllGroups = async (): Promise => { 26 | const masters: MasterInterface[] = await db.master.getMultiple({}); 27 | const groups = masters 28 | .map((m) => m.group_id) 29 | .filter((elem, index, self) => { 30 | return index === self.indexOf(elem); 31 | }); 32 | logger.info(groups.join(" ")); 33 | }; 34 | 35 | /** 36 | * Set specific user score 37 | * ===================== 38 | * Set a specific score to an user in a specific group 39 | * 40 | * @param {any} ctx - Telegram context 41 | * @param {string} score - score to set 42 | * @param {string} group_id - group id where the score needs to be updated 43 | */ 44 | const setUserScore = async (): Promise => { 45 | logger.info("setUserScore function"); 46 | }; 47 | 48 | export { sendMessageToAllGroups, setUserScore }; 49 | export default { 50 | sendMessageToAllGroups, 51 | setUserScore, 52 | }; 53 | -------------------------------------------------------------------------------- /Dockerfile.armv7: -------------------------------------------------------------------------------- 1 | FROM arm32v7/ubuntu:18.10 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | # For cross compile on dockerhub 8 | ENV QEMU_EXECVE 1 9 | COPY docker/armv7/qemu-arm-static /usr/bin/ 10 | COPY docker/armv7/resin-xbuild /usr/bin/ 11 | RUN [ "/usr/bin/qemu-arm-static", "/bin/sh", "-c", "ln -s resin-xbuild /usr/bin/cross-build-start; ln -s resin-xbuild /usr/bin/cross-build-end; ln /bin/sh /bin/sh.real" ] 12 | RUN [ "cross-build-start" ] 13 | 14 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates --assume-yes --no-install-recommends apt-utils 15 | RUN apt-get update && apt-get install -y locales --assume-yes && rm -rf /var/lib/apt/lists/* && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 16 | ENV LANG en_US.utf8 17 | 18 | RUN apt-get update && apt-get install -y xvfb libssl-dev curl xauth --assume-yes --no-install-recommends apt-utils 19 | RUN apt-get update && apt-get install -y build-essential --assume-yes --no-install-recommends apt-utils 20 | RUN apt-get update && apt-get install -y npm --assume-yes --no-install-recommends apt-utils 21 | 22 | # Install pm2 23 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install pm2 -g 24 | 25 | # Install project dependencies 26 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install 27 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install puppeteer@1.9.0 28 | 29 | # For cross compile on dockerhub 30 | RUN [ "cross-build-end" ] 31 | 32 | CMD ["pm2-runtime", ".pm2-process.json"] -------------------------------------------------------------------------------- /Dockerfile.armv8: -------------------------------------------------------------------------------- 1 | FROM arm64v8/ubuntu:18.10 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | # For cross compile on dockerhub 8 | ENV QEMU_EXECVE 1 9 | COPY docker/armv8/qemu-aarch64-static /usr/bin/ 10 | COPY docker/armv8/resin-xbuild /usr/bin/ 11 | RUN [ "/usr/bin/qemu-aarch64-static", "/bin/sh", "-c", "ln -s resin-xbuild /usr/bin/cross-build-start; ln -s resin-xbuild /usr/bin/cross-build-end; ln /bin/sh /bin/sh.real" ] 12 | RUN [ "cross-build-start" ] 13 | 14 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates --assume-yes --no-install-recommends apt-utils 15 | RUN apt-get update && apt-get install -y locales --assume-yes && rm -rf /var/lib/apt/lists/* && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 16 | ENV LANG en_US.utf8 17 | 18 | RUN apt-get update && apt-get install -y xvfb libssl-dev curl xauth --assume-yes --no-install-recommends apt-utils 19 | RUN apt-get update && apt-get install -y build-essential --assume-yes --no-install-recommends apt-utils 20 | RUN apt-get update && apt-get install -y npm --assume-yes --no-install-recommends apt-utils 21 | 22 | # Install pm2 23 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install pm2 -g 24 | 25 | # Install project dependencies 26 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install 27 | RUN npm config set unsafe-perm true && npm config set registry http://registry.npmjs.org/ && npm install puppeteer@1.9.0 28 | 29 | # For cross compile on dockerhub 30 | RUN [ "cross-build-end" ] 31 | 32 | CMD ["pm2-runtime", ".pm2-process.json"] -------------------------------------------------------------------------------- /app/functions/commands/ping.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ping 3 | * ===================== 4 | * Ping send notify to all users for start game 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import bot from "@app/core/token"; 13 | import translate from "@translations/translate"; 14 | import telegram from "@routes/api/telegram"; 15 | import logger from "@app/functions/utils/logger"; 16 | 17 | const ping = async (): Promise => { 18 | bot.command("ping", async (ctx) => { 19 | logger.info("command: /ping", "ping.ts:ping()"); 20 | const lang = await telegram.api.message.getLanguage(ctx); 21 | 22 | if (telegram.api.message.getChatID(ctx) < 0) { 23 | // is group chat 24 | const message = await telegram.api.message.send( 25 | ctx, 26 | telegram.api.message.getChatID(ctx), 27 | translate(lang.language, "ping_command"), 28 | ); 29 | 30 | if (message) { 31 | await telegram.api.message.pin(ctx, telegram.api.message.getChatID(ctx), message?.message_id, { 32 | disable_notification: false, 33 | }); 34 | 35 | setTimeout(async function () { 36 | await telegram.api.message.unpin(ctx, telegram.api.message.getChatID(ctx), message?.message_id); 37 | }, 3000); 38 | } 39 | } else { 40 | await telegram.api.message.send( 41 | ctx, 42 | telegram.api.message.getChatID(ctx), 43 | translate(lang.language, "command_only_group"), 44 | ); 45 | } 46 | }); 47 | }; 48 | 49 | export { ping }; 50 | export default ping; 51 | -------------------------------------------------------------------------------- /.github/workflows/beta.yml: -------------------------------------------------------------------------------- 1 | name: Release Beta 2 | on: 3 | push: 4 | branches: 5 | - beta 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: "!startsWith(github.event.head_commit.message, '[🚀 Release Beta]')" 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | token: ${{ secrets.GIT_TOKEN }} 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: "16.x" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: git config --global user.name 'Patryk Rzucidlo (@PTKDev)' 19 | - run: git config --global user.email 'support@ptkdev.io' 20 | - run: npm ci 21 | - run: npm run github-workflow-next-version -- --cmd beta 22 | - run: npm run release 23 | - run: npm run pre-commit 24 | - id: pkgjson 25 | run: chmod +x ./scripts/version.sh && ./scripts/version.sh 26 | - run: git add . && git commit -m "[🚀 Release Beta] v${{ steps.pkgjson.outputs.pkgversion }}" && git push 27 | - run: npm publish --tag beta 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | - run: git fetch --all && git checkout nightly 31 | - run: npm run github-workflow-next-version -- --cmd nightly-next 32 | - run: npm run pre-commit 33 | - id: nextnightly 34 | run: chmod +x ./scripts/version.sh && ./scripts/version.sh 35 | - run: git add . && git commit -m "[🚀 Release Nightly] v${{ steps.nextnightly.outputs.pkgversion }}" && git push 36 | -------------------------------------------------------------------------------- /app/functions/commands/start.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Start 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import db from "@routes/api/database"; 14 | import telegram from "@routes/api/telegram"; 15 | import logger from "@app/functions/utils/logger"; 16 | 17 | import type { MasterInterface } from "@app/types/master.interfaces"; 18 | 19 | /** 20 | * command: /start 21 | * ===================== 22 | * Send welcome message 23 | * 24 | */ 25 | const start = async (): Promise => { 26 | bot.command("start", async (ctx) => { 27 | logger.info("command: /start", "start.ts:start()"); 28 | const lang = await telegram.api.message.getLanguage(ctx); 29 | const users: MasterInterface = await db.users.get({ 30 | id: telegram.api.message.getUserID(ctx), 31 | }); 32 | 33 | if (users.id.toString() !== "0") { 34 | await db.users.update({ id: users.id }, telegram.api.message.getFullUser(ctx)); 35 | } else { 36 | await db.users.add(telegram.api.message.getFullUser(ctx)); 37 | } 38 | 39 | if (telegram.api.message.getChatID(ctx) < 0) { 40 | // is group chat 41 | await telegram.api.message.send( 42 | ctx, 43 | telegram.api.message.getChatID(ctx), 44 | translate(lang.language, "start_command_group", { 45 | username: telegram.api.message.getUsername(ctx), 46 | }), 47 | ); 48 | } else { 49 | await telegram.api.message.send( 50 | ctx, 51 | telegram.api.message.getChatID(ctx), 52 | translate(lang.language, "start_command_private"), 53 | ); 54 | } 55 | }); 56 | }; 57 | 58 | export { start }; 59 | export default start; 60 | -------------------------------------------------------------------------------- /app/functions/api/database/connection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Connect to mongo 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import configs from "@configs/config"; 12 | import Mongoose from "mongoose"; 13 | import { logger } from "@app/functions/utils/logger"; 14 | 15 | let database: Mongoose.Connection; 16 | 17 | /** 18 | * MongoDB Connection 19 | * ===================== 20 | * Connects to mongo DB 21 | * 22 | */ 23 | const connectDB = async (): Promise => { 24 | if (database) { 25 | logger.info("trying to connect but have already a connection"); 26 | return; 27 | } 28 | try { 29 | await Mongoose.connect(configs.database.URL, { 30 | useNewUrlParser: true, 31 | useFindAndModify: false, 32 | useUnifiedTopology: true, 33 | useCreateIndex: true, 34 | maxPoolSize: 100, 35 | keepAlive: true, 36 | keepAliveInitialDelay: 3600, 37 | }); 38 | database = Mongoose.connection; 39 | logger.info("Connected to database", "connections.ts:connectDB()"); 40 | } catch (error: unknown) { 41 | logger.error(JSON.stringify(error || ""), "connections.ts:connectDB()"); 42 | } 43 | }; 44 | 45 | /** 46 | * MongoDB Disconnection 47 | * ===================== 48 | * Disconnect to mongo DB 49 | * 50 | */ 51 | const disconnectDB = async (): Promise => { 52 | if (!database) { 53 | logger.info("tried to disconnect but dont have connections"); 54 | return; 55 | } 56 | try { 57 | await Mongoose.disconnect(() => { 58 | logger.info("Disconnected from database", "connections.ts:disconnectDB()"); 59 | process.exit(0); 60 | }); 61 | } catch (error: unknown) { 62 | logger.error(JSON.stringify(error || ""), "connections.ts:disconnectDB()"); 63 | } 64 | }; 65 | 66 | export { connectDB, disconnectDB }; 67 | export default { connectDB, disconnectDB }; 68 | -------------------------------------------------------------------------------- /app/functions/commands/show.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Show 3 | * ===================== 4 | * Show current quiz and tip 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * Alessandro Di Maria [@ImAl3x03] (https://github.com/ImAl3x03) 9 | * 10 | * @license: MIT License 11 | * 12 | */ 13 | import bot from "@app/core/token"; 14 | import translate from "@translations/translate"; 15 | import telegram from "@routes/api/telegram"; 16 | import logger from "@app/functions/utils/logger"; 17 | import db from "@routes/api/database"; 18 | 19 | import type { MasterInterface } from "@app/types/master.interfaces"; 20 | 21 | const show = async (): Promise => { 22 | bot.command("show", async (ctx) => { 23 | logger.info("command: /show", "show.ts:show()"); 24 | const lang = await telegram.api.message.getLanguage(ctx); 25 | 26 | if (telegram.api.message.getChatID(ctx) < 0) { 27 | // is group chat 28 | 29 | const master: MasterInterface = await db.master.get({ 30 | group_id: telegram.api.message.getChatID(ctx), 31 | }); 32 | 33 | if (master.description === "") { 34 | await telegram.api.message.send( 35 | ctx, 36 | telegram.api.message.getChatID(ctx), 37 | translate(lang.language, "show_command_noquiz", { 38 | first_name: master?.first_name, 39 | username: master?.username, 40 | }), 41 | ); 42 | } else { 43 | await telegram.api.message.send( 44 | ctx, 45 | telegram.api.message.getChatID(ctx), 46 | translate(lang.language, "show_command", { 47 | first_name: master?.first_name, 48 | username: master?.username, 49 | answer: master?.description, 50 | }), 51 | ); 52 | } 53 | } else { 54 | await telegram.api.message.send( 55 | ctx, 56 | telegram.api.message.getChatID(ctx), 57 | translate(lang.language, "command_only_group"), 58 | ); 59 | } 60 | }); 61 | }; 62 | 63 | export { show }; 64 | export default show; 65 | -------------------------------------------------------------------------------- /app/core/bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Start bot 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import db from "@routes/api/database"; 12 | import commands from "@app/routes/commands"; 13 | import logger from "@app/functions/utils/logger"; 14 | 15 | (async () => { 16 | logger.info("Bot is starting...", "bot.ts:main()"); 17 | 18 | await db.connection.connectDB(); 19 | 20 | await commands.start(); 21 | await commands.actions(); 22 | await commands.master(); 23 | await commands.version(); 24 | await commands.top10(); 25 | await commands.topYearly(); 26 | // await commands.topDaily(); 27 | await commands.topMonthly(); 28 | await commands.score(); 29 | await commands.groups(); 30 | await commands.ping(); 31 | await commands.show(); 32 | await commands.settings(); 33 | await commands.hears(); 34 | await commands.hearsPhoto(); 35 | 36 | await commands.launch(); 37 | })(); 38 | 39 | process.on("SIGINT", async function () { 40 | // on CTRL-C 41 | await db.connection.disconnectDB(); 42 | logger.info("Process will restart now.", "bot.ts:SIGINT"); 43 | }); 44 | 45 | process.once("SIGUSR2", async function () { 46 | // On nodemon refresh 47 | await db.connection.disconnectDB(); 48 | logger.info("Process will restart now.", "bot.ts:SIGUSR2"); 49 | }); 50 | 51 | process.on("uncaughtException", function (error) { 52 | logger.error( 53 | `An error uncaughtException has occured. error is: ${JSON.stringify(error)}`, 54 | "bot.ts::uncaughtException", 55 | ); 56 | logger.error("Process will restart now.", "bot.ts:uncaughtException"); 57 | process.exit(1); 58 | }); 59 | 60 | process.on("unhandledRejection", function (error) { 61 | logger.error( 62 | `An error unhandledRejection has occured. error is: ${JSON.stringify(error)}`, 63 | "bot.ts::unhandledRejection", 64 | ); 65 | logger.error("Process will restart now.", "bot.ts:unhandledRejection"); 66 | process.exit(1); 67 | }); 68 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "afterDelay", 3 | "files.autoSaveDelay": 30000, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "eslint.format.enable": true, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[scss]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[css]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | "[html]": { 21 | "editor.defaultFormatter": "esbenp.prettier-vscode" 22 | }, 23 | "[jsonc]": { 24 | "editor.defaultFormatter": "esbenp.prettier-vscode" 25 | }, 26 | "[json]": { 27 | "editor.defaultFormatter": "esbenp.prettier-vscode" 28 | }, 29 | "[svelte]": { 30 | "editor.defaultFormatter": "svelte.svelte-vscode" 31 | }, 32 | "editor.formatOnSave": true, 33 | "editor.formatOnPaste": true, 34 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "svelte"], 35 | "eslint.options": { 36 | "extensinons": [".ts", ".js", ".tsx", ".jsx", ".svelte"] 37 | }, 38 | "todo-tree.highlights.customHighlight": { 39 | "TODO": { 40 | "foreground": "black", 41 | "background": "#82AAFF", 42 | "icon": "tag", 43 | "iconColour": "#82AAFF", 44 | "gutterIcon": true 45 | }, 46 | "BUG": { 47 | "foreground": "white", 48 | "background": "#AC4142", 49 | "icon": "bug", 50 | "iconColour": "#AC4142", 51 | "gutterIcon": true 52 | }, 53 | "FIXME": { 54 | "foreground": "black", 55 | "background": "#FFCC00", 56 | "iconColour": "#FFCC00", 57 | "icon": "alert", 58 | "gutterIcon": true 59 | } 60 | }, 61 | "todo-tree.highlights.defaultHighlight": { 62 | "type": "text-and-comment" 63 | }, 64 | "material-icon-theme.folders.associations": { 65 | "wordpress": "wordpress", 66 | "modules": "App", 67 | "desktop": "Container", 68 | "dist-desktop": "Dist", 69 | "dist-client": "Dist", 70 | "dist-mobile": "Dist", 71 | "webcomponent": "Middleware", 72 | "webcomponents": "Middleware", 73 | "interfaces": "Include", 74 | "interface": "Include", 75 | "logger": "Log", 76 | "mode": "Class", 77 | "types": "Typescript", 78 | "ISSUE_TEMPLATE": "Template" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Release Stable 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: "!startsWith(github.event.head_commit.message, '[🚀 Release]')" 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | token: ${{ secrets.GIT_TOKEN }} 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: "16.x" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: git config --global user.name 'Patryk Rzucidlo (@PTKDev)' 19 | - run: git config --global user.email 'support@ptkdev.io' 20 | - run: npm ci 21 | - run: npm run github-workflow-next-version -- --cmd main 22 | - run: npm run release 23 | - run: npm run pre-commit 24 | - id: pkgjson 25 | run: chmod +x ./scripts/version.sh && ./scripts/version.sh 26 | - run: git add . && git commit -m "[🚀 Release] v${{ steps.pkgjson.outputs.pkgversion }}" && git push 27 | - run: npm publish --tag latest 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | - name: Get current date 31 | id: date 32 | run: echo "::set-output name=date::$(date +'%B %d, %Y')" 33 | - run: npm run github-workflow-changelog 34 | - id: changelog 35 | run: chmod +x ./scripts/changelog_release.sh && ./scripts/changelog_release.sh 36 | - name: Release snapshot 37 | id: release-snapshot 38 | uses: ncipollo/release-action@v1 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | tag_name: ${{ steps.pkgjson.outputs.pkgversion }} 43 | release_name: v${{ steps.pkgjson.outputs.pkgversion }} 44 | tag: ${{ steps.pkgjson.outputs.pkgversion }} 45 | name: v${{ steps.pkgjson.outputs.pkgversion }} 46 | body: | 47 | ### CHANGELOG: v${{ steps.pkgjson.outputs.pkgversion }} (${{ steps.date.outputs.date }})${{ steps.changelog.outputs.changelog }} 48 | draft: false 49 | prerelease: false 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################################################################################### 2 | # License: MIT - http://opensource.org/licenses/MIT # 3 | # Author: Patryk Rzucidło (@ptkdev) https://ptk.dev # 4 | # Original: octocat - https://github.com/octocat # 5 | # Latest version: https://github.com/ptkdev/dotfiles # 6 | ###################################################################################### 7 | 8 | # Compiled source # 9 | ################### 10 | *.com 11 | *.class 12 | *.dll 13 | *.exe 14 | *.o 15 | *.so 16 | 17 | # Packages # 18 | ############ 19 | *.7z 20 | *.dmg 21 | *.gz 22 | *.iso 23 | *.jar 24 | *.rar 25 | *.tar 26 | *.zip 27 | 28 | # Eclipse # 29 | ########### 30 | .classpath 31 | .project 32 | .settings 33 | .idea 34 | .idea/ 35 | .metadata 36 | *.iml 37 | *.ipr 38 | proguard/ 39 | 40 | # Logs and databases # 41 | ###################### 42 | logs/*.log 43 | logs/screenshots/*.png 44 | logs/screenshots/*.jpg 45 | examples/*.log 46 | examples/*.json 47 | databases/*.db 48 | databases/*.sql 49 | databases/*.json 50 | *.log 51 | *.sql 52 | *.sqlite 53 | *.lock 54 | yarn-debug.log 55 | yarn-error.log 56 | 57 | # OS generated files # 58 | ###################### 59 | .DS_Store 60 | .DS_Store? 61 | ._* 62 | .Spotlight-V100 63 | .Trashes 64 | ehthumbs.db 65 | Thumbs.db 66 | 67 | # Python # 68 | ########## 69 | *.pyc 70 | *.pyo 71 | 72 | # KDE/Linux generated files # 73 | ############################# 74 | .directory 75 | 76 | # Backup generated files # 77 | ########################## 78 | *~ 79 | *.swp 80 | *.bak 81 | *# 82 | 83 | # Android # 84 | ########### 85 | *.apk 86 | *.ap_ 87 | *.dex 88 | bin/ 89 | gen/ 90 | .gradle/ 91 | build/ 92 | local.properties 93 | 94 | # Vagrant # 95 | ########### 96 | /.vagrant 97 | 98 | # PHP # 99 | ####### 100 | ./config.php 101 | phpunit.xml 102 | /vendor 103 | composer.phar 104 | /bower_components 105 | 106 | # Runtime data # 107 | ################ 108 | pids 109 | *.pid 110 | *.seed 111 | *.pid.lock 112 | *.eslintcache 113 | 114 | # NodeJS # 115 | ########## 116 | npm-debug.log 117 | /node_modules 118 | 119 | # Husky # 120 | /.husky/_/ 121 | 122 | # App # 123 | ####### 124 | .env 125 | /app/configs/config.js 126 | /app/configs/config.ts 127 | /app/configs/config.json 128 | /app/configs/version.json 129 | /dist 130 | /build 131 | -------------------------------------------------------------------------------- /app/translations/translate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Translate 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import translations from "@app/routes/translations"; 12 | import type { TranslateParamsInterface } from "@app/types/translate.interfaces"; 13 | 14 | /** 15 | * Replace Params 16 | * ===================== 17 | * If translation text is: hi {name} how are you? 18 | * This function replace {name} token with correct value 19 | * 20 | * @param {string} text - text of current phrase to translate (mandatory) 21 | * @param {Object} language_params - object with token to replace, example: {name:"alex"} (mandatory) 22 | * 23 | * @return {string} text - text with replaced token 24 | * 25 | */ 26 | const replaceParams = (text: string, language_params: TranslateParamsInterface): string => { 27 | for (const [key, value] of Object.entries(language_params)) { 28 | text = text.replace(`{{${key}}}`, value); 29 | } 30 | 31 | return text; 32 | }; 33 | 34 | /** 35 | * Check 36 | * ===================== 37 | * Check if exist translation in selected language, if not exist get string of phrase from "en" translation 38 | * 39 | * @param {string} lang - language from group/user (mandatory) 40 | * @param {string} language_id - key of translation phrase from /translations/*.json (mandatory) 41 | * 42 | * @return {string} text - text of available translation 43 | * 44 | */ 45 | const check = (lang: string, language_id: string): string => { 46 | const t: any = translations; 47 | return ( 48 | t?.[lang]?.[language_id] ?? 49 | t?.["en"]?.[language_id] ?? 50 | `translation id: ${language_id} in ${lang}.json is undefined` 51 | ); 52 | }; 53 | 54 | /** 55 | * Translate 56 | * ===================== 57 | * Get correct translation 58 | * 59 | * @param {string} lang - language from group/user (mandatory) 60 | * @param {string} language_id - key of translation phrase from /translations/*.json (mandatory) 61 | * @param {Object} language_params - object with token to replace, example: {name:"alex"} (optional) 62 | * 63 | * @return {string} text - text of available translation 64 | * 65 | */ 66 | const translate = (lang: string, language_id: string, language_params?: TranslateParamsInterface): string => { 67 | let text = ""; 68 | 69 | text = check(lang, language_id); 70 | if (language_params) { 71 | text = replaceParams(text, language_params); 72 | } 73 | 74 | return text; 75 | }; 76 | 77 | export { translate }; 78 | export default translate; 79 | -------------------------------------------------------------------------------- /app/functions/commands/top_daily.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Top10 Daily 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import telegram from "@routes/api/telegram"; 14 | import db from "@routes/api/database"; 15 | import { getTopScoreEmoji } from "@app/functions/utils/utils"; 16 | 17 | import type { MasterInterface } from "@app/types/master.interfaces"; 18 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 19 | 20 | import logger from "@app/functions/utils/logger"; 21 | 22 | const topDaily = async (): Promise => { 23 | bot.command(["topdaily", "top_daily", "top10daily"], async (ctx) => { 24 | logger.info("command: /top10daily", "top_daily.ts:topdaily()"); 25 | const lang = await telegram.api.message.getLanguage(ctx); 26 | 27 | if (telegram.api.message.getChatID(ctx) < 0) { 28 | // is group chat 29 | const top_scores: MasterInterface[] = await db.scores.getMultiple({ 30 | group_id: telegram.api.message.getChatID(ctx), 31 | }); 32 | 33 | let mapped_scores: MasterInterface[] = await Promise.all( 34 | top_scores.map(async (s: MasterInterface) => { 35 | const user_questions: QuestionsInterface = await db.questions.get({ 36 | group_id: telegram.api.message.getChatID(ctx), 37 | user_id: s?.id || "", 38 | }); 39 | 40 | if (user_questions) { 41 | s[`score_daily`] = 42 | s[`score_daily`] + user_questions[`upvotes_daily`] - user_questions[`downvotes_daily`]; 43 | } 44 | return s; 45 | }), 46 | ); 47 | 48 | mapped_scores = mapped_scores.sort((a, b) => b?.[`score_daily`] - a?.[`score_daily`]).slice(0, 10); 49 | 50 | const scores_message = mapped_scores 51 | .map((s: MasterInterface, index: number) => { 52 | return translate(lang.language, "top10_command_list", { 53 | emoji: getTopScoreEmoji(index), 54 | first_name: s.first_name, 55 | username: s.username, 56 | score: s[`score_daily`], 57 | }); 58 | }) 59 | .join(""); 60 | 61 | if (scores_message) { 62 | await telegram.api.message.send(ctx, telegram.api.message.getChatID(ctx), scores_message); 63 | } else { 64 | await telegram.api.message.send( 65 | ctx, 66 | telegram.api.message.getChatID(ctx), 67 | translate(lang.language, "top10_command_not_available"), 68 | ); 69 | } 70 | } else { 71 | await telegram.api.message.send( 72 | ctx, 73 | telegram.api.message.getChatID(ctx), 74 | translate(lang.language, "command_only_group"), 75 | ); 76 | } 77 | }); 78 | }; 79 | 80 | export { topDaily }; 81 | export default topDaily; 82 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "browser": false, 6 | "jest/globals": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"], 10 | "parserOptions": { 11 | "sourceType": "module", 12 | "ecmaVersion": 2019 13 | }, 14 | "plugins": ["jsdoc", "jest", "@typescript-eslint"], 15 | "globals": { 16 | "fetch": false 17 | }, 18 | "settings": { 19 | "jsdoc": { 20 | "tagNamePreference": { 21 | "returns": "return" 22 | } 23 | } 24 | }, 25 | "rules": { 26 | "no-multi-spaces": [ 27 | "error", 28 | { 29 | "ignoreEOLComments": true, 30 | "exceptions": { 31 | "VariableDeclarator": true 32 | } 33 | } 34 | ], 35 | "block-spacing": ["error", "always"], 36 | "array-bracket-spacing": ["error", "never"], 37 | "space-in-parens": ["error", "never"], 38 | "comma-spacing": [ 39 | "error", 40 | { 41 | "before": false, 42 | "after": true 43 | } 44 | ], 45 | "key-spacing": [ 46 | "error", 47 | { 48 | "afterColon": true, 49 | "beforeColon": false 50 | } 51 | ], 52 | "quotes": [ 53 | "error", 54 | "double", 55 | { 56 | "avoidEscape": true, 57 | "allowTemplateLiterals": true 58 | } 59 | ], 60 | "semi": ["error", "always"], 61 | "no-console": ["warn"], 62 | "no-constant-condition": ["warn"], 63 | "curly": ["error", "all"], 64 | "brace-style": [ 65 | "error", 66 | "1tbs", 67 | { 68 | "allowSingleLine": false 69 | } 70 | ], 71 | "keyword-spacing": [ 72 | "error", 73 | { 74 | "before": true, 75 | "after": true 76 | } 77 | ], 78 | "object-curly-spacing": ["error", "always"], 79 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], 80 | "spaced-comment": [2, "always"], 81 | "space-before-blocks": ["error", "always"], 82 | "space-before-function-paren": "off", 83 | "prefer-template": "error", 84 | "no-useless-concat": "error", 85 | "linebreak-style": ["error", "unix"], 86 | "eol-last": ["error", "always"], 87 | "template-curly-spacing": ["error", "never"], 88 | "no-multiple-empty-lines": "off", 89 | "jest/no-disabled-tests": "warn", 90 | "jest/no-focused-tests": "error", 91 | "jest/no-identical-title": "error", 92 | "jest/prefer-to-have-length": "warn", 93 | "jest/valid-expect": "error", 94 | "jsdoc/require-param": 1, 95 | "jsdoc/require-param-description": 1, 96 | "jsdoc/require-param-name": 1, 97 | "jsdoc/require-param-type": 1, 98 | "jsdoc/require-returns": 1, 99 | "jsdoc/require-returns-description": 1, 100 | "jsdoc/require-returns-type": 1, 101 | "jsdoc/require-returns-check": 1, 102 | "jsdoc/require-hyphen-before-param-description": 1 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /app/functions/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils 3 | * ===================== 4 | * Utility 5 | * 6 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 7 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | 13 | /** 14 | * Get emoji helper 15 | * ===================== 16 | * Get an emoji by indexes position 17 | * 18 | * @param {number} index - index refered to the top 10 position 19 | * @return {string} medal_emoji - emoji 20 | */ 21 | const getTopScoreEmoji = (index: number): string => { 22 | let medal_emoji = ""; 23 | switch (index) { 24 | case 0: 25 | medal_emoji = "🥇"; 26 | break; 27 | case 1: 28 | medal_emoji = "🥈"; 29 | break; 30 | case 2: 31 | medal_emoji = "🥉"; 32 | break; 33 | case 3: 34 | medal_emoji = "4️⃣"; 35 | break; 36 | case 4: 37 | medal_emoji = "5️⃣"; 38 | break; 39 | case 5: 40 | medal_emoji = "6️⃣"; 41 | break; 42 | case 6: 43 | medal_emoji = "7️⃣"; 44 | break; 45 | case 7: 46 | medal_emoji = "8️⃣"; 47 | break; 48 | case 8: 49 | medal_emoji = "9️⃣"; 50 | break; 51 | case 9: 52 | medal_emoji = "💩"; 53 | break; 54 | } 55 | return medal_emoji; 56 | }; 57 | 58 | /** 59 | * Get similarity of two words 60 | * ===================== 61 | * Get a number from 0 to 1 representing the similarity of two words 62 | * 63 | * @param {string} s1 - First string 64 | * @param {string} s2 - Second string 65 | * @return {number} percentage - the value from 0 to 1 representing the similarity 66 | */ 67 | const similarity = (s1: string, s2: string): number => { 68 | let longer = s1; 69 | let shorter = s2; 70 | if (s1.length < s2.length) { 71 | longer = s2; 72 | shorter = s1; 73 | } 74 | const longerLength: number = longer.length; 75 | if (longerLength == 0) { 76 | return 1.0; 77 | } 78 | return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength.toString()); 79 | }; 80 | 81 | function editDistance(s1: string, s2: string) { 82 | s1 = s1.toLowerCase(); 83 | s2 = s2.toLowerCase(); 84 | 85 | const costs: number[] = []; 86 | for (let i = 0; i <= s1.length; i++) { 87 | let lastValue = i; 88 | for (let j = 0; j <= s2.length; j++) { 89 | if (i == 0) { 90 | costs[j] = j; 91 | } else { 92 | if (j > 0) { 93 | let newValue = costs[j - 1]; 94 | if (s1.charAt(i - 1) != s2.charAt(j - 1)) { 95 | newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; 96 | } 97 | costs[j - 1] = lastValue; 98 | lastValue = newValue; 99 | } 100 | } 101 | } 102 | if (i > 0) { 103 | costs[s2.length] = lastValue; 104 | } 105 | } 106 | return costs[s2.length]; 107 | } 108 | 109 | export { getTopScoreEmoji, similarity }; 110 | export default { 111 | getTopScoreEmoji, 112 | similarity, 113 | }; 114 | -------------------------------------------------------------------------------- /app/functions/commands/top_yearly.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Top10 Yearly 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import telegram from "@routes/api/telegram"; 14 | import db from "@routes/api/database"; 15 | import { getTopScoreEmoji } from "@app/functions/utils/utils"; 16 | 17 | import type { MasterInterface } from "@app/types/master.interfaces"; 18 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 19 | 20 | import logger from "@app/functions/utils/logger"; 21 | 22 | const topYearly = async (): Promise => { 23 | bot.command(["top2021", "top2022", "top2023", "top2024", "top2025"], async (ctx) => { 24 | logger.info("command: /topyearly", "topYearly.ts:topyearly()"); 25 | const lang = await telegram.api.message.getLanguage(ctx); 26 | 27 | if (telegram.api.message.getChatID(ctx) < 0) { 28 | // is group chat 29 | const top_scores: MasterInterface[] = await db.scores.getMultiple({ 30 | group_id: telegram.api.message.getChatID(ctx), 31 | }); 32 | const year = ctx?.update?.message?.text?.substring(4, 8) || 2022; 33 | 34 | let mapped_scores: MasterInterface[] = await Promise.all( 35 | top_scores.map(async (s: MasterInterface) => { 36 | const user_questions: QuestionsInterface = await db.questions.get({ 37 | group_id: telegram.api.message.getChatID(ctx), 38 | user_id: s?.id || "", 39 | }); 40 | 41 | if (user_questions) { 42 | s[`score_${year}`] = 43 | s[`score_${year}`] + 44 | user_questions[`upvotes_${year}`] - 45 | user_questions[`downvotes_${year}`]; 46 | } 47 | return s; 48 | }), 49 | ); 50 | 51 | mapped_scores = mapped_scores.sort((a, b) => b?.[`score_${year}`] - a?.[`score_${year}`]).slice(0, 10); 52 | 53 | const scores_message = mapped_scores 54 | .map((s: MasterInterface, index: number) => { 55 | return translate(lang.language, "top10_command_list", { 56 | emoji: getTopScoreEmoji(index), 57 | first_name: s.first_name, 58 | username: s.username, 59 | score: s[`score_${year}`], 60 | }); 61 | }) 62 | .join(""); 63 | 64 | if (scores_message) { 65 | await telegram.api.message.send(ctx, telegram.api.message.getChatID(ctx), scores_message); 66 | } else { 67 | await telegram.api.message.send( 68 | ctx, 69 | telegram.api.message.getChatID(ctx), 70 | translate(lang.language, "top10_command_not_available"), 71 | ); 72 | } 73 | } else { 74 | await telegram.api.message.send( 75 | ctx, 76 | telegram.api.message.getChatID(ctx), 77 | translate(lang.language, "command_only_group"), 78 | ); 79 | } 80 | }); 81 | }; 82 | 83 | export { topYearly }; 84 | export default topYearly; 85 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | > Code and Contributions 4 | 5 | Copyright (c) 2021 Patryk Rzucidło (PTKDev) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ##### https://choosealicense.com/licenses/mit/ 26 | 27 | ## Creative Commons BY-NC 4.0 License 28 | 29 | > Images, assets and logos 30 | 31 | Copyleft (c) 2022 Patryk Rzucidło (PTKDev) 32 | 33 | #### You are free to: 34 | 35 | - Share — copy and redistribute the material in any medium or format 36 | - Adapt — remix, transform, and build upon the material 37 | 38 | The licensor cannot revoke these freedoms as long as you follow the license terms. 39 | 40 | #### Under the following terms: 41 | 42 | - Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that - suggests the licensor endorses you or your use. 43 | - NonCommercial — You may not use the material for commercial purposes. 44 | 45 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 46 | 47 | ##### https://creativecommons.org/licenses/by-nc/4.0/ 48 | 49 | ## Creative Commons BY 4.0 License 50 | 51 | > Documentation and Translations 52 | 53 | Copyleft (c) 2022 Patryk Rzucidło (PTKDev) 54 | 55 | #### You are free to: 56 | 57 | - Share — copy and redistribute the material in any medium or format 58 | - Adapt — remix, transform, and build upon the material for any purpose, even commercially. 59 | 60 | The licensor cannot revoke these freedoms as long as you follow the license terms. 61 | 62 | #### Under the following terms: 63 | 64 | - Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 65 | 66 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 67 | 68 | ##### https://creativecommons.org/licenses/by/4.0/ 69 | -------------------------------------------------------------------------------- /app/functions/commands/top_monthly.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Top10 Monthly 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import telegram from "@routes/api/telegram"; 14 | import db from "@routes/api/database"; 15 | import { getTopScoreEmoji } from "@app/functions/utils/utils"; 16 | 17 | import type { MasterInterface } from "@app/types/master.interfaces"; 18 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 19 | 20 | import logger from "@app/functions/utils/logger"; 21 | 22 | const topMonthly = async (): Promise => { 23 | bot.command(["topmonthly", "top_monthly", "top10monthly"], async (ctx) => { 24 | logger.info("command: /top10monthly", "topYearly.ts:topMonthly()"); 25 | const lang = await telegram.api.message.getLanguage(ctx); 26 | 27 | if (telegram.api.message.getChatID(ctx) < 0) { 28 | // is group chat 29 | let month = new Date().getMonth() + 1; 30 | if (ctx?.match) { 31 | if (parseInt(ctx?.match) >= 1 && parseInt(ctx?.match) <= 12) { 32 | month = parseInt(ctx?.match); 33 | } 34 | } 35 | const top_scores: MasterInterface[] = await db.scores.getMultiple({ 36 | group_id: telegram.api.message.getChatID(ctx), 37 | }); 38 | const year = new Date().getFullYear(); 39 | 40 | let mapped_scores: MasterInterface[] = await Promise.all( 41 | top_scores.map(async (s: MasterInterface) => { 42 | const user_questions: QuestionsInterface = await db.questions.get({ 43 | group_id: telegram.api.message.getChatID(ctx), 44 | user_id: s?.id || "", 45 | }); 46 | 47 | console.log(user_questions); 48 | 49 | if (user_questions) { 50 | s[`score_${month}_${year}`] = 51 | s[`score_${month}_${year}`] + 52 | user_questions[`upvotes_${month}_${year}`] - 53 | user_questions[`downvotes_${month}_${year}`]; 54 | } 55 | return s; 56 | }), 57 | ); 58 | 59 | mapped_scores = mapped_scores 60 | .sort((a, b) => b?.[`score_${month}_${year}`] - a?.[`score_${month}_${year}`]) 61 | .slice(0, 10); 62 | 63 | const scores_message = mapped_scores 64 | .map((s: MasterInterface, index: number) => { 65 | return translate(lang.language, "top10_command_list", { 66 | emoji: getTopScoreEmoji(index), 67 | first_name: s.first_name, 68 | username: s.username, 69 | score: s[`score_${month}_${year}`], 70 | }); 71 | }) 72 | .join(""); 73 | 74 | if (scores_message) { 75 | await telegram.api.message.send(ctx, telegram.api.message.getChatID(ctx), scores_message); 76 | } else { 77 | await telegram.api.message.send( 78 | ctx, 79 | telegram.api.message.getChatID(ctx), 80 | translate(lang.language, "top10_command_not_available"), 81 | ); 82 | } 83 | } else { 84 | await telegram.api.message.send( 85 | ctx, 86 | telegram.api.message.getChatID(ctx), 87 | translate(lang.language, "command_only_group"), 88 | ); 89 | } 90 | }); 91 | }; 92 | 93 | export { topMonthly }; 94 | export default topMonthly; 95 | -------------------------------------------------------------------------------- /app/functions/api/database/settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Settings database 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { Schema, model } from "mongoose"; 12 | import { logger } from "@app/functions/utils/logger"; 13 | 14 | import type { SettingsInterface } from "@app/types/settings.interfaces"; 15 | 16 | const schema = new Schema({ 17 | group_id: { type: Number, default: 0 }, 18 | language: { type: String, default: "auto" }, 19 | pin_message: { type: Boolean, default: true }, 20 | }); 21 | 22 | const query = model("Settings", schema, "settings"); 23 | 24 | /** 25 | * Settings CRUD 26 | * ===================== 27 | * Add settings to DB 28 | * 29 | * @param {SettingsInterface} settings - settings to add 30 | */ 31 | const add = async (settings: SettingsInterface): Promise => { 32 | try { 33 | const doc = new query(settings); 34 | await doc.save(); 35 | } catch (error: unknown) { 36 | logger.error(JSON.stringify(error || ""), "settings.ts:add()"); 37 | } 38 | }; 39 | 40 | /** 41 | * Settings CRUD 42 | * ===================== 43 | * Remove settings from DB 44 | * 45 | * @param {Record} search - search condition e.g {id:"123"} 46 | */ 47 | const remove = async (search: Record): Promise => { 48 | try { 49 | await query.findOneAndDelete(search); 50 | } catch (error: unknown) { 51 | logger.error(JSON.stringify(error || ""), "settings.ts:remove()"); 52 | } 53 | }; 54 | 55 | /** 56 | * Settings CRUD 57 | * ===================== 58 | * Update settings from DB 59 | * 60 | * @param {Record} search - search condition e.g {id:"123"} 61 | * @param {SettingsInterface} settings - settings info to update 62 | */ 63 | const update = async ( 64 | search: Record, 65 | settings: SettingsInterface, 66 | ): Promise => { 67 | try { 68 | await query.findOneAndUpdate(search, settings); 69 | } catch (error: unknown) { 70 | logger.error(JSON.stringify(error || ""), "settings.ts:update()"); 71 | } 72 | }; 73 | 74 | /** 75 | * Settings CRUD 76 | * ===================== 77 | * Get settings from DB 78 | * 79 | * @param {Record} search - search condition e.g {id:"123"} 80 | * @return {SettingsInterface[]} settings. 81 | 82 | */ 83 | const get = async (search: Record): Promise => { 84 | try { 85 | const settings = await query.findOne(search, function (error: string) { 86 | if (error) { 87 | logger.error(JSON.stringify(error || ""), "settings.ts:get()"); 88 | } 89 | }); 90 | 91 | return (await settings) || new query().toJSON(); 92 | } catch (error: unknown) { 93 | logger.error(JSON.stringify(error || ""), "settings.ts:get()"); 94 | } 95 | return new query().toJSON(); 96 | }; 97 | 98 | export { get, update, remove, add }; 99 | export default { get, update, remove, add }; 100 | -------------------------------------------------------------------------------- /app/functions/api/database/users.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Users database 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { Schema, model } from "mongoose"; 12 | import { logger } from "@app/functions/utils/logger"; 13 | 14 | import type { MasterInterface } from "@app/types/master.interfaces"; 15 | import type { GameInterface } from "@app/types/game.interfaces"; 16 | 17 | const schema = new Schema({ 18 | id: { type: String, default: "0" }, 19 | is_bot: { type: Boolean, default: false }, 20 | first_name: { type: String, default: "" }, 21 | username: { type: String, default: "" }, 22 | language_code: { type: String, default: "en" }, 23 | }); 24 | 25 | const query = model("User", schema, "users"); 26 | 27 | /** 28 | * Users CRUD 29 | * ===================== 30 | * Add user to DB 31 | * 32 | * @param {MasterInterface} user - user to add 33 | */ 34 | const add = async (user: MasterInterface): Promise => { 35 | try { 36 | const doc = new query(user); 37 | await doc.save(); 38 | } catch (error: unknown) { 39 | logger.error(JSON.stringify(error || ""), "users.ts:add()"); 40 | } 41 | }; 42 | 43 | /** 44 | * Users CRUD 45 | * ===================== 46 | * Remove user from DB 47 | * 48 | * @param {Record} search - search condition e.g {id:"123"} 49 | */ 50 | const remove = async (search: Record): Promise => { 51 | try { 52 | await query.findOneAndDelete(search); 53 | } catch (error: unknown) { 54 | logger.error(JSON.stringify(error || ""), "users.ts:remove()"); 55 | } 56 | }; 57 | 58 | /** 59 | * Users CRUD 60 | * ===================== 61 | * Update user from DB 62 | * 63 | * @param {Record} search - search condition e.g {id:"123"} 64 | * @param {MasterInterface} user - user info to update 65 | */ 66 | const update = async (search: Record, user: MasterInterface): Promise => { 67 | try { 68 | await query.findOneAndUpdate(search, user); 69 | } catch (error: unknown) { 70 | logger.error(JSON.stringify(error || ""), "users.ts:update()"); 71 | } 72 | }; 73 | 74 | /** 75 | * Users CRUD 76 | * ===================== 77 | * Get user from DB 78 | * 79 | * @param {Record} search - search condition e.g {id:"123"} 80 | * @return {MasterInterface[]} user. 81 | 82 | */ 83 | const get = async (search: Record): Promise => { 84 | try { 85 | const user = await query.findOne(search, function (error: string) { 86 | if (error) { 87 | logger.error(JSON.stringify(error || ""), "users.ts:get()"); 88 | } 89 | }); 90 | 91 | return (await user) || new query().toJSON(); 92 | } catch (error: unknown) { 93 | logger.error(JSON.stringify(error || ""), "users.ts:get()"); 94 | } 95 | 96 | return new query().toJSON(); 97 | }; 98 | 99 | export { get, update, remove, add }; 100 | export default { get, update, remove, add }; 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.7.9 (March 31, 2023) 2 | 3 | - Fix: Now you cannot answer the question asked by yourself yourself 4 | - Feature: Night mode (alpha) 5 | - Changed: `/master` is now only for moderators and admins, tag a mod that will change the master 6 | - Feature:The `/topmonthly` command can specify the month, type `/topmonthly 3` refers to March 2023, by default it is the current month 7 | 8 | 9 | 10 | [![Donate Paypal](https://img.shields.io/badge/donate-paypal-005EA6.svg?style=for-the-badge&logo=paypal)](https://www.paypal.me/ptkdev) [![Donate Ko-Fi](https://img.shields.io/badge/donate-ko--fi-29abe0.svg?style=for-the-badge&logo=ko-fi)](https://ko-fi.com/ptkdev) [![Donate Github Sponsors](https://img.shields.io/badge/donate-sponsors-ea4aaa.svg?style=for-the-badge&logo=github)](https://github.com/sponsors/ptkdev) [![Donate Patreon](https://img.shields.io/badge/donate-patreon-F87668.svg?style=for-the-badge&logo=patreon)](https://www.patreon.com/join/ptkdev) [![Donate Bitcoin](https://img.shields.io/badge/BTC-35jQmZCy4nsxoMM3QPFrnZePDVhdKaHMRH-E38B29.svg?style=flat-square&logo=bitcoin)](https://ptk.dev/img/icons/menu/bitcoin_wallet.png) [![Donate Ethereum](https://img.shields.io/badge/ETH-0x8b8171661bEb032828e82baBb0B5B98Ba8fBEBFc-4E8EE9.svg?style=flat-square&logo=ethereum)](https://ptk.dev/img/icons/menu/ethereum_wallet.png) 11 | 12 | 13 | 14 | # v0.7.8 (February 27, 2023) 15 | 16 | - Fix: message confirmed if the question (photo) is on the group 17 | - Fix: can't win if you are master 18 | - Fix: CI/CD 19 | - Feature: Multi hints 20 | 21 | # v0.7.7 (January 29, 2023) 22 | 23 | - Feature: `/topmonthly` 24 | 25 | # v0.7.6 (November 18, 2022) 26 | 27 | - New: penality if you transfer master to other player (and you are master) 28 | - Fix: message confirmed if the question is on the group 29 | 30 | # v0.7.5 (November 18, 2022) 31 | 32 | - New: vote buttons in win message 33 | - Fix: `/score @nickname` 34 | 35 | # v0.7.4 (November 17, 2022) 36 | 37 | - Fix: misprint in the translation 38 | - Fix: unpin if exist question and use `/master` 39 | - Fix: unpin if exist question and master send again a new question 40 | - New: `/master off` to stop bot 41 | 42 | # v0.7.3 (November 07, 2022) 43 | 44 | - Fix: Telegram Topics 45 | 46 | # v0.7.2 (January 10, 2022) 47 | 48 | - Fix: now split with two HASHTAGS (##), not with MINUS sign (-) 49 | - Feature: TOP10 yearly 50 | 51 | # v0.6.0 (November 24, 2021) 52 | 53 | - Refactor: No Any 54 | - Feature: /show - current quiz 55 | - Feature: /ping - call to play 56 | - Feature: /groups - official groups 57 | - Fix: unpin current quiz when the user pick the master role 58 | - Fix: remove up/down vote from previously quiz 59 | - Fix: default language from telegram api 60 | 61 | # v0.5.0 (November 21, 2021) 62 | 63 | - Porting: from Telegraf to Grammy Framework 64 | 65 | # v0.4.0 (November 07, 2021) 66 | 67 | - Fix bug 68 | - Vote the questions/quizzes 69 | 70 | # v0.3.0 (October 18, 2021) 71 | 72 | - Multilanguage support 73 | - Scores board 74 | - Porting from lowdb to mongodb 75 | 76 | # v0.2.0 (August 23, 2021) 77 | 78 | - Stable version (only italian language) 79 | - Work with all groups, play with friends! 80 | 81 | # v0.1.0 (August 16, 2021) 82 | 83 | - First release 84 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## AUTO-DETECT 2 | * text=auto 3 | 4 | ## SOURCE CODE 5 | *.bat text eol=crlf 6 | *.css text eol=lf 7 | *.html text diff=html eol=lf 8 | *.ini text eol=crlf 9 | *.js text eol=lf 10 | *.json text eol=lf 11 | *.php text diff=php eol=lf 12 | *.py text diff=python eol=lf 13 | *.rb text diff=ruby 14 | *.sass text eol=lf 15 | *.scss text eol=lf 16 | *.sh text eol=lf 17 | *.sql text eol=lf 18 | *.ts text eol=lf 19 | *.vue text eol=lf 20 | *.svelte text eol=lf 21 | *.xml text eol=lf 22 | *.xhtml text diff=html eol=lf 23 | 24 | ## DOCKER 25 | *.dockerignore text eol=lf 26 | Dockerfile text eol=lf 27 | 28 | ## DOCUMENTATION 29 | *.md text eol=lf 30 | *.txt text eol=lf 31 | AUTHORS text eol=lf 32 | CHANGELOG text eol=lf 33 | CHANGES text eol=lf 34 | CONTRIBUTING text eol=lf 35 | COPYING text eol=lf 36 | INSTALL text eol=lf 37 | license text eol=lf 38 | LICENSE text eol=lf 39 | NEWS text eol=lf 40 | README text eol=lf 41 | TODO text eol=lf 42 | 43 | ## TEMPLATES 44 | *.dot text eol=lf 45 | *.tpl text eol=lf 46 | *.twig text eol=lf 47 | 48 | ## LINTERS 49 | .csslintrc text eol=lf 50 | .eslintrc text eol=lf 51 | .htmlhintrc text eol=lf 52 | .jscsrc text eol=lf 53 | .jshintrc text eol=lf 54 | .jshintignore text eol=lf 55 | .stylelintrc text eol=lf 56 | .npmignore text eol=lf 57 | 58 | ## CONFIGS 59 | *.bowerrc text eol=lf 60 | *.cnf text eol=lf 61 | *.conf text eol=lf 62 | *.config text eol=lf 63 | .babelrc text eol=lf 64 | .browserslistrc text eol=lf 65 | .editorconfig text eol=lf 66 | .env text eol=lf 67 | .gitattributes text eol=lf 68 | .gitconfig text eol=lf 69 | .htaccess text eol=lf 70 | *.lock text eol=lf 71 | *.npmignore text eol=lf 72 | *.yaml text eol=lf 73 | *.yml text eol=lf 74 | browserslist text eol=lf 75 | Makefile text eol=lf 76 | makefile text eol=lf 77 | 78 | ## GRAPHICS 79 | *.ai binary 80 | *.bmp binary 81 | *.eps binary 82 | *.gif binary 83 | *.ico binary 84 | *.jng binary 85 | *.jp2 binary 86 | *.jpg binary 87 | *.jpeg binary 88 | *.jpx binary 89 | *.jxr binary 90 | *.pdf binary 91 | *.png binary 92 | *.psb binary 93 | *.psd binary 94 | *.svg text 95 | *.svgz binary 96 | *.tif binary 97 | *.tiff binary 98 | *.wbmp binary 99 | *.webp binary 100 | 101 | ## AUDIO 102 | *.kar binary 103 | *.m4a binary 104 | *.mid binary 105 | *.midi binary 106 | *.mp3 binary 107 | *.ogg binary 108 | *.ra binary 109 | 110 | ## VIDEO 111 | *.3gpp binary 112 | *.3gp binary 113 | *.as binary 114 | *.asf binary 115 | *.asx binary 116 | *.fla binary 117 | *.flv binary 118 | *.m4v binary 119 | *.mng binary 120 | *.mov binary 121 | *.mp4 binary 122 | *.mpeg binary 123 | *.mpg binary 124 | *.ogv binary 125 | *.swc binary 126 | *.swf binary 127 | *.webm binary 128 | 129 | ## ARCHIVES 130 | *.7z binary 131 | *.gz binary 132 | *.jar binary 133 | *.rar binary 134 | *.tar binary 135 | *.zip binary 136 | 137 | ## FONTS 138 | *.ttf binary 139 | *.eot binary 140 | *.otf binary 141 | *.woff binary 142 | *.woff2 binary 143 | 144 | ## EXECUTABLES 145 | *.exe binary 146 | *.pyc binary -------------------------------------------------------------------------------- /app/functions/commands/score.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Score 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import db from "@routes/api/database"; 14 | import telegram from "@routes/api/telegram"; 15 | 16 | import { MasterInterface } from "@app/types/master.interfaces"; 17 | import { QuestionsInterface } from "@app/types/question.interfaces"; 18 | 19 | import logger from "@app/functions/utils/logger"; 20 | 21 | /** 22 | * command: /score 23 | * ===================== 24 | * Get user score 25 | * 26 | */ 27 | const score = async (): Promise => { 28 | bot.command("score", async (ctx) => { 29 | logger.info("command: /score", "score.ts:score()"); 30 | const lang = await telegram.api.message.getLanguage(ctx); 31 | 32 | if (telegram.api.message.getChatID(ctx) < 0) { 33 | // is group chat 34 | if ( 35 | telegram.api.message.getText(ctx).trim() === "/score" || 36 | telegram.api.message.getText(ctx).trim() === `/score@${telegram.api.bot.getUsername(ctx)}` 37 | ) { 38 | const score: MasterInterface = await db.scores.get({ 39 | group_id: telegram.api.message.getChatID(ctx), 40 | id: telegram.api.message.getUserID(ctx), 41 | }); 42 | const user_questions: QuestionsInterface = await db.questions.get({ 43 | group_id: telegram.api.message.getChatID(ctx), 44 | user_id: telegram.api.message.getUserID(ctx), 45 | }); 46 | 47 | if (user_questions) { 48 | score[`score_${new Date().getFullYear()}`] = 49 | score[`score_${new Date().getFullYear()}`] + 50 | user_questions[`upvotes_${new Date().getFullYear()}`] - 51 | user_questions[`downvotes_${new Date().getFullYear()}`]; 52 | } 53 | 54 | await telegram.api.message.send( 55 | ctx, 56 | telegram.api.message.getChatID(ctx), 57 | translate(lang.language, "score_command_show", { 58 | first_name: telegram.api.message.getUserFirstName(ctx) || "", 59 | username: telegram.api.message.getUsername(ctx) || "", 60 | score: score?.[`score_${new Date().getFullYear()}`] || 0, 61 | }), 62 | ); 63 | } else { 64 | const username = telegram.api.message 65 | .getText(ctx) 66 | .replace("/score ", "") 67 | .replace(`/score@${telegram.api.bot.getUsername(ctx)}`, "") 68 | .replace("@", "") 69 | .trim(); 70 | 71 | const score: MasterInterface = await db.scores.get({ 72 | group_id: telegram.api.message.getChatID(ctx), 73 | username, 74 | }); 75 | const user_questions: QuestionsInterface = await db.questions.get({ 76 | group_id: telegram.api.message.getChatID(ctx), 77 | user_id: score.id, 78 | }); 79 | 80 | if (user_questions) { 81 | score[`score_${new Date().getFullYear()}`] = 82 | score[`score_${new Date().getFullYear()}`] + 83 | user_questions[`upvotes_${new Date().getFullYear()}`] - 84 | user_questions[`downvotes_${new Date().getFullYear()}`]; 85 | } 86 | 87 | await telegram.api.message.send( 88 | ctx, 89 | telegram.api.message.getChatID(ctx), 90 | translate(lang.language, "score_command_show_with_username", { 91 | username: username, 92 | score: score?.[`score_${new Date().getFullYear()}`] || 0, 93 | }), 94 | ); 95 | } 96 | } else { 97 | await telegram.api.message.send( 98 | ctx, 99 | telegram.api.message.getChatID(ctx), 100 | translate(lang.language, "command_only_group"), 101 | ); 102 | } 103 | }); 104 | }; 105 | 106 | export { score }; 107 | export default score; 108 | -------------------------------------------------------------------------------- /app/functions/api/database/master.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Master database 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * Alessandro Di Maria [@ImAl3x03] (https://github.com/ImAl3x03) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import { Schema, model } from "mongoose"; 13 | import { logger } from "@app/functions/utils/logger"; 14 | 15 | import type { MasterInterface } from "@app/types/master.interfaces"; 16 | 17 | const schema = new Schema({ 18 | id: { type: String, default: "0" }, 19 | is_bot: { type: Boolean, default: false }, 20 | first_name: { type: String, default: "" }, 21 | username: { type: String, default: "" }, 22 | language_code: { type: String, default: "en" }, 23 | group_id: { type: Number, default: 0 }, 24 | question: { type: String, default: "" }, 25 | description: { type: String, default: "" }, 26 | pin_id: { type: Number, default: 0 }, 27 | timezone: { type: String, default: "" }, 28 | win_message_id: { type: Number, default: 0 }, 29 | message_thread_id: { type: Number, default: 0 }, 30 | off: { type: Boolean, default: false }, 31 | }); 32 | 33 | const query = model("Master", schema, "master"); 34 | 35 | /** 36 | * Master CRUD 37 | * ===================== 38 | * Add master to DB 39 | * 40 | * @param {MasterInterface} user - user master to add 41 | */ 42 | const add = async (user: MasterInterface): Promise => { 43 | try { 44 | const doc = new query(user); 45 | await doc.save(); 46 | } catch (error: unknown) { 47 | logger.error(JSON.stringify(error || ""), "master.ts:add()"); 48 | } 49 | }; 50 | 51 | /** 52 | * Master CRUD 53 | * ===================== 54 | * Remove master from DB 55 | * 56 | * @param {Record} search - search condition e.g {id: "123"} 57 | */ 58 | const remove = async (search: Record): Promise => { 59 | try { 60 | await query.findOneAndDelete(search); 61 | } catch (error: unknown) { 62 | logger.error(JSON.stringify(error || ""), "master.ts:remove()"); 63 | } 64 | }; 65 | 66 | /** 67 | * Master CRUD 68 | * ===================== 69 | * Update master from DB 70 | * 71 | * @param {Record} search - search condition e.g {id: "123"} 72 | * @param {MasterInterface} user - data to update 73 | */ 74 | const update = async (search: Record, user: MasterInterface): Promise => { 75 | try { 76 | await query.findOneAndUpdate(search, user); 77 | } catch (error: unknown) { 78 | logger.error(JSON.stringify(error || ""), "master.ts:update()"); 79 | } 80 | }; 81 | 82 | /** 83 | * Master CRUD 84 | * ===================== 85 | * Get master from DB 86 | * 87 | * @param {Record} search - search condition e.g {id:"123"} 88 | * @return {MasterInterface} user. 89 | */ 90 | const get = async (search: Record): Promise => { 91 | try { 92 | const user = query.findOne(search, function (error: string) { 93 | if (error) { 94 | logger.error(JSON.stringify(error || ""), "master.ts:get()"); 95 | } 96 | }); 97 | 98 | return (await user) || new query().toJSON(); 99 | } catch (error: unknown) { 100 | logger.error(JSON.stringify(error || ""), "master.ts:get()"); 101 | } 102 | 103 | return new query().toJSON(); 104 | }; 105 | 106 | /** 107 | * Master CRUD 108 | * ===================== 109 | * Get multiple masters DB 110 | * 111 | * @param {Record} search - search condition e.g {id:"123"} 112 | * @return {MasterInterface[]} user. 113 | 114 | */ 115 | const getMultiple = async (search: Record): Promise => { 116 | try { 117 | const master = await query.find(search, function (error: string) { 118 | if (error) { 119 | logger.error(JSON.stringify(error || ""), "master.ts:getMultiple()"); 120 | } 121 | }); 122 | return (await master) || []; 123 | } catch (error: unknown) { 124 | logger.error(JSON.stringify(error || ""), "master.ts:getMultiple()"); 125 | } 126 | 127 | return []; 128 | }; 129 | 130 | export { get, update, remove, add, getMultiple }; 131 | export default { get, update, remove, add, getMultiple }; 132 | -------------------------------------------------------------------------------- /app/functions/utils/vote.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vote 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { InlineKeyboard } from "grammy"; 12 | import telegram from "@routes/api/telegram"; 13 | import db from "@routes/api/database"; 14 | import translate from "@translations/translate"; 15 | 16 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 17 | 18 | const vote = async (ctx, type, user_id): Promise => { 19 | const lang = await telegram.api.message.getLanguage(ctx); 20 | 21 | if (telegram.api.message.getChatID(ctx) < 0) { 22 | // is group chat 23 | 24 | const voter_user_id = telegram.api.message.getUserIDFromAction(ctx); 25 | const message_id = telegram.api.message.getMessageIDFromAction(ctx); 26 | 27 | // If it's a self vote (Comment this part for debugging) 28 | /* if (user_id === voter_user_id) { 29 | await telegram.api.message.send( 30 | ctx, 31 | telegram.api.message.getChatID(ctx), 32 | translate(lang.language, "goodquestion_not_autovote"), 33 | ); 34 | return; 35 | }*/ 36 | 37 | if (user_id && user_id !== "") { 38 | const group_id = telegram.api.message.getChatID(ctx); 39 | const is_upvote = type === "upvote"; 40 | 41 | const user_questions: QuestionsInterface = await db.questions.get({ 42 | group_id: telegram.api.message.getChatID(ctx), 43 | user_id, 44 | }); 45 | 46 | if (user_questions.group_id < 0) { 47 | // if voted user is in the question DB 48 | 49 | const same_message: boolean = user_questions.voters.message_id === message_id; 50 | // If the voter user has already voted this question/message 51 | 52 | if ( 53 | same_message && 54 | (is_upvote 55 | ? user_questions?.voters?.users?.upvotes?.some((u) => u === voter_user_id) 56 | : user_questions?.voters?.users?.downvotes?.some((u) => u === voter_user_id)) 57 | ) { 58 | return; 59 | } 60 | 61 | if (is_upvote) { 62 | user_questions[`upvotes_${new Date().getFullYear()}`] += 1; 63 | user_questions[`upvotes_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] += 1; 64 | } else { 65 | user_questions[`downvotes_${new Date().getFullYear()}`] += 1; 66 | user_questions[`downvotes_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] += 1; 67 | } 68 | user_questions.voters = { 69 | message_id, 70 | users: same_message 71 | ? is_upvote 72 | ? { 73 | ...user_questions.voters.users, 74 | upvotes: [...user_questions.voters.users.upvotes, voter_user_id], 75 | } 76 | : { 77 | ...user_questions.voters.users, 78 | downvotes: [...user_questions.voters.users.downvotes, voter_user_id], 79 | } 80 | : { 81 | upvotes: is_upvote ? [voter_user_id] : [], 82 | downvotes: is_upvote ? [] : [voter_user_id], 83 | }, 84 | }; 85 | await db.questions.update({ group_id, user_id }, user_questions); 86 | 87 | const buttons = new InlineKeyboard(); 88 | buttons.text(`👍 ${user_questions?.voters?.users?.upvotes?.length || 0} `, `upvote ${user_id}`); 89 | buttons.text(`👎 ${user_questions?.voters?.users?.downvotes?.length || 0} `, `downvote ${user_id}`); 90 | 91 | await telegram.api.message.editMessageReplyMarkup(ctx, { 92 | reply_markup: buttons, 93 | }); 94 | } else { 95 | const json = { 96 | user_id, 97 | group_id: group_id, 98 | voters: { 99 | message_id, 100 | users: { 101 | upvotes: is_upvote ? [voter_user_id] : [], 102 | downvotes: is_upvote ? [] : [voter_user_id], 103 | }, 104 | }, 105 | }; 106 | 107 | json[`upvotes_${new Date().getFullYear()}`] = is_upvote ? 1 : 0; 108 | json[`downvotes_${new Date().getFullYear()}`] = is_upvote ? 0 : 1; 109 | json[`upvotes_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] = is_upvote ? 1 : 0; 110 | json[`downvotes_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] = is_upvote ? 0 : 1; 111 | await db.questions.add(json); 112 | 113 | const buttons = new InlineKeyboard(); 114 | buttons.text(`👍 ${is_upvote ? 1 : 0} `, `upvote ${user_id}`); 115 | buttons.text(`👎 ${is_upvote ? 0 : 1}`, `downvote ${user_id}`); 116 | 117 | await telegram.api.message.editMessageReplyMarkup(ctx, { 118 | reply_markup: buttons, 119 | }); 120 | } 121 | } 122 | } else { 123 | await telegram.api.message.send( 124 | ctx, 125 | telegram.api.message.getChatID(ctx), 126 | translate(lang.language, "command_only_group"), 127 | ); 128 | } 129 | }; 130 | 131 | export { vote }; 132 | export default vote; 133 | -------------------------------------------------------------------------------- /app/functions/commands/settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Settings 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { InlineKeyboard } from "grammy"; 12 | import bot from "@app/core/token"; 13 | import translate from "@translations/translate"; 14 | import db from "@routes/api/database"; 15 | import telegram from "@routes/api/telegram"; 16 | import logger from "@app/functions/utils/logger"; 17 | 18 | /** 19 | * command: /settings 20 | * ===================== 21 | * Set language 22 | * 23 | */ 24 | const settings = async (): Promise => { 25 | bot.command("settings", async (ctx) => { 26 | logger.info("command: /settings", "settings.ts:settings()"); 27 | const lang = await telegram.api.message.getLanguage(ctx); 28 | 29 | const buttons = new InlineKeyboard(); 30 | 31 | buttons.text(translate(lang.language, "settings_command_setlanguage"), "settings_languages"); 32 | buttons.row(); 33 | buttons.url( 34 | translate(lang.language, "settings_command_opensource"), 35 | "https://github.com/ptkdev/quizquickanswer-telegram-game-bot", 36 | ); 37 | buttons.row(); 38 | buttons.text(translate(lang.language, "settings_command_credits"), "settings_credits"); 39 | buttons.row(); 40 | buttons.url(translate(lang.language, "settings_command_email"), "https://t.me/QuizQuickAnswerGroup"); 41 | 42 | await telegram.api.message.send( 43 | ctx, 44 | telegram.api.message.getChatID(ctx), 45 | translate(lang.language, "settings_command_options"), 46 | { 47 | reply_markup: buttons, 48 | parse_mode: "HTML", 49 | }, 50 | ); 51 | }); 52 | 53 | bot.callbackQuery("settings_languages", async (ctx) => { 54 | const lang = await telegram.api.message.getLanguage(ctx); 55 | 56 | const buttons = new InlineKeyboard(); 57 | 58 | buttons.text(translate(lang.language, "settings_command_language_english"), "settings_set_english"); 59 | buttons.text(translate(lang.language, "settings_command_language_italian"), "settings_set_italian"); 60 | buttons.row(); 61 | buttons.url( 62 | translate(lang.language, "settings_command_language_new"), 63 | "https://github.com/ptkdev/quizquickanswer-telegram-game-bot/tree/main/app/translations", 64 | ); 65 | 66 | await telegram.api.message.send( 67 | ctx, 68 | telegram.api.message.getChatID(ctx), 69 | translate(lang.language, "settings_command_switchlanguage"), 70 | { 71 | reply_markup: buttons, 72 | parse_mode: "HTML", 73 | }, 74 | ); 75 | }); 76 | 77 | bot.callbackQuery("settings_credits", async (ctx) => { 78 | const lang = await telegram.api.message.getLanguage(ctx); 79 | 80 | const buttons = new InlineKeyboard(); 81 | 82 | buttons.url(translate(lang.language, "settings_command_ptkdev"), "https://ptk.dev"); 83 | buttons.row(); 84 | buttons.url(translate(lang.language, "settings_command_ali"), "https://github.com/alishadman95/"); 85 | 86 | await telegram.api.message.send( 87 | ctx, 88 | telegram.api.message.getChatID(ctx), 89 | translate(lang.language, "settings_command_credits"), 90 | { 91 | reply_markup: buttons, 92 | parse_mode: "HTML", 93 | }, 94 | ); 95 | }); 96 | 97 | bot.callbackQuery("settings_set_english", async (ctx) => { 98 | const lang = await telegram.api.message.getLanguage(ctx); 99 | 100 | if (lang.group_id !== 0) { 101 | await db.settings.update( 102 | { group_id: telegram.api.message.getChatID(ctx) }, 103 | { group_id: telegram.api.message.getChatID(ctx), language: "en", pin_message: lang.pin_message }, 104 | ); 105 | } else { 106 | await db.settings.add({ 107 | group_id: telegram.api.message.getChatID(ctx), 108 | language: "en", 109 | pin_message: lang.pin_message, 110 | }); 111 | } 112 | 113 | await telegram.api.message.send( 114 | ctx, 115 | telegram.api.message.getChatID(ctx), 116 | translate(lang.language, "settings_command_current_english"), 117 | ); 118 | }); 119 | 120 | bot.callbackQuery("settings_set_italian", async (ctx) => { 121 | const lang = await telegram.api.message.getLanguage(ctx); 122 | 123 | if (lang.group_id !== 0) { 124 | await db.settings.update( 125 | { group_id: telegram.api.message.getChatID(ctx) }, 126 | { group_id: telegram.api.message.getChatID(ctx), language: "it", pin_message: lang.pin_message }, 127 | ); 128 | } else { 129 | await db.settings.add({ 130 | group_id: telegram.api.message.getChatID(ctx), 131 | language: "it", 132 | pin_message: lang.pin_message, 133 | }); 134 | } 135 | 136 | await telegram.api.message.send( 137 | ctx, 138 | telegram.api.message.getChatID(ctx), 139 | translate(lang.language, "settings_command_current_italian"), 140 | ); 141 | }); 142 | }; 143 | 144 | export { settings }; 145 | export default settings; 146 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ptkdev/quizquickanswer-telegram-game-bot", 3 | "displayName": "Quiz Quick Answer Bot", 4 | "description": "Funny quiz game for telegram groups", 5 | "version": "0.7.9-nightly.99", 6 | "main": "dist/core/bot.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "author": "Patryk Rzucidło [@ptkdev] (https://ptk.dev)", 11 | "license": "MIT", 12 | "license-docs": "CC BY 4.0", 13 | "license-translations": "CC BY 4.0", 14 | "license-images": "CC BY-NC 4.0", 15 | "homepage": "https://github.com/ptkdev/quizquickanswer-telegram-game-bot", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ptkdev/quizquickanswer-telegram-game-bot.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/ptkdev/quizquickanswer-telegram-game-bot/issues" 22 | }, 23 | "engines": { 24 | "node": ">=14.0.0" 25 | }, 26 | "scripts": { 27 | "setup": "ts-node --skip-project scripts/setup.ts && ts-node --skip-project scripts/changelog.ts && npm run pre-commit", 28 | "start": "npm run git-hash && node dist/core/bot.js", 29 | "start-pm2": "pm2 start .pm2-process.json", 30 | "stop-pm2": "pm2 stop .pm2-process.json", 31 | "dev": "npm run git-hash && ts-node scripts/configs.ts && ts-node scripts/rmdist.ts && ts-node scripts/debug.ts --enable && nodemon --exec ts-node app/core/bot.ts", 32 | "build": "ts-node scripts/configs.ts && ts-node scripts/rmdist.ts && ts-node scripts/debug.ts --disable && tsc", 33 | "release": "npm run git-hash && npm run build && tsc --declaration --emitDeclarationOnly", 34 | "test": "jest app", 35 | "migrate": "ts-node scripts/migration.ts", 36 | "mongodump": "mongodump -d quizquickanswerdb -o .", 37 | "mongorestore": "mongorestore -d quizquickanswerdb quizquickanswerdb", 38 | "docs": "git submodule update --recursive && markserv ./README.md", 39 | "lint": "npm run lint-prettify && eslint . --cache --ext .ts,.js", 40 | "lint-fix": "npm run lint-prettify && eslint . --cache --ext .ts,.js --fix", 41 | "lint-prettify": "prettier --write --ignore-unknown \"**/*.{md,json,ts,js}\"", 42 | "git-set-upstream": "git remote add upstream git@github.com:ptkdev/quizquickanswer-telegram-game-bot.git && git fetch upstream", 43 | "git-pull-upstream": "git pull upstream main && git pull upstream beta && git pull upstream nightly", 44 | "git-pull": "git pull --recursive", 45 | "git-hash": "ts-node scripts/githash.ts", 46 | "git-ignore-reset": "git rm -r --cached . && git add . && git commit -m \"[Fix] Removing all files in .gitignore\"", 47 | "github-workflow-next-version": "ts-node scripts/version.ts", 48 | "github-workflow-changelog": "ts-node scripts/changelog_release.ts", 49 | "npm-publish-main": "git checkout main && npm publish", 50 | "npm-publish-beta": "git checkout beta && npm publish --tag beta", 51 | "npm-publish-nightly": "git checkout nightly && npm publish --tag nightly", 52 | "contributors-generate": "all-contributors generate", 53 | "all-shields-generate": "all-shields-generate", 54 | "pre-commit": "npm run contributors-generate && npm run all-shields-generate && npm run lint-fix && npm run test", 55 | "pkg-clean": "rm -rf node_modules package-lock.json && npm install && npm install mongoose@5 && npm install eslint@7 @types/yargs@16 --save-dev && husky install", 56 | "pkg-update": "npm update", 57 | "pkg-upgrade": "npx --yes npm-check-updates -u && npm install && npm install mongoose@5 && npm install eslint@7 @types/yargs@16 --save-dev && husky install", 58 | "prepare": "ts-patch install -s && husky install" 59 | }, 60 | "dependencies": { 61 | "@ptkdev/logger": "^1.8.0", 62 | "cron": "^2.3.1", 63 | "dotenv": "^16.3.1", 64 | "grammy": "^1.17.2", 65 | "mongoose": "^5.13.19", 66 | "pm2": "^5.3.0" 67 | }, 68 | "devDependencies": { 69 | "@ptkdev/all-shields-cli": "^2.0.2", 70 | "@types/jest": "^29.5.2", 71 | "@types/node": "^20.4.1", 72 | "@types/shelljs": "^0.8.12", 73 | "@types/yargs": "^16.0.5", 74 | "@typescript-eslint/eslint-plugin": "^5.61.0", 75 | "@typescript-eslint/parser": "^5.61.0", 76 | "all-contributors-cli": "^6.26.1", 77 | "eslint": "^7.32.0", 78 | "eslint-plugin-jest": "^27.2.2", 79 | "eslint-plugin-jsdoc": "^46.4.3", 80 | "husky": "^8.0.3", 81 | "jest": "^29.6.1", 82 | "json": "^11.0.0", 83 | "markserv": "^1.17.4", 84 | "nodemon": "^3.0.1", 85 | "prettier": "^3.0.0", 86 | "shelljs": "^0.8.5", 87 | "ts-jest": "^29.1.1", 88 | "ts-node": "^10.9.1", 89 | "ts-patch": "^3.0.1", 90 | "typescript": "^5.1.6", 91 | "typescript-transform-paths": "^3.4.6", 92 | "yargs": "^17.7.2" 93 | }, 94 | "keywords": [ 95 | "ptkdev", 96 | "telegram", 97 | "telegram groups", 98 | "friends", 99 | "telegram bot", 100 | "bot", 101 | "quiz game", 102 | "quiz telegram", 103 | "game telegram", 104 | "game bot telegram" 105 | ], 106 | "contributors": [ 107 | { 108 | "name": "Ali Shadman", 109 | "url": "https://github.com/AliShadman95" 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /app/functions/commands/hearsphoto.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Telegraf Hears 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import bot from "@app/core/token"; 12 | import translate from "@translations/translate"; 13 | import db from "@routes/api/database"; 14 | import telegram from "@routes/api/telegram"; 15 | import logger from "@app/functions/utils/logger"; 16 | 17 | import type { MasterInterface } from "@app/types/master.interfaces"; 18 | 19 | /** 20 | * hearsPhoto: any photo from bot chat 21 | * ===================== 22 | * Listen any photo user send 23 | * 24 | */ 25 | const hearsPhoto = async (): Promise => { 26 | bot.on("message:photo", async (ctx) => { 27 | logger.info("hears: photo", "hears.ts:on(photo)"); 28 | const lang = await telegram.api.message.getLanguage(ctx); 29 | 30 | if (telegram.api.message.getChatID(ctx) > 0) { 31 | // is chat with bot 32 | const master: MasterInterface = await db.master.get({ 33 | username: telegram.api.message.getUsername(ctx), 34 | }); 35 | 36 | const username = telegram.api.message.getUsername(ctx); 37 | 38 | if (master.username === username) { 39 | return; 40 | } 41 | 42 | const photo_id = telegram.api.message.getPhotoFileID(ctx); 43 | 44 | if (master?.username === telegram.api.message.getUsername(ctx)) { 45 | const text = telegram.api.message.getText(ctx).split("##"); 46 | if (text !== undefined) { 47 | const json = telegram.api.message.getFullUser(ctx); 48 | json.question = text[0]?.trim()?.toLowerCase() || ""; 49 | json.description = text[1]?.trim() || ""; 50 | json.group_id = master?.group_id || 0; 51 | json.message_thread_id = master?.message_thread_id; 52 | 53 | if (json.question === undefined || json.question === "") { 54 | await telegram.api.message.send( 55 | ctx, 56 | telegram.api.message.getChatID(ctx), 57 | translate(lang.language, "hears_missing_question"), 58 | ); 59 | } else if (json.description === undefined || json.description === "") { 60 | await telegram.api.message.send( 61 | ctx, 62 | telegram.api.message.getChatID(ctx), 63 | translate(lang.language, "hears_missing_tip"), 64 | ); 65 | } else { 66 | if (master?.win_message_id > 0) { 67 | await telegram.api.message.removeMessageMarkup(master?.group_id, master?.win_message_id); 68 | } 69 | 70 | if (master?.pin_id > 0) { 71 | await telegram.api.message.unpin(ctx, master?.group_id, master?.pin_id); 72 | } 73 | 74 | await db.master.update({ username: telegram.api.message.getUsername(ctx) }, json); 75 | 76 | const master_in_multi_groups = await db.master.getMultiple({ 77 | username: telegram.api.message.getUsername(ctx), 78 | }); 79 | 80 | master_in_multi_groups.forEach(async (master_in_group) => { 81 | const quiz = await telegram.api.message.sendPhoto( 82 | ctx, 83 | master_in_group?.group_id, 84 | photo_id, 85 | { 86 | caption: `⏱ ${json.description || ""}`, 87 | 88 | message_thread_id: master_in_group.message_thread_id, 89 | }, 90 | ); 91 | 92 | await telegram.api.message.send( 93 | ctx, 94 | telegram.api.message.getChatID(ctx), 95 | translate(lang.language, "hears_question_success"), 96 | ); 97 | 98 | if (quiz) { 99 | await telegram.api.message.pin(ctx, master_in_group?.group_id, quiz?.message_id, { 100 | disable_notification: true, 101 | message_thread_id: master_in_group.message_thread_id, 102 | }); 103 | 104 | master_in_group.pin_id = quiz?.message_id || 0; 105 | await db.master.update( 106 | { username: telegram.api.message.getUsername(ctx) }, 107 | master_in_group, 108 | ); 109 | } else { 110 | await db.master.remove({ 111 | group_id: master_in_group?.group_id, 112 | }); 113 | 114 | if (master?.win_message_id > 0) { 115 | await telegram.api.message.removeMessageMarkup( 116 | master?.group_id, 117 | master?.win_message_id, 118 | ); 119 | } 120 | 121 | await telegram.api.message.unpin( 122 | ctx, 123 | master_in_group?.group_id, 124 | master_in_group?.pin_id, 125 | ); 126 | 127 | await telegram.api.message.removeMessageMarkup( 128 | master_in_group?.group_id, 129 | master_in_group?.pin_id, 130 | ); 131 | } 132 | }); 133 | } 134 | } else { 135 | await telegram.api.message.send( 136 | ctx, 137 | telegram.api.message.getChatID(ctx), 138 | translate(lang.language, "hears_missing_photo_caption"), 139 | ); 140 | } 141 | } else { 142 | await telegram.api.message.send( 143 | ctx, 144 | telegram.api.message.getChatID(ctx), 145 | translate(lang.language, "hears_not_you_master"), 146 | ); 147 | } 148 | } 149 | }); 150 | }; 151 | 152 | export { hearsPhoto }; 153 | export default hearsPhoto; 154 | -------------------------------------------------------------------------------- /app/types/question.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Question Interfaces 3 | * ===================== 4 | * 5 | * Funny quiz game for telegram groups 6 | * 7 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 8 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 9 | * 10 | * @license: MIT License 11 | * 12 | */ 13 | import type { VotersInterface } from "@app/types/voters.interfaces"; 14 | 15 | /** 16 | * Question Interface 17 | * ===================== 18 | * 19 | * 20 | * @interface [QuestionInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 21 | * 22 | * @param { string } user_id - user id from telegram 23 | * @param { number } upvotes - user good questions count 24 | * @param { number } downvotes - user bad questions count 25 | * @param { string } group_id - user group id 26 | * @param { string } error - error message; 27 | * @param { VotersInterface } message - users that have voted on the current question; 28 | * 29 | */ 30 | export interface QuestionsInterface { 31 | /** 32 | * Questions Interface 33 | * ===================== 34 | * 35 | * @interface [QuestionsInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 36 | * 37 | * @param { string } user_id - user id from telegram 38 | * 39 | */ 40 | user_id: number | string; 41 | /** 42 | * Questions Interface 43 | * ===================== 44 | * 45 | * @interface [QuestionsUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 46 | * 47 | */ 48 | upvotes_2021?: number; 49 | upvotes_2022?: number; 50 | upvotes_2023?: number; 51 | upvotes_1_2023?: number; 52 | upvotes_2_2023?: number; 53 | upvotes_3_2023?: number; 54 | upvotes_4_2023?: number; 55 | upvotes_5_2023?: number; 56 | upvotes_6_2023?: number; 57 | upvotes_7_2023?: number; 58 | upvotes_8_2023?: number; 59 | upvotes_9_2023?: number; 60 | upvotes_10_2023?: number; 61 | upvotes_11_2023?: number; 62 | upvotes_12_2023?: number; 63 | upvotes_2024?: number; 64 | upvotes_1_2024?: number; 65 | upvotes_2_2024?: number; 66 | upvotes_3_2024?: number; 67 | upvotes_4_2024?: number; 68 | upvotes_5_2024?: number; 69 | upvotes_6_2024?: number; 70 | upvotes_7_2024?: number; 71 | upvotes_8_2024?: number; 72 | upvotes_9_2024?: number; 73 | upvotes_10_2024?: number; 74 | upvotes_11_2024?: number; 75 | upvotes_12_2024?: number; 76 | upvotes_2025?: number; 77 | upvotes_1_2025?: number; 78 | upvotes_2_2025?: number; 79 | upvotes_3_2025?: number; 80 | upvotes_4_2025?: number; 81 | upvotes_5_2025?: number; 82 | upvotes_6_2025?: number; 83 | upvotes_7_2025?: number; 84 | upvotes_8_2025?: number; 85 | upvotes_9_2025?: number; 86 | upvotes_10_2025?: number; 87 | upvotes_11_2025?: number; 88 | upvotes_12_2025?: number; 89 | /** 90 | * Questions Interface 91 | * ===================== 92 | * 93 | * @interface [QuestionsUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 94 | * 95 | * 96 | */ 97 | downvotes_2021?: number; 98 | downvotes_2022?: number; 99 | downvotes_2023?: number; 100 | downvotes_1_2023?: number; 101 | downvotes_2_2023?: number; 102 | downvotes_3_2023?: number; 103 | downvotes_4_2023?: number; 104 | downvotes_5_2023?: number; 105 | downvotes_6_2023?: number; 106 | downvotes_7_2023?: number; 107 | downvotes_8_2023?: number; 108 | downvotes_9_2023?: number; 109 | downvotes_10_2023?: number; 110 | downvotes_11_2023?: number; 111 | downvotes_12_2023?: number; 112 | downvotes_2024?: number; 113 | downvotes_1_2024?: number; 114 | downvotes_2_2024?: number; 115 | downvotes_3_2024?: number; 116 | downvotes_4_2024?: number; 117 | downvotes_5_2024?: number; 118 | downvotes_6_2024?: number; 119 | downvotes_7_2024?: number; 120 | downvotes_8_2024?: number; 121 | downvotes_9_2024?: number; 122 | downvotes_10_2024?: number; 123 | downvotes_11_2024?: number; 124 | downvotes_12_2024?: number; 125 | downvotes_2025?: number; 126 | downvotes_1_2025?: number; 127 | downvotes_2_2025?: number; 128 | downvotes_3_2025?: number; 129 | downvotes_4_2025?: number; 130 | downvotes_5_2025?: number; 131 | downvotes_6_2025?: number; 132 | downvotes_7_2025?: number; 133 | downvotes_8_2025?: number; 134 | downvotes_9_2025?: number; 135 | downvotes_10_2025?: number; 136 | downvotes_11_2025?: number; 137 | downvotes_12_2025?: number; 138 | /** 139 | * Questions Interface 140 | * ===================== 141 | * 142 | * @interface [QuestionsUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 143 | * 144 | * @param { string } group_id - user group id 145 | * 146 | */ 147 | group_id: number; 148 | /** 149 | * Questions Interface 150 | * ===================== 151 | * 152 | * @interface [QuestionsUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 153 | * 154 | * @param { string } error - error message 155 | * 156 | */ 157 | error?: string; 158 | /** 159 | * Questions Interface 160 | * ===================== 161 | * 162 | * @interface [QuestionsUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 163 | * 164 | * @param { VotersInterface } voters - users that have voted on the current question; 165 | * 166 | */ 167 | voters: VotersInterface; 168 | } 169 | -------------------------------------------------------------------------------- /app/functions/api/database/scores.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Scores database 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { Schema, model } from "mongoose"; 12 | import { logger } from "@app/functions/utils/logger"; 13 | 14 | import type { MasterInterface } from "@app/types/master.interfaces"; 15 | 16 | const schema = new Schema({ 17 | id: { type: String, default: "0" }, 18 | is_bot: { type: Boolean, default: false }, 19 | first_name: { type: String, default: "" }, 20 | username: { type: String, default: "" }, 21 | language_code: { type: String, default: "en" }, 22 | group_id: { type: Number, default: 0 }, 23 | score_2021: { type: Number, default: 0 }, 24 | score_2022: { type: Number, default: 0 }, 25 | score_2023: { type: Number, default: 0 }, 26 | score_1_2023: { type: Number, default: 0 }, 27 | score_2_2023: { type: Number, default: 0 }, 28 | score_3_2023: { type: Number, default: 0 }, 29 | score_4_2023: { type: Number, default: 0 }, 30 | score_5_2023: { type: Number, default: 0 }, 31 | score_6_2023: { type: Number, default: 0 }, 32 | score_7_2023: { type: Number, default: 0 }, 33 | score_8_2023: { type: Number, default: 0 }, 34 | score_9_2023: { type: Number, default: 0 }, 35 | score_10_2023: { type: Number, default: 0 }, 36 | score_11_2023: { type: Number, default: 0 }, 37 | score_12_2023: { type: Number, default: 0 }, 38 | score_2024: { type: Number, default: 0 }, 39 | score_1_2024: { type: Number, default: 0 }, 40 | score_2_2024: { type: Number, default: 0 }, 41 | score_3_2024: { type: Number, default: 0 }, 42 | score_4_2024: { type: Number, default: 0 }, 43 | score_5_2024: { type: Number, default: 0 }, 44 | score_6_2024: { type: Number, default: 0 }, 45 | score_7_2024: { type: Number, default: 0 }, 46 | score_8_2024: { type: Number, default: 0 }, 47 | score_9_2024: { type: Number, default: 0 }, 48 | score_10_2024: { type: Number, default: 0 }, 49 | score_11_2024: { type: Number, default: 0 }, 50 | score_12_2024: { type: Number, default: 0 }, 51 | score_2025: { type: Number, default: 0 }, 52 | score_1_2025: { type: Number, default: 0 }, 53 | score_2_2025: { type: Number, default: 0 }, 54 | score_3_2025: { type: Number, default: 0 }, 55 | score_4_2025: { type: Number, default: 0 }, 56 | score_5_2025: { type: Number, default: 0 }, 57 | score_6_2025: { type: Number, default: 0 }, 58 | score_7_2025: { type: Number, default: 0 }, 59 | score_8_2025: { type: Number, default: 0 }, 60 | score_9_2025: { type: Number, default: 0 }, 61 | score_10_2025: { type: Number, default: 0 }, 62 | score_11_2025: { type: Number, default: 0 }, 63 | score_12_2025: { type: Number, default: 0 }, 64 | }); 65 | 66 | const query = model("Scores", schema, "scores"); 67 | 68 | /** 69 | * Scores CRUD 70 | * ===================== 71 | * Add score to DB 72 | * 73 | * @param {MasterInterface} user - user with score to add 74 | */ 75 | const add = async (user: MasterInterface): Promise => { 76 | try { 77 | const doc = new query(user); 78 | await doc.save(); 79 | } catch (error: unknown) { 80 | logger.error(JSON.stringify(error || ""), "scores.ts:add()"); 81 | } 82 | }; 83 | 84 | /** 85 | * Scores CRUD 86 | * ===================== 87 | * Remove score from DB 88 | * 89 | * @param {Record} search - search condition e.g {id:"123"} 90 | */ 91 | const remove = async (search: Record): Promise => { 92 | try { 93 | query.findOneAndDelete(search); 94 | } catch (error: unknown) { 95 | logger.error(JSON.stringify(error || ""), "scores.ts:remove()"); 96 | } 97 | }; 98 | 99 | /** 100 | * Scores CRUD 101 | * ===================== 102 | * Update score from DB 103 | * 104 | * @param {Record} search - search condition e.g {id:"123"} 105 | * @param {MasterInterface} user - user info with score to update 106 | */ 107 | const update = async (search: Record, user: MasterInterface): Promise => { 108 | try { 109 | await query.findOneAndUpdate(search, user); 110 | } catch (error: unknown) { 111 | logger.error(JSON.stringify(error || ""), "scores.ts:update()"); 112 | } 113 | }; 114 | 115 | /** 116 | * Scores CRUD 117 | * ===================== 118 | * Get user with score from DB 119 | * 120 | * @param {Record} search - search condition e.g {id:"123"} 121 | * @return {MasterInterface} user. 122 | 123 | */ 124 | const get = async (search: Record): Promise => { 125 | try { 126 | const user = await query.findOne(search); 127 | 128 | return (await user) || new query().toJSON(); 129 | } catch (error: unknown) { 130 | logger.error(JSON.stringify(error || ""), "scores.ts:get()"); 131 | } 132 | 133 | return new query().toJSON(); 134 | }; 135 | 136 | /** 137 | * Scores CRUD 138 | * ===================== 139 | * Get multiple user with score from DB 140 | * 141 | * @param {Record} search - search condition e.g {id:"123"} 142 | * @return {MasterInterface[]} user. 143 | 144 | */ 145 | const getMultiple = async (search: Record): Promise => { 146 | try { 147 | const user = await query.find(search); 148 | return user || []; 149 | } catch (error: unknown) { 150 | logger.error(JSON.stringify(error || ""), "scores.ts:getMultiple()"); 151 | } 152 | 153 | return []; 154 | }; 155 | 156 | export { getMultiple, get, update, remove, add }; 157 | export default { getMultiple, get, update, remove, add }; 158 | -------------------------------------------------------------------------------- /app/functions/commands/master.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Master 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * Alessandro Di Maria [@ImAl3x03] (https://github.com/ImAl3x03) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import bot from "@app/core/token"; 13 | import translate from "@translations/translate"; 14 | import db from "@routes/api/database"; 15 | import telegram from "@routes/api/telegram"; 16 | import logger from "@app/functions/utils/logger"; 17 | 18 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 19 | import type { MasterInterface } from "@app/types/master.interfaces"; 20 | 21 | /** 22 | * command: /master 23 | * ===================== 24 | * Set master game 25 | * 26 | */ 27 | const master = async (): Promise => { 28 | bot.command("master", async (ctx) => { 29 | logger.info("command: /master", "master.ts:master()"); 30 | const lang = await telegram.api.message.getLanguage(ctx); 31 | 32 | const username = telegram.api.message.getText(ctx).replace("/master ", "").replace("@", "").trim(); 33 | 34 | if (telegram.api.message.getChatID(ctx) < 0) { 35 | // is group chat 36 | 37 | if ( 38 | telegram.api.message.getText(ctx).trim() === "/master" || 39 | telegram.api.message.getText(ctx).trim() === `/master@${telegram.api.bot.getUsername(ctx)}` 40 | ) { 41 | await telegram.api.message.send( 42 | ctx, 43 | telegram.api.message.getChatID(ctx), 44 | translate(lang.language, "master_command_empty"), 45 | ); 46 | } else if (username === "off") { 47 | await telegram.api.message.send( 48 | ctx, 49 | telegram.api.message.getChatID(ctx), 50 | translate(lang.language, "master_off"), 51 | ); 52 | } else { 53 | let master: MasterInterface = await db.master.get({ 54 | group_id: telegram.api.message.getChatID(ctx), 55 | }); 56 | 57 | if (master.off) { 58 | return; 59 | } 60 | 61 | const json = { 62 | id: "0", 63 | is_bot: false, 64 | first_name: "", 65 | username: username, 66 | language_code: "", 67 | question: "", 68 | description: "", 69 | score_2021: 0, 70 | score_2022: 0, 71 | score_2023: 0, 72 | score_1_2023: 0, 73 | score_2_2023: 0, 74 | score_3_2023: 0, 75 | score_4_2023: 0, 76 | score_5_2023: 0, 77 | score_6_2023: 0, 78 | score_7_2023: 0, 79 | score_8_2023: 0, 80 | score_9_2023: 0, 81 | score_10_2023: 0, 82 | score_11_2023: 0, 83 | score_12_2023: 0, 84 | score_2024: 0, 85 | score_1_2024: 0, 86 | score_2_2024: 0, 87 | score_3_2024: 0, 88 | score_4_2024: 0, 89 | score_5_2024: 0, 90 | score_6_2024: 0, 91 | score_7_2024: 0, 92 | score_8_2024: 0, 93 | score_9_2024: 0, 94 | score_10_2024: 0, 95 | score_11_2024: 0, 96 | score_12_2024: 0, 97 | score_2025: 0, 98 | score_1_2025: 0, 99 | score_2_2025: 0, 100 | score_3_2025: 0, 101 | score_4_2025: 0, 102 | score_5_2025: 0, 103 | score_6_2025: 0, 104 | score_7_2025: 0, 105 | score_8_2025: 0, 106 | score_9_2025: 0, 107 | score_10_2025: 0, 108 | score_11_2025: 0, 109 | score_12_2025: 0, 110 | pin_id: 0, 111 | win_message_id: 0, 112 | group_id: telegram.api.message.getChatID(ctx), 113 | message_thread_id: telegram.api.message.getThreadID(ctx), 114 | off: master.off, 115 | timezone: master.timezone, 116 | }; 117 | 118 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 119 | 120 | master = await db.master.get({ 121 | group_id: telegram.api.message.getChatID(ctx), 122 | }); 123 | 124 | if (master?.win_message_id > 0) { 125 | await telegram.api.message.removeMessageMarkup(master?.group_id, master?.win_message_id); 126 | } 127 | 128 | if (master?.pin_id > 0) { 129 | await telegram.api.message.unpin(ctx, master?.group_id, master?.pin_id); 130 | } 131 | 132 | if (master.username === telegram.api.message.getUsername(ctx)) { 133 | const user_questions: QuestionsInterface = await db.questions.get({ 134 | group_id: telegram.api.message.getChatID(ctx), 135 | user_id: telegram.api.message.getUserID(ctx), 136 | }); 137 | 138 | user_questions[`downvotes_${new Date().getFullYear()}`] += 15; 139 | user_questions[`downvotes_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] += 15; 140 | 141 | await db.questions.update( 142 | { group_id: telegram.api.message.getChatID(ctx), user_id: telegram.api.message.getUserID(ctx) }, 143 | user_questions, 144 | ); 145 | 146 | await telegram.api.message.send( 147 | ctx, 148 | telegram.api.message.getChatID(ctx), 149 | translate(lang.language, "master_command_penality", { 150 | username: master.username, 151 | bot_username: telegram.api.bot.getUsername(ctx), 152 | }), 153 | ); 154 | } 155 | 156 | const memberId = Number(telegram.api.message.getUserID(ctx)); 157 | 158 | const { user, status } = await ctx.getChatMember(memberId); 159 | 160 | logger.debug(`master:${JSON.stringify(master)}`); 161 | 162 | // Just checking if the user whose lauunching the command is an admin of the group or the current master 163 | if (status !== "creator" && status !== "administrator" && user?.username !== master?.username) { 164 | logger.error(`${user.username} wasn't authorized to change the master`); 165 | 166 | await telegram.api.message.send( 167 | ctx, 168 | telegram.api.message.getChatID(ctx), 169 | translate(lang.language, "not_authorized", { 170 | username: user.username, 171 | }), 172 | ); 173 | 174 | return; 175 | } 176 | 177 | if (master.group_id < 0) { 178 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 179 | } else { 180 | await db.master.add(json); 181 | } 182 | await telegram.api.message.send( 183 | ctx, 184 | telegram.api.message.getChatID(ctx), 185 | translate(lang.language, "master_command_success", { 186 | username: username, 187 | bot_username: telegram.api.bot.getUsername(ctx), 188 | }), 189 | ); 190 | } 191 | } else { 192 | await telegram.api.message.send( 193 | ctx, 194 | telegram.api.message.getChatID(ctx), 195 | translate(lang.language, "command_only_group"), 196 | ); 197 | } 198 | }); 199 | }; 200 | 201 | export { master }; 202 | export default master; 203 | -------------------------------------------------------------------------------- /app/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributions": { 3 | "contributors": ["Patryk Rzucidło [@ptkdev] (https://ptk.dev)"], 4 | "language": "Italian", 5 | "license": "CC BY 4.0" 6 | }, 7 | "app_name": "Quiz Quick Answer Bot", 8 | "command_only_group": "Puoi usare questo comando solo in un gruppo telegram!", 9 | "goodquestion_not_autovote": "⛔️ Mi dispiace, non puoi votarti da solo!", 10 | "hears_not_you_master": "📵 Non sei tu il master al momento, se è un errore puoi usare:\n\n/master @TUO_NICKNAME", 11 | "hears_missing_question": "🤬 Devi mettere prima la parola che devono indovinare, due HASHTAG, e poi il suggerimento. Se stai mandando una foto inseriscila nella didascalia. Tutto in un unico messaggio, Esempio:\n\ngameboy ## console anni 90\n\ngameboy è la parola che devono indovinare, dopo i due HASHTAG è il suggerimento che gli dai tu (console anni '90). Riprova!", 12 | "hears_missing_tip": "🤬 Hai dimenticato i due hashtag. Devi mettere prima la parola che devono indovinare, due HASHTAG, e poi il suggerimento. Se stai mandando una foto inseriscila nella didascalia. Tutto in un unico messaggio, Esempio:\n\ngameboy ## console anni '90\n\ngameboy è la parola che devono indovinare, dopo i due HASHTAG è il suggerimento che gli dai tu (console anni '90). Riprova!", 13 | "hears_missing_caption": "🤬 Hai dimenticato la caption. Devi mettere prima la parola che devono indovinare, due HASHTAG, e poi il suggerimento. Tutto in un unico messaggio, Esempio:\n\ngameboy ## console anni '90\n\ngameboy è la parola che devono indovinare, dopo i due HASHTAG è il suggerimento che gli dai tu (console anni '90). Riprova!", 14 | "hears_missing_photo_caption": "🤬 Hai dimenticato la didascalia quando hai inviato la foto. Devi mettere come didascalia prima la parola che devono indovinare, due HASHTAG, e poi il suggerimento. Tutto in un unico messaggio, Esempio:\n\ngameboy ## console anni '90\n\ngameboy è la parola che devono indovinare, dopo i due HASHTAG è il suggerimento che gli dai tu (console anni '90). Riprova!", 15 | "hears_win": "🏆 HAI VINTO {{first_name}} (@{{username}})!!!\n\n❓ La domanda era: {{tip}}\n✍️ La risposta giusta era: {{answer}}\n👑 Ora sei il nuovo master! ⚽️ Il tuo punteggio é {{score}} 🔥\n\nContatta in privato @{{bot_username}} (clicca sul nickname) e segui le istruzioni.\n\nTi è piaciuta la domanda di {{master}}? Votala:", 16 | "hears_win_but_not_master": "🏆 HAI VINTO {{first_name}}!! Ma non puoi diventare master perchè non hai impostato un username su telegram. Vai nelle impostazioni di telegram, entra su modifica in alto e imposta un @nickname! Il master è rimasto {{master_first_name}} (@{{master_username}}).", 17 | "master_command_empty": "Inserisci un nickname, ad esempio:\n\n/master @ptkdev", 18 | "master_command_success": "Ora sei diventato master @{{username}}!\n\nContatta in privato @{{bot_username}} (clicca sul nickname) e scrivigli la parola o frase che gli altri devono indovinare, a seguire sempre nello stesso messaggio, aggiungi due HASHTAG per dare un suggerimento, esempio: \n\ngameboy ## console anni '90", 19 | "score_command_show": "{{first_name}} (@{{username}}) il tuo punteggio in questo gruppo è di {{score}} punti!", 20 | "score_command_show_with_username": "Il punteggio di @{{username}} in questo gruppo è di {{score}} punti!", 21 | "start_command_group": "Prima di iniziare a giocare rendi questo bot amministratore. Successivamente diventa master lanciando il comando:\n\n/master @{{username}}", 22 | "start_command_private": "Scrivi la parola o frase che devono indovinare, due HASHTAG, e poi il suggerimento. Puoi anche inviare una foto e come didascalia usare la stessa regola. Tutto in un unico messaggio, esempio:\n\ngameboy ## console anni '90\n\ngameboy è la parola o frase che devono indovinare, dopo i due HASHTAG è il suggerimento che gli dai tu (console anni '90).\n\nCambia lingua usando: /settings", 23 | "top10_command_not_available": "Classifica non disponibile per questo gruppo!", 24 | "top10_command_list": "{{emoji}} {{first_name}} ({{username}}) - {{score}} punti\n\n", 25 | "hot_answer": "🔥 {{first_name}} (@{{username}}) - FUOCO! Hai quasi indovinato!\n\n", 26 | "admin_welcome": "Benvenuto nel pannello di amministrazione. Scegli una delle seguenti opzioni:\n\n", 27 | "action_send_messagge_all_groups": "Inviare messaggio a tutti i gruppi", 28 | "action_set_user_score": "Assegnare lo score di un utente", 29 | "admin_set_user_score_info_request": "Inserisci l'username, lo score e l'id del gruppo di un utente in un singolo messaggio separato da due HASHTAG Esempio:\n\nptkdev ## 100 ## 5032010\n\n", 30 | "settings_command_options": "⚙️ Le opzioni disponibili sono:", 31 | "settings_command_switchlanguage": "🌎 Seleziona la lingua del bot", 32 | "settings_command_setlanguage": "🌎 Cambia lingua", 33 | "settings_command_language_italian": "🇮🇹 Italiano", 34 | "settings_command_language_english": "🇬🇧 English", 35 | "settings_command_language_new": "🏁 Aiuta a tradurre", 36 | "settings_command_current_italian": "Ora il bot parlerà in: 🇮🇹 Italiano", 37 | "settings_command_current_english": "Ora il bot parlerà in: 🇬🇧 Inglese", 38 | "settings_command_opensource": "👨‍💻 OpenSource", 39 | "settings_command_email": "📨 Aiuto", 40 | "settings_command_credits": "🌟 Riconoscimenti", 41 | "settings_command_ptkdev": "👨‍💻 Patryk Rzucidlo (@PTKDev)", 42 | "settings_command_ali": "👨‍💻 Alì Shadman", 43 | "groups_command": "Se non hai gruppi telegram di amici puoi giocare in quello ufficiale, troverai nuovi amici e persone gentili:", 44 | "groups_button_official_english": "🇬🇧 Gruppo Ufficiale Inglese", 45 | "groups_button_official_italian": "🇮🇹 Gruppo Ufficiale Italiano", 46 | "ping_command": "🎮 Venite a giocare! ⏱ La partita sta per iniziare!", 47 | "show_command": "👨‍💻 Il master è: {{first_name}} (@{{username}})\n\n⏱ Il quiz è: {{answer}}", 48 | "show_command_noquiz": "👨‍💻 Il master è: {{first_name}} (@{{username}})\n\n⏱ Il quiz non c'è! Daje un po'! Sbrigati a scriverlo!", 49 | "top10_commands": "Le Top10 disponibili sono: \n\n/top10monthly\n\n/top2021\n\n/top2022\n\n/top2023", 50 | "master_off": "🌙🌙🌙 Notte Time 🌙🌙🌙\n\nSi gioca 9:00-00:00 domenica-giovedì, 9:00-01:00 venerdì e sabato.\n\nA domani!", 51 | "master_on": "🌞🌞🌞 Buongiornissimo 🌞🌞🌞\n\n⏱ Il quiz è: linguaggio di programmazione.", 52 | "master_command_penality": "🚨 @{{username}} hai ceduto il master, verrai punito con -15 punti.", 53 | "hears_question_success": "👍 Fatto, torna sul gruppo!", 54 | "not_authorized": "⛔️ @{{username}} non sei autorizzato a cambiare il master\n\nPer favore contatta un amministratore del gruppo" 55 | } 56 | -------------------------------------------------------------------------------- /app/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributions": { 3 | "language": "English", 4 | "license": "CC BY 4.0", 5 | "contributors": [ 6 | "Patryk Rzucidło [@ptkdev] (https://ptk.dev)", 7 | "Denise Scazzari [@DeniseKarina] (https://instagram.com/den_karina)" 8 | ] 9 | }, 10 | "app_name": "Quiz Quick Answer Bot", 11 | "command_only_group": "You can use this command only in a Telegram group!", 12 | "goodquestion_not_autovote": "⛔️ Sorry man, you can't vote for yourself!", 13 | "haers_not_you_master": "📵 You're not the master at the moment, if it's a mistake you can use:\n\n/master @nickname", 14 | "hears_missing_question": "🤬 Hey buddy! What are you writing? First of all, the WORD(S) to guess, then two HASHTAGS, finally the HINT. If you want to send also a picture, please insert it the caption. All in a single message. Exemple:\n\ngameboy ## old console '90s\n\ngameboy is the word to guess, then there is the two HASHTAS sign and finally the HINT (old console '90s). Don't be ashamed, try again!", 15 | "hears_missing_tip": "🤬 Hey buddy! You forgot the two HASHTAGS. First of all, the WORD(S) to guess, then two HASHTAGS, finally the HINT. If you want to send also a picture, please insert it the caption. All in a single message. Exemple:\n\ngameboy ## old console '90s\n\ngameboy is the word to guess, then there is the two HASHTAGS and finally the HINT (old console '90s). Don't be ashamed, try again!", 16 | "hears_missing_caption": "🤬 Hey buddy! You forgot the caption. First of all, the WORD(S) to guess, then two HASHTAGS, finally the HINT. If you want to send also a picture, please insert it the caption. All in a single message. Exemple:\n\ngameboy ## old console '90s\n\ngameboy is the word to guess, then there is the two HASHTAGS and finally the HINT (old console '90s). Don't be ashamed, try again!", 17 | "hears_missing_photo_caption": "🤬 Hey buddy! You forgot the caption when you inserted the picture. Write down the WORD(S) to guess as caption, then two HASHTAGS and finally the HINT. All in a single message. Exemple:\n\ngameboy ## old console '90s\n\ngameboy is the word to guess, then there is the two HASHTAGS and finally the HINT (old console '90s). Don't be ashamed, try again!!", 18 | "hears_win": "🏆 GREAT JOB {{first_name}} (@{{username}}) YOU WIN!!!\n\n❓ The question was: {{tip}}\n✍️ The correct answer was: {{answer}}\n👑 Now you are the new master! ⚽️ Your score is {{score}} 🔥\n\nTo take a quiz, write a private message to @{{bot_username}} (click on the nickname) and follow the instruction.\n\nDid you like the question of {{master}}? Rate it:", 19 | "hears_win_but_not_master": "🏆 YOU WIN {{first_name}}!! But you can't be master because you don't have a Telegram username. Please set a username: go to Telegram settings, click on edit and write your @nickname! {{master_first_name}} (@{{master_username}}) is still the master.", 20 | "master_command_empty": "Insert a nickname, for exemple:\n\n/master @ptkdev", 21 | "master_command_success": "Now you are the master @{{username}}!\n\nTo take a quiz, write a private message to @{{bot_username}} (click on the nickname) and write there the word or the sentence to guess, followed by two HASHTAGS and the hint, for exemple: \n\ngameboy ## old console '90s", 22 | "score_command_show": "{{first_name}} (@{{username}}) your score in this group is {{score}}!", 23 | "score_command_show_with_username": "@{{username}} 's score in this group is {{score}}!", 24 | "start_command_group": "Before starting to play set this bot as an admin of this group. Then make yourself the master by running the command:\n\n/master @{{username}}", 25 | "start_command_private": "Write down the world or the sentence to guess followed by two HASHTAGS and the hint. You can also send a picture and use the same scheme in the caption. All in a single message, for exemple: \n\ngameboy ## old console '90s\n\ngameboy is the word to guess, then there is the two HASHTAGS and finally the HINT (old console '90s).\n\nChange language on /settings", 26 | "top10_command_not_available": "Leaderboard not available for this group!", 27 | "top10_command_list": "{{emoji}} {{first_name}} ({{username}}) - {{score}} points\n\n", 28 | "hot_answer": "🔥 {{first_name}} (@{{username}}) - PRETTY CLOSE! You quite guess the answer!\n\n", 29 | "admin_welcome": "Welcome to the admin board. Choose the option:\n\n", 30 | "action_send_messagge_all_groups": "Send a message to all groups", 31 | "action_set_user_score": "Set a user's score", 32 | "admin_set_user_score_info_request": "Insert the username, the score and the id of the user in a single message separated by two HASHTAGS, for exemple:\n\nptkdev ## 100 ## 5032010\n\n", 33 | "settings_command_options": "⚙️ The available options are:", 34 | "settings_command_switchlanguage": "🌎 Choose the language of the bot", 35 | "settings_command_setlanguage": "🌎 Set language", 36 | "settings_command_language_italian": "🇮🇹 Italiano", 37 | "settings_command_language_english": "🇬🇧 English", 38 | "settings_command_language_new": "🏁 Helps translate", 39 | "settings_command_current_italian": "Now the bot speaks in: 🇮🇹 Italian", 40 | "settings_command_current_english": "Now the bot speaks in: 🇬🇧 English", 41 | "settings_command_opensource": "👨‍💻 OpenSource", 42 | "settings_command_email": "📨 Help", 43 | "settings_command_credits": "🌟 Credits", 44 | "settings_command_ptkdev": "👨‍💻 Patryk Rzucidlo (@PTKDev)", 45 | "settings_command_ali": "👨‍💻 Alì Shadman", 46 | "groups_command": "If you have no telegram groups of friends you can play in the official one, you will find new friends and kind people:", 47 | "groups_button_official_english": "🇬🇧 Official English group", 48 | "groups_button_official_italian": "🇮🇹 Official Italian group", 49 | "ping_command": "🎮 Come and play! ⏱ The game is about to start!", 50 | "show_command": "👨‍💻 The master is: {{first_name}} (@{{username}})\n\n⏱ The quiz is: {{answer}}", 51 | "show_command_noquiz": "👨‍💻 The master is: {{first_name}} (@{{username}})\n\n⏱ The quiz is not avaiable! Hurry up to write it!", 52 | "top10_commands": "The top10 available are: \n\n/top10monthly\n\n/top2021\n\n/top2022\n\n/top2023", 53 | "master_off": "🌙🌙🌙 Night mode 🌙🌙🌙\n\nWe play 9:00-12:00 Sunday-Thursday, 9:00-1:00 Friday and Saturday.\n\nSee you tomorrow!", 54 | "master_on": "🌞🌞🌞 Buongiornissimo 🌞🌞🌞\n\n⏱ Il quiz è: linguaggio di programmazione.", 55 | "master_command_penality": "🚨 @{{username}} you gave up the master, you will be punished with -15 points.", 56 | "hears_question_success": "👍 Done, come back to the group!", 57 | "not_authorized": "⛔️ @{{username}} you aren't allowed to change the master\n\nPlease contact a group administrator" 58 | } 59 | -------------------------------------------------------------------------------- /app/types/master.interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Master Interfaces 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * Alessandro Di Maria [@ImAl3x03] (https://github.com/ImAl3x03) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | 13 | /** 14 | * Master User Interface 15 | * ===================== 16 | * 17 | * 18 | * @interface [MasterUserInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 19 | * 20 | * @param { number } id - telegram id 21 | * @param { boolean } is_bot - is user a bot 22 | * @param { string } first_name - user name from telegram 23 | * @param { string } username - user username from telegram 24 | * @param { string } language_code - user code language from OS 25 | * @param { string } question - user submitted question 26 | * @param { string } description - user submitted question tip 27 | * @param { number } score - user current score 28 | * @param { number } group_id - users group id 29 | * @param { string } error - error message 30 | * 31 | */ 32 | export interface MasterInterface { 33 | /** 34 | * Master Interface 35 | * ===================== 36 | * 37 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 38 | * 39 | * @param { string } id - telegram id 40 | * 41 | */ 42 | id: number | string; 43 | /** 44 | * Master Interface 45 | * ===================== 46 | * 47 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 48 | * 49 | * @param { boolean } is_bot - is user a bot 50 | * 51 | */ 52 | is_bot?: boolean; 53 | /** 54 | * Master Interface 55 | * ===================== 56 | * 57 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 58 | * 59 | * @param { string } first_name - user name from telegram 60 | * 61 | */ 62 | first_name?: string; 63 | /** 64 | * Master Interface 65 | * ===================== 66 | * 67 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 68 | * 69 | * @param { string } username - user username from telegram 70 | * 71 | */ 72 | username?: string; 73 | /** 74 | * Master Interface 75 | * ===================== 76 | * 77 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 78 | * 79 | * @param { string } language_code - user code language from OS 80 | * 81 | */ 82 | language_code?: string; 83 | /** 84 | * Master Interface 85 | * ===================== 86 | * 87 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 88 | * 89 | * @param { string } question - user submitted question 90 | * 91 | */ 92 | question?: string; 93 | /** 94 | * Master Interface 95 | * ===================== 96 | * 97 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 98 | * 99 | * @param { string } description - user submitted question tip 100 | * 101 | */ 102 | description?: string; 103 | /** 104 | * Master Interface 105 | * ===================== 106 | * 107 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 108 | * 109 | * @param { number } score - user current score 110 | * 111 | */ 112 | score_2021: number; 113 | score_2022: number; 114 | score_2023: number; 115 | score_1_2023: number; 116 | score_2_2023: number; 117 | score_3_2023: number; 118 | score_4_2023: number; 119 | score_5_2023: number; 120 | score_6_2023: number; 121 | score_7_2023: number; 122 | score_8_2023: number; 123 | score_9_2023: number; 124 | score_10_2023: number; 125 | score_11_2023: number; 126 | score_12_2023: number; 127 | score_2024: number; 128 | score_1_2024: number; 129 | score_2_2024: number; 130 | score_3_2024: number; 131 | score_4_2024: number; 132 | score_5_2024: number; 133 | score_6_2024: number; 134 | score_7_2024: number; 135 | score_8_2024: number; 136 | score_9_2024: number; 137 | score_10_2024: number; 138 | score_11_2024: number; 139 | score_12_2024: number; 140 | score_2025: number; 141 | score_1_2025: number; 142 | score_2_2025: number; 143 | score_3_2025: number; 144 | score_4_2025: number; 145 | score_5_2025: number; 146 | score_6_2025: number; 147 | score_7_2025: number; 148 | score_8_2025: number; 149 | score_9_2025: number; 150 | score_10_2025: number; 151 | score_11_2025: number; 152 | score_12_2025: number; 153 | /** 154 | * Master Interface 155 | * ===================== 156 | * 157 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 158 | * 159 | * @param { number } pin_id - message pinned id 160 | * 161 | */ 162 | pin_id: number; 163 | /** 164 | * Master Interface 165 | * ===================== 166 | * 167 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 168 | * 169 | * @param { number } group_id - users group id 170 | * 171 | */ 172 | group_id: number; 173 | /** 174 | * Master Interface 175 | * ===================== 176 | * 177 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 178 | * 179 | * @param { number } win_message_id - id of win message 180 | * 181 | */ 182 | win_message_id: number; 183 | /** 184 | * Master Interface 185 | * ===================== 186 | * 187 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 188 | * 189 | * @param { number } message_thread_id - thread id if group is a topic 190 | * 191 | */ 192 | message_thread_id: number; 193 | /** 194 | * Master Interface 195 | * ===================== 196 | * 197 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 198 | * 199 | * @param { number } count - count answer 200 | * 201 | */ 202 | off: boolean; 203 | timezone: string; 204 | /** 205 | * Master Interface 206 | * ===================== 207 | * 208 | * @interface [MasterInterface](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/app/webcomponent/types/databases.interfaces.ts) 209 | * 210 | * @param { string } error - error message 211 | * 212 | */ 213 | error?: string; 214 | } 215 | -------------------------------------------------------------------------------- /app/functions/api/database/questions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Question database 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import { Schema, model } from "mongoose"; 12 | import { logger } from "@app/functions/utils/logger"; 13 | 14 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 15 | 16 | const schema = new Schema({ 17 | user_id: { type: String, default: 0 }, 18 | group_id: { type: Number, default: 0 }, 19 | upvotes_2021: { type: Number, default: 0 }, 20 | downvotes_2021: { type: Number, default: 0 }, 21 | upvotes_2022: { type: Number, default: 0 }, 22 | downvotes_2022: { type: Number, default: 0 }, 23 | upvotes_2023: { type: Number, default: 0 }, 24 | downvotes_2023: { type: Number, default: 0 }, 25 | upvotes_1_2023: { type: Number, default: 0 }, 26 | downvotes_1_2023: { type: Number, default: 0 }, 27 | upvotes_2_2023: { type: Number, default: 0 }, 28 | downvotes_2_2023: { type: Number, default: 0 }, 29 | upvotes_3_2023: { type: Number, default: 0 }, 30 | downvotes_3_2023: { type: Number, default: 0 }, 31 | upvotes_4_2023: { type: Number, default: 0 }, 32 | downvotes_4_2023: { type: Number, default: 0 }, 33 | upvotes_5_2023: { type: Number, default: 0 }, 34 | downvotes_5_2023: { type: Number, default: 0 }, 35 | upvotes_6_2023: { type: Number, default: 0 }, 36 | downvotes_6_2023: { type: Number, default: 0 }, 37 | upvotes_7_2023: { type: Number, default: 0 }, 38 | downvotes_7_2023: { type: Number, default: 0 }, 39 | upvotes_8_2023: { type: Number, default: 0 }, 40 | downvotes_8_2023: { type: Number, default: 0 }, 41 | upvotes_9_2023: { type: Number, default: 0 }, 42 | downvotes_9_2023: { type: Number, default: 0 }, 43 | upvotes_10_2023: { type: Number, default: 0 }, 44 | downvotes_10_2023: { type: Number, default: 0 }, 45 | upvotes_11_2023: { type: Number, default: 0 }, 46 | downvotes_11_2023: { type: Number, default: 0 }, 47 | upvotes_12_2023: { type: Number, default: 0 }, 48 | downvotes_12_2023: { type: Number, default: 0 }, 49 | upvotes_2024: { type: Number, default: 0 }, 50 | downvotes_2024: { type: Number, default: 0 }, 51 | upvotes_1_2024: { type: Number, default: 0 }, 52 | downvotes_1_2024: { type: Number, default: 0 }, 53 | upvotes_2_2024: { type: Number, default: 0 }, 54 | downvotes_2_2024: { type: Number, default: 0 }, 55 | upvotes_3_2024: { type: Number, default: 0 }, 56 | downvotes_3_2024: { type: Number, default: 0 }, 57 | upvotes_4_2024: { type: Number, default: 0 }, 58 | downvotes_4_2024: { type: Number, default: 0 }, 59 | upvotes_5_2024: { type: Number, default: 0 }, 60 | downvotes_5_2024: { type: Number, default: 0 }, 61 | upvotes_6_2024: { type: Number, default: 0 }, 62 | downvotes_6_2024: { type: Number, default: 0 }, 63 | upvotes_7_2024: { type: Number, default: 0 }, 64 | downvotes_7_2024: { type: Number, default: 0 }, 65 | upvotes_8_2024: { type: Number, default: 0 }, 66 | downvotes_8_2024: { type: Number, default: 0 }, 67 | upvotes_9_2024: { type: Number, default: 0 }, 68 | downvotes_9_2024: { type: Number, default: 0 }, 69 | upvotes_10_2024: { type: Number, default: 0 }, 70 | downvotes_10_2024: { type: Number, default: 0 }, 71 | upvotes_11_2024: { type: Number, default: 0 }, 72 | downvotes_11_2024: { type: Number, default: 0 }, 73 | upvotes_12_2024: { type: Number, default: 0 }, 74 | downvotes_12_2024: { type: Number, default: 0 }, 75 | upvotes_2025: { type: Number, default: 0 }, 76 | downvotes_2025: { type: Number, default: 0 }, 77 | upvotes_1_2025: { type: Number, default: 0 }, 78 | downvotes_1_2025: { type: Number, default: 0 }, 79 | upvotes_2_2025: { type: Number, default: 0 }, 80 | downvotes_2_2025: { type: Number, default: 0 }, 81 | upvotes_3_2025: { type: Number, default: 0 }, 82 | downvotes_3_2025: { type: Number, default: 0 }, 83 | upvotes_4_2025: { type: Number, default: 0 }, 84 | downvotes_4_2025: { type: Number, default: 0 }, 85 | upvotes_5_2025: { type: Number, default: 0 }, 86 | downvotes_5_2025: { type: Number, default: 0 }, 87 | upvotes_6_2025: { type: Number, default: 0 }, 88 | downvotes_6_2025: { type: Number, default: 0 }, 89 | upvotes_7_2025: { type: Number, default: 0 }, 90 | downvotes_7_2025: { type: Number, default: 0 }, 91 | upvotes_8_2025: { type: Number, default: 0 }, 92 | downvotes_8_2025: { type: Number, default: 0 }, 93 | upvotes_9_2025: { type: Number, default: 0 }, 94 | downvotes_9_2025: { type: Number, default: 0 }, 95 | upvotes_10_2025: { type: Number, default: 0 }, 96 | downvotes_10_2025: { type: Number, default: 0 }, 97 | upvotes_11_2025: { type: Number, default: 0 }, 98 | downvotes_11_2025: { type: Number, default: 0 }, 99 | upvotes_12_2025: { type: Number, default: 0 }, 100 | downvotes_12_2025: { type: Number, default: 0 }, 101 | voters: { type: Object, default: { message_id: 0, users: { upvotes: [], downvotes: [] } } }, 102 | }); 103 | 104 | const query = model("Questions", schema, "questions"); 105 | 106 | /** 107 | * Questions CRUD 108 | * ===================== 109 | * Add question to DB 110 | * 111 | * @param {QuestionsInterface} user - user with questions to add 112 | */ 113 | const add = async (user: QuestionsInterface): Promise => { 114 | try { 115 | const doc = new query(user); 116 | await doc.save(); 117 | } catch (error: unknown) { 118 | logger.error(JSON.stringify(error || ""), "question.ts:add()"); 119 | } 120 | }; 121 | 122 | /** 123 | * Questions CRUD 124 | * ===================== 125 | * Remove question from DB 126 | * 127 | * @param {Record} search - search condition e.g {id:"123"} 128 | */ 129 | const remove = async (search: Record): Promise => { 130 | try { 131 | await query.findOneAndDelete(search); 132 | } catch (error: unknown) { 133 | logger.error(JSON.stringify(error || ""), "question.ts:remove()"); 134 | } 135 | }; 136 | 137 | /** 138 | * Questions CRUD 139 | * ===================== 140 | * Update questions from DB 141 | * 142 | * @param {Record} search - search condition e.g {id:"123"} 143 | * @param {QuestionsInterface} user - user info with questions to update 144 | */ 145 | const update = async (search: Record, user: QuestionsInterface): Promise => { 146 | try { 147 | await query.findOneAndUpdate(search, user); 148 | } catch (error: unknown) { 149 | logger.error(JSON.stringify(error || ""), "question.ts:update()"); 150 | } 151 | }; 152 | 153 | /** 154 | * Questions CRUD 155 | * ===================== 156 | * Get user with questions from DB 157 | * 158 | * @param {Record} search - search condition e.g {id:"123"} 159 | * @return {MasterInterface} user. 160 | * 161 | */ 162 | const get = async (search: Record): Promise => { 163 | try { 164 | const user = await query.findOne(search); 165 | 166 | return (await user) || new query().toJSON(); 167 | } catch (error: unknown) { 168 | logger.error(JSON.stringify(error || ""), "question.ts:get()"); 169 | } 170 | 171 | return new query().toJSON(); 172 | }; 173 | 174 | export { get, update, remove, add }; 175 | export default { get, update, remove, add }; 176 | -------------------------------------------------------------------------------- /app/functions/api/telegram/message.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper telegram api (message) 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * 8 | * @license: MIT License 9 | * 10 | */ 11 | import logger from "@app/functions/utils/logger"; 12 | import bot from "@app/core/token"; 13 | import db from "@routes/api/database"; 14 | import type { Context, RawApi } from "grammy"; 15 | import type { MasterInterface } from "@app/types/master.interfaces"; 16 | import type { SettingsInterface } from "@app/types/settings.interfaces"; 17 | import { Other } from "grammy/out/core/api"; 18 | 19 | const getUsername = (ctx: Context): string => { 20 | const username = ctx?.update?.message?.from?.username; 21 | 22 | return username?.trim() || ""; 23 | }; 24 | 25 | const getUsernameFromAction = (ctx: Context): string => { 26 | const username = ctx?.update?.callback_query?.from?.username; 27 | 28 | return username?.trim() || ""; 29 | }; 30 | const getUserIDFromAction = (ctx: Context): string => { 31 | const id = ctx?.update?.callback_query?.from?.id; 32 | 33 | return `${id}` || "0"; 34 | }; 35 | 36 | const getUserID = (ctx: Context): string => { 37 | const id = ctx?.update?.message?.from?.id; 38 | 39 | return `${id}` || "0"; 40 | }; 41 | 42 | const getUserFirstName = (ctx: Context): string => { 43 | const first_name = ctx?.update?.message?.from?.first_name; 44 | 45 | return first_name?.trim() || ""; 46 | }; 47 | 48 | const getFullUser = (ctx: Context): MasterInterface => { 49 | const from = (ctx?.update?.message?.from as MasterInterface) || {}; 50 | 51 | from.username = getUsername(ctx); 52 | from.question = ""; 53 | from.description = ""; 54 | from[`score_${new Date().getFullYear()}`] = 0; 55 | from[`score_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] = 0; 56 | from.pin_id = 0; 57 | 58 | return from; 59 | }; 60 | 61 | const getChatID = (ctx: Context): number => { 62 | return ( 63 | ctx?.update?.message?.chat?.id || ctx?.message?.chat?.id || ctx?.update?.callback_query?.message?.chat?.id || 0 64 | ); 65 | }; 66 | 67 | const getThreadID = (ctx: any): number => { 68 | return ( 69 | ctx?.update.message?.message_thread_id || 70 | ctx?.message?.message_thread_id || 71 | ctx?.update?.callback_query?.message?.message_thread_id || 72 | 0 73 | ); 74 | }; 75 | 76 | const getActionType = (ctx: Context): string => { 77 | return ctx?.update?.callback_query?.data || ""; 78 | }; 79 | 80 | const getPhotoFileID = (ctx: Context, position = 0): string => { 81 | return ctx?.update?.message?.photo?.[position]?.file_id || ""; 82 | }; 83 | 84 | const getPhotoCaption = (ctx: Context): string => { 85 | return ctx?.update?.message?.caption || ""; 86 | }; 87 | 88 | const getText = (ctx: Context): string => { 89 | return ctx?.update?.message?.text || ctx?.message?.text || ""; 90 | }; 91 | 92 | const getMessageID = (ctx: Context): number => { 93 | return ctx?.update?.message?.message_id || ctx?.message?.message_id || 0; 94 | }; 95 | 96 | const getMessageIDFromAction = (ctx: Context): number => { 97 | return ctx?.update?.callback_query?.message?.message_id || ctx?.message?.message_id || 0; 98 | }; 99 | 100 | const editMessageReplyMarkup = async ( 101 | ctx: Context, 102 | options: Other | undefined, 103 | ): Promise => { 104 | try { 105 | await ctx.editMessageReplyMarkup(options); 106 | } catch (err: unknown) { 107 | logger.error(JSON.stringify(err), "message.ts:editMessageReplyMarkup()"); 108 | } 109 | }; 110 | 111 | const removeMessageMarkup = async (groupd_id: number, pin_id: number): Promise => { 112 | try { 113 | await bot.api.editMessageReplyMarkup(groupd_id, pin_id); 114 | } catch (err: unknown) { 115 | logger.error(JSON.stringify(err), "message.ts:removeMessageMarkup()"); 116 | } 117 | }; 118 | 119 | const getLanguage = async (ctx: Context): Promise => { 120 | const lang = await db.settings.get({ 121 | group_id: getChatID(ctx), 122 | }); 123 | 124 | if ( 125 | lang.language === "auto" && 126 | (ctx?.update?.message?.from?.language_code === "en" || ctx?.update?.message?.from?.language_code === "it") 127 | ) { 128 | lang.language = ctx?.update?.message?.from?.language_code; 129 | } 130 | 131 | return lang; 132 | }; 133 | 134 | const send = async ( 135 | ctx: Context, 136 | group_id: number, 137 | text: string, 138 | options: any = { parse_mode: "HTML" }, 139 | ): Promise => { 140 | if (group_id && text) { 141 | let message; 142 | 143 | logger.error(JSON.stringify(options), "message.ts:send()"); 144 | 145 | const thread_id = getThreadID(ctx); 146 | if (thread_id !== 0) { 147 | options.message_thread_id = thread_id; 148 | } 149 | 150 | try { 151 | message = await ctx.api.sendMessage(group_id, text, options); 152 | return message; 153 | } catch (err: unknown) { 154 | logger.error(JSON.stringify(err), "message.ts:send()"); 155 | } 156 | } 157 | }; 158 | 159 | const sendPhoto = async ( 160 | ctx: Context, 161 | group_id: number, 162 | photo: string, 163 | options: any = { parse_mode: "HTML" }, 164 | ): Promise => { 165 | if (group_id && photo) { 166 | let message; 167 | 168 | const thread_id = getThreadID(ctx); 169 | if (thread_id !== 0) { 170 | options.message_thread_id = thread_id; 171 | } 172 | 173 | try { 174 | message = await ctx.api.sendPhoto(group_id, photo, options); 175 | return message; 176 | } catch (err: unknown) { 177 | logger.error(JSON.stringify(err), "message.ts:send()"); 178 | } 179 | } 180 | }; 181 | 182 | const pin = async ( 183 | ctx: Context, 184 | group_id: number, 185 | message_id: number, 186 | options: any = { disable_notification: true }, 187 | ): Promise => { 188 | logger.debug(`group_id: ${group_id}`, "message.ts:pin()"); 189 | logger.debug(`message_id: ${message_id}`, "message.ts:pin()"); 190 | 191 | if (group_id && message_id) { 192 | const thread_id = getThreadID(ctx); 193 | if (thread_id !== 0) { 194 | options.message_thread_id = thread_id; 195 | } 196 | 197 | try { 198 | await ctx.api.pinChatMessage(group_id, message_id, options); 199 | } catch (err: unknown) { 200 | logger.error(JSON.stringify(err), "message.ts:pin()"); 201 | } 202 | } 203 | }; 204 | 205 | const unpin = async (ctx: Context, group_id: number, message_id: number, options?: any): Promise => { 206 | logger.debug(`group_id: ${group_id}`, "message.ts:unpin()"); 207 | logger.debug(`message_id: ${message_id}`, "message.ts:unpin()"); 208 | 209 | if (group_id && message_id) { 210 | try { 211 | await ctx.api.unpinChatMessage(group_id, message_id, options); 212 | } catch (err: unknown) { 213 | logger.error(JSON.stringify(err), "message.ts:unpin()"); 214 | } 215 | } 216 | }; 217 | 218 | const getDate = (gmt = 60): Date => { 219 | function addMinutes(date, min) { 220 | date.setMinutes(date.getMinutes() + min); 221 | 222 | return date; 223 | } 224 | 225 | return new Date(addMinutes(new Date(), gmt)); 226 | }; 227 | 228 | export { 229 | getFullUser, 230 | getUsername, 231 | getChatID, 232 | getThreadID, 233 | getText, 234 | getLanguage, 235 | getUserID, 236 | getUserFirstName, 237 | send, 238 | pin, 239 | unpin, 240 | sendPhoto, 241 | getPhotoFileID, 242 | getPhotoCaption, 243 | getActionType, 244 | getUsernameFromAction, 245 | getMessageID, 246 | getUserIDFromAction, 247 | getMessageIDFromAction, 248 | removeMessageMarkup, 249 | editMessageReplyMarkup, 250 | getDate, 251 | }; 252 | export default { 253 | getFullUser, 254 | getUsername, 255 | getChatID, 256 | getThreadID, 257 | getText, 258 | getLanguage, 259 | getUserID, 260 | getUserFirstName, 261 | send, 262 | pin, 263 | unpin, 264 | sendPhoto, 265 | getPhotoFileID, 266 | getPhotoCaption, 267 | getActionType, 268 | getUsernameFromAction, 269 | getMessageID, 270 | getUserIDFromAction, 271 | getMessageIDFromAction, 272 | removeMessageMarkup, 273 | editMessageReplyMarkup, 274 | getDate, 275 | }; 276 | -------------------------------------------------------------------------------- /.all-shieldsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md", 4 | "CHANGELOG.md" 5 | ], 6 | "shields": [ 7 | { 8 | "id": "header-badges", 9 | "badges": [ 10 | { 11 | "url": "https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/CHANGELOG.md", 12 | "color": "lightgray", 13 | "label": "version", 14 | "message": "v{{version}}", 15 | "title": "v{{version}}", 16 | "style": "flat", 17 | "logo": "", 18 | "platform": "shields" 19 | }, 20 | { 21 | "url": "https://www.npmjs.com/package/@ptkdev/quizquickanswer-telegram-game-bot", 22 | "color": "#CC3534", 23 | "label": "npm", 24 | "logo": "npm", 25 | "style": "flat", 26 | "custom": "/npm/v/@ptkdev/quizquickanswer-telegram-game-bot?color=CC3534&logo=npm", 27 | "platform": "shields" 28 | }, 29 | { 30 | "url": "https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/LICENSE.md", 31 | "color": "brightgreen", 32 | "label": "license", 33 | "message": "{{license}}", 34 | "title": "License: {{license}}", 35 | "style": "flat", 36 | "logo": "license", 37 | "platform": "shields" 38 | }, 39 | { 40 | "url": "https://www.typescriptlang.org/", 41 | "color": "blue", 42 | "label": "language", 43 | "message": "typescript", 44 | "title": "Language: TypeScript", 45 | "style": "flat", 46 | "logo": "typescript", 47 | "platform": "shields" 48 | }, 49 | { 50 | "url": "https://grammy.dev/", 51 | "color": "#009dca", 52 | "label": "powered by", 53 | "message": "grammy", 54 | "title": "Framework: Grammy", 55 | "style": "flat", 56 | "logo": "telegram", 57 | "platform": "shields" 58 | }, 59 | { 60 | "url": "https://github.com/tc39/ecma262", 61 | "color": "#F7DF1E", 62 | "label": "ES", 63 | "message": "9", 64 | "title": "ECMAScript: 2019", 65 | "style": "flat", 66 | "logo": "javascript", 67 | "platform": "shields" 68 | }, 69 | { 70 | "url": "https://discord.ptkdev.io", 71 | "server_id": "383373985666301975", 72 | "title": "Discord Server", 73 | "platform": "discord" 74 | } 75 | ] 76 | }, 77 | { 78 | "id": "sponsors-badges", 79 | "badges": [ 80 | { 81 | "url": "https://www.paypal.me/ptkdev", 82 | "color": "#005EA6", 83 | "label": "donate", 84 | "message": "paypal", 85 | "title": "Donate Paypal", 86 | "style": "for-the-badge", 87 | "logo": "paypal", 88 | "platform": "shields" 89 | }, 90 | { 91 | "url": "https://ko-fi.com/ptkdev", 92 | "color": "#29abe0", 93 | "label": "donate", 94 | "message": "ko-fi", 95 | "title": "Donate Ko-Fi", 96 | "style": "for-the-badge", 97 | "logo": "ko-fi", 98 | "platform": "shields" 99 | }, 100 | { 101 | "url": "https://github.com/sponsors/ptkdev", 102 | "color": "#ea4aaa", 103 | "label": "donate", 104 | "message": "sponsors", 105 | "title": "Donate Github Sponsors", 106 | "style": "for-the-badge", 107 | "logo": "github", 108 | "platform": "shields" 109 | }, 110 | { 111 | "url": "https://www.patreon.com/join/ptkdev", 112 | "color": "#F87668", 113 | "label": "donate", 114 | "message": "patreon", 115 | "title": "Donate Patreon", 116 | "style": "for-the-badge", 117 | "logo": "patreon", 118 | "platform": "shields" 119 | }, 120 | { 121 | "url": "https://ptk.dev/img/icons/menu/bitcoin_wallet.png", 122 | "color": "#E38B29", 123 | "label": "BTC", 124 | "message": "35jQmZCy4nsxoMM3QPFrnZePDVhdKaHMRH", 125 | "title": "Donate Bitcoin", 126 | "style": "flat-square", 127 | "logo": "bitcoin", 128 | "platform": "shields" 129 | }, 130 | { 131 | "url": "https://ptk.dev/img/icons/menu/ethereum_wallet.png", 132 | "color": "#4E8EE9", 133 | "label": "ETH", 134 | "message": "0x8b8171661bEb032828e82baBb0B5B98Ba8fBEBFc", 135 | "title": "Donate Ethereum", 136 | "style": "flat-square", 137 | "logo": "ethereum", 138 | "platform": "shields" 139 | } 140 | ] 141 | }, 142 | { 143 | "id": "projects-badges1", 144 | "badges": [ 145 | { 146 | "url": "https://ptk.dev/", 147 | "color": "#3498db", 148 | "label": "💻 My", 149 | "message": "Portfolio", 150 | "style": "flat", 151 | "logo": "", 152 | "platform": "shields" 153 | } 154 | ] 155 | }, 156 | { 157 | "id": "projects-badges2", 158 | "badges": [ 159 | { 160 | "url": "https://github.com/ptkdev/ptkdev-logger", 161 | "color": "#9b59b6", 162 | "label": "🦒 Tools", 163 | "message": "Node Logger", 164 | "style": "flat", 165 | "logo": "", 166 | "platform": "shields" 167 | }, 168 | { 169 | "url": "https://github.com/ptkdev/all-shields-cli", 170 | "color": "#9b59b6", 171 | "label": "🦌 Tools", 172 | "message": "All Shields CLI", 173 | "style": "flat", 174 | "logo": "", 175 | "platform": "shields" 176 | }, 177 | { 178 | "url": "https://github.com/ptkdev/chrome-extension-aspectratio219", 179 | "color": "#9b59b6", 180 | "label": "🖥️ Tools", 181 | "message": "Aspect Ratio 21:9", 182 | "style": "flat", 183 | "logo": "", 184 | "platform": "shields" 185 | }, 186 | { 187 | "url": "https://availableon.badge.ptkdev.io/", 188 | "color": "#9b59b6", 189 | "label": "🛡 Tools", 190 | "message": "Badges: Available on", 191 | "style": "flat", 192 | "logo": "", 193 | "platform": "shields" 194 | }, 195 | { 196 | "url": "https://github.com/ptkdev/json-token-replace", 197 | "color": "#9b59b6", 198 | "label": "🐾 Tools", 199 | "message": "JSON Token Replace", 200 | "style": "flat", 201 | "logo": "", 202 | "platform": "shields" 203 | }, 204 | { 205 | "url": "https://github.com/ptkdev/eslint-plugin-snakecasejs", 206 | "color": "#9b59b6", 207 | "label": "🐍 Tools", 208 | "message": "ESLint: snakecasejs", 209 | "style": "flat", 210 | "logo": "", 211 | "platform": "shields" 212 | } 213 | ] 214 | }, 215 | { 216 | "id": "projects-badges3", 217 | "badges": [ 218 | { 219 | "url": "https://github.com/ptkdev-components/webcomponent-instagram-widget", 220 | "color": "#e74c3c", 221 | "label": "📸 WebComponent", 222 | "message": "Instagram Widget", 223 | "style": "flat", 224 | "logo": "", 225 | "platform": "shields" 226 | }, 227 | { 228 | "url": "https://github.com/ptkdev-components/webcomponent-patreon-box", 229 | "color": "#e74c3c", 230 | "label": "👑 WebComponent", 231 | "message": "My Patreon Box", 232 | "style": "flat", 233 | "logo": "", 234 | "platform": "shields" 235 | }, 236 | { 237 | "url": "https://github.com/ptkdev-components/webcomponent-carousel-slideshow", 238 | "color": "#e74c3c", 239 | "label": "🏞 WebComponent", 240 | "message": "Carousel Slideshow", 241 | "style": "flat", 242 | "logo": "", 243 | "platform": "shields" 244 | } 245 | ] 246 | }, 247 | { 248 | "id": "projects-badges4", 249 | "badges": [ 250 | { 251 | "url": "https://github.com/ptkdev/vscode-theme-dark-blood", 252 | "color": "#f1c40f", 253 | "label": "🎨 Themes", 254 | "message": "VSCode", 255 | "style": "flat", 256 | "logo": "", 257 | "platform": "shields" 258 | }, 259 | { 260 | "url": "https://t.me/gamebookchatbot", 261 | "color": "#34495e", 262 | "label": "📚 Bot", 263 | "message": "GameBookChat", 264 | "style": "flat", 265 | "logo": "", 266 | "platform": "shields" 267 | }, 268 | { 269 | "url": "https://github.com/ptkdev?q=svelte", 270 | "color": "#f368e0", 271 | "label": "👔 Boilerplate", 272 | "message": "Svelte", 273 | "style": "flat", 274 | "logo": "", 275 | "platform": "shields" 276 | }, 277 | { 278 | "url": "https://github.com/ptkdev?q=webcomponent", 279 | "color": "#f368e0", 280 | "label": "👔 Boilerplate", 281 | "message": "WebComponents", 282 | "style": "flat", 283 | "logo": "", 284 | "platform": "shields" 285 | }, 286 | { 287 | "url": "https://github.com/ptkdev?q=bot", 288 | "color": "#f368e0", 289 | "label": "👔 Boilerplate", 290 | "message": "BOT", 291 | "style": "flat", 292 | "logo": "", 293 | "platform": "shields" 294 | }, 295 | { 296 | "url": "https://github.com/ptkdev?q=node", 297 | "color": "#f368e0", 298 | "label": "👔 Boilerplate", 299 | "message": "Node", 300 | "style": "flat", 301 | "logo": "", 302 | "platform": "shields" 303 | }, 304 | { 305 | "url": "https://meingifs.pics/", 306 | "color": "#2ecc71", 307 | "label": "💅 App", 308 | "message": "Me in Gifs", 309 | "style": "flat", 310 | "logo": "", 311 | "platform": "shields" 312 | }, 313 | { 314 | "url": "https://github.com/ptkdev/ptkdev-stickers#-install-free", 315 | "color": "#2ecc71", 316 | "label": "📱 App", 317 | "message": "Stickers", 318 | "style": "flat", 319 | "logo": "", 320 | "platform": "shields" 321 | } 322 | ] 323 | } 324 | ] 325 | } 326 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 quizquickanswer-telegram-game-bot 2 | 3 | 4 | 5 | [![v0.7.9-nightly.99](https://img.shields.io/badge/version-v0.7.9--nightly.99-lightgray.svg?style=flat&logo=)](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/CHANGELOG.md) [![](https://img.shields.io/npm/v/@ptkdev/quizquickanswer-telegram-game-bot?color=CC3534&logo=npm)](https://www.npmjs.com/package/@ptkdev/quizquickanswer-telegram-game-bot) [![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat&logo=license)](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/blob/main/LICENSE.md) [![Language: TypeScript](https://img.shields.io/badge/language-typescript-blue.svg?style=flat&logo=typescript)](https://www.typescriptlang.org/) [![Framework: Grammy](https://img.shields.io/badge/powered%20by-grammy-009dca.svg?style=flat&logo=telegram)](https://grammy.dev/) [![ECMAScript: 2019](https://img.shields.io/badge/ES-9-F7DF1E.svg?style=flat&logo=javascript)](https://github.com/tc39/ecma262) [![Discord Server](https://discordapp.com/api/guilds/383373985666301975/embed.png)](https://discord.ptkdev.io) 6 | 7 | 8 | 9 | Funny quiz game, play with friends on your telegram group! 10 | 11 | ## 🎁 Support: Donate 12 | 13 | > This project is **free**, **open source** and I try to provide excellent **free support**. Why donate? I work on this project several hours in my spare time and try to keep it up to date and working. **THANK YOU!** 14 | 15 | 16 | 17 | [![Donate Paypal](https://img.shields.io/badge/donate-paypal-005EA6.svg?style=for-the-badge&logo=paypal)](https://www.paypal.me/ptkdev) [![Donate Ko-Fi](https://img.shields.io/badge/donate-ko--fi-29abe0.svg?style=for-the-badge&logo=ko-fi)](https://ko-fi.com/ptkdev) [![Donate Github Sponsors](https://img.shields.io/badge/donate-sponsors-ea4aaa.svg?style=for-the-badge&logo=github)](https://github.com/sponsors/ptkdev) [![Donate Patreon](https://img.shields.io/badge/donate-patreon-F87668.svg?style=for-the-badge&logo=patreon)](https://www.patreon.com/join/ptkdev) [![Donate Bitcoin](https://img.shields.io/badge/BTC-35jQmZCy4nsxoMM3QPFrnZePDVhdKaHMRH-E38B29.svg?style=flat-square&logo=bitcoin)](https://ptk.dev/img/icons/menu/bitcoin_wallet.png) [![Donate Ethereum](https://img.shields.io/badge/ETH-0x8b8171661bEb032828e82baBb0B5B98Ba8fBEBFc-4E8EE9.svg?style=flat-square&logo=ethereum)](https://ptk.dev/img/icons/menu/ethereum_wallet.png) 18 | 19 | 20 | 21 | ## 📎 Menu 22 | 23 | - 💡 [Features](#-features) 24 | - 👔 [Screenshot](#-screenshot) 25 | - 🚀 [How to use](#-installation) 26 | - 🎮 [How to play](#-how-to-play) 27 | - 🔨 [Developer Mode](#-developer-mode) 28 | - - 🏁 [Run Project](#-run-project) 29 | - - 💾 [Setup Project](#-setup-project) 30 | - - 🚀 [Deploy](#-deploy) 31 | - 📚 [Documentation](#-documentation) 32 | - 👨‍💻 [Contributing](#-contributing) 33 | - 🐛 [Known Bugs](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/issues?q=is%3Aopen+is%3Aissue+label%3Abug) 34 | - 🍻 Community: 35 | - Telegram ([🇬🇧 English](https://t.me/QuizQuickAnswerGroup) | [🇮🇹 Italian](https://t.me/QuizQuickAnswerGroupITA)) 36 | 37 | ## 💡 Features 38 | 39 | - [✔️] Easy to use 40 | - [✔️] MIT License 41 | - [✔️] Powered by Grammy Telegram API Framework 42 | - [✔️] Quiz game, play with friends on your telegram group 43 | 44 | ## 👔 Screenshot 45 | 46 | [![quizquickanswer-telegram-game-bot](https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/main/.github/assets/screenshot.png)](https://raw.githubusercontent.com/ptkdev/quizquickanswer-telegram-game-bot/main/.github/assets/screenshot.png) 47 | 48 | ## 🚀 Installation 49 | 50 | 1. Add [@QuizQuickAnswerBot](https://t.me/QuizQuickAnswerBot) to your Telegram group 51 | 2. Make **@QuizQuickAnswerBot** admin of your group 52 | 3. Run `/start` or `/start@QuizQuickAnswerBot` 53 | 4. Make yourself master of game, run `/master @nickname` 54 | 5. Follow instructions and Play with friends! 55 | 56 | ## 🎮 How to play 57 | 58 | 1. Set a master with `/master @nickname` 59 | 2. Master send private message to [@QuizQuickAnswerBot](https://t.me/QuizQuickAnswerBot) 60 | 3. Master write question with syntax: `QUESTION ## TIP`, example: `gameboy ## '90s' portable console` 61 | 4. Friends try to answer quickly in the telegram group. Who reply with right answer is the new master! 62 | 63 | ## ⏱ Official Group 64 | 65 | If you have no telegram groups of friends you can play in the official one, you will find new friends and kind people: 66 | 67 | - [🇬🇧 English](https://t.me/QuizQuickAnswerGroup) 68 | - [🇮🇹 Italian](https://t.me/QuizQuickAnswerGroupITA) 69 | 70 | ## 🔨 Developer Mode 71 | 72 | #### 🏁 Run Project 73 | 74 | 1. Clone this repository or download [nightly](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/archive/nightly.zip), [beta](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/archive/beta.zip) or [stable](https://github.com/ptkdev/quizquickanswer-telegram-game-bot/archive/main.zip). 75 | 2. Write to [@botfather](https://t.me/botfather) on telegram and create new bot (save token and set bot username) 76 | 3. Set bot token: `export BOT_TOKEN=1234:asdfghjkl` 77 | 4. Set mongodb connection url: `export MONGODB=mongodb://localhost:27017/quizquickanswerdb` 78 | 5. Run `npm install` 79 | 6. Run `npm run dev` 80 | 7. Write `/start` on telegram bot. 81 | 82 | #### 🚀 Deploy 83 | 84 | Deploy bot to your server and: 85 | 86 | 1. Set bot token: `export BOT_TOKEN=1234:asdfghjkl` 87 | 2. Set mongodb connection url: `export MONGODB=mongodb://localhost:27017/quizquickanswerdb` 88 | 3. Run init npm install 89 | 4. Generate release `npm run release` 90 | 5. Start bot `npm run start-pm2` 91 | 92 | ## 📚 Documentation 93 | 94 | Run `npm run docs` 95 | 96 | ## 👑 Backers and Sponsors 97 | 98 | Thanks to all our backers! 🙏 Donate 3$ or more on [paypal](https://www.paypal.me/ptkdev), [ko-fi](https://ko-fi.com/ptkdev), [github](https://github.com/sponsors/ptkdev) or [patreon](https://www.patreon.com/join/ptkdev) and send me [email](mailto:support@ptkdev.io) with your avatar and url. 99 | 100 | [![](https://api.ptkdev.io/backers/sponsor1.png?)](https://api.ptkdev.io/backers/sponsor1.html) [![](https://api.ptkdev.io/backers/sponsor2.png?)](https://api.ptkdev.io/backers/sponsor2.html) [![](https://api.ptkdev.io/backers/sponsor-kofi1.png?)](https://api.ptkdev.io/backers/sponsor-kofi1.html) [![](https://api.ptkdev.io/backers/sponsor-kofi2.png?)](https://api.ptkdev.io/backers/sponsor-kofi2.html) [![](https://api.ptkdev.io/backers/sponsor-kofi3.png?)](https://api.ptkdev.io/backers/sponsor-kofi3.html) [![](https://api.ptkdev.io/backers/sponsor3.png?)](https://api.ptkdev.io/backers/sponsor3.html) [![](https://api.ptkdev.io/backers/sponsor4.png?)](https://api.ptkdev.io/backers/sponsor4.html) [![](https://api.ptkdev.io/backers/sponsor5.png?)](https://api.ptkdev.io/backers/sponsor5.html) [![](https://api.ptkdev.io/backers/sponsor6.png?)](https://api.ptkdev.io/backers/sponsor6.html) [![](https://api.ptkdev.io/backers/sponsor7.png?)](https://api.ptkdev.io/backers/sponsor7.html) [![](https://api.ptkdev.io/backers/sponsor8.png?)](https://api.ptkdev.io/backers/sponsor8.html) [![](https://api.ptkdev.io/backers/sponsor9.png?)](https://api.ptkdev.io/backers/sponsor9.html) [![](https://api.ptkdev.io/backers/sponsor10.png?)](https://api.ptkdev.io/backers/sponsor10.html) [![](https://api.ptkdev.io/backers/sponsor11.png?)](https://api.ptkdev.io/backers/sponsor11.html) [![](https://api.ptkdev.io/backers/sponsor12.png?)](https://api.ptkdev.io/backers/sponsor12.html) [![](https://api.ptkdev.io/backers/sponsor13.png?)](https://api.ptkdev.io/backers/sponsor13.html) [![](https://api.ptkdev.io/backers/sponsor14.png?)](https://api.ptkdev.io/backers/sponsor14.html) [![](https://api.ptkdev.io/backers/sponsor15.png?)](https://api.ptkdev.io/backers/sponsor15.html) [![](https://api.ptkdev.io/backers/backer1.png?)](https://api.ptkdev.io/backers/backer1.html) [![](https://api.ptkdev.io/backers/backer2.png?)](https://api.ptkdev.io/backers/backer2.html) [![](https://api.ptkdev.io/backers/backer3.png?)](https://api.ptkdev.io/backers/backer3.html) [![](https://api.ptkdev.io/backers/backer4.png?)](https://api.ptkdev.io/backers/backer4.html) [![](https://api.ptkdev.io/backers/backer5.png?)](https://api.ptkdev.io/backers/backer5.html) [![](https://api.ptkdev.io/backers/backer6.png?)](https://api.ptkdev.io/backers/backer6.html) [![](https://api.ptkdev.io/backers/backer7.png?)](https://api.ptkdev.io/backers/backer7.html) [![](https://api.ptkdev.io/backers/backer8.png?)](https://api.ptkdev.io/backers/backer8.html) [![](https://api.ptkdev.io/backers/backer9.png?)](https://api.ptkdev.io/backers/backer9.html) [![](https://api.ptkdev.io/backers/backer10.png?)](https://api.ptkdev.io/backers/backer10.html) [![](https://api.ptkdev.io/backers/backer11.png?)](https://api.ptkdev.io/backers/backer11.html) [![](https://api.ptkdev.io/backers/backer12.png?)](https://api.ptkdev.io/backers/backer12.html) [![](https://api.ptkdev.io/backers/backer13.png?)](https://api.ptkdev.io/backers/backer13.html) [![](https://api.ptkdev.io/backers/backer14.png?)](https://api.ptkdev.io/backers/backer14.html) [![](https://api.ptkdev.io/backers/backer15.png?)](https://api.ptkdev.io/backers/backer15.html) [![](https://api.ptkdev.io/backers/backer16.png?)](https://api.ptkdev.io/backers/backer16.html) [![](https://api.ptkdev.io/backers/backer17.png?)](https://api.ptkdev.io/backers/backer17.html) [![](https://api.ptkdev.io/backers/backer18.png?)](https://api.ptkdev.io/backers/backer18.html) [![](https://api.ptkdev.io/backers/backer19.png?)](https://api.ptkdev.io/backers/backer19.html) [![](https://api.ptkdev.io/backers/backer20.png?)](https://api.ptkdev.io/backers/backer20.html) [![](https://api.ptkdev.io/backers/backer21.png?)](https://api.ptkdev.io/backers/backer21.html) [![](https://api.ptkdev.io/backers/backer22.png?)](https://api.ptkdev.io/backers/backer22.html) [![](https://api.ptkdev.io/backers/backer23.png?)](https://api.ptkdev.io/backers/backer23.html) [![](https://api.ptkdev.io/backers/backer24.png?)](https://api.ptkdev.io/backers/backer24.html) [![](https://api.ptkdev.io/backers/backer25.png?)](https://api.ptkdev.io/backers/backer25.html) [![](https://api.ptkdev.io/backers/backer26.png?)](https://api.ptkdev.io/backers/backer26.html) [![](https://api.ptkdev.io/backers/backer27.png?)](https://api.ptkdev.io/backers/backer27.html) [![](https://api.ptkdev.io/backers/backer28.png?)](https://api.ptkdev.io/backers/backer28.html) [![](https://api.ptkdev.io/backers/backer29.png?)](https://api.ptkdev.io/backers/backer29.html) 101 | 102 | ## 👨‍💻 Contributing 103 | 104 | I ❤️ contributions! I will happily accept your pull request! (**IMPORTANT**: Only to nightly branch!) Translations, grammatical corrections (GrammarNazi you are welcome! Yes my English is bad, sorry), etc... Do not be afraid, if the code is not perfect we will work together 👯 and remember to insert your name in `.all-contributorsrc` and `package.json` file. 105 | 106 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
Patryk Rzucidło
Patryk Rzucidło

💻 🌍 📖 🐛
Alì Shadman
Alì Shadman
💻 🌍 📖 🐛
Denise Scazzari
Denise Scazzari

🌍
Alessandro Di Maria
Alessandro Di Maria

💻 🐛 🌍
121 | 122 | 123 | 124 | 125 | 126 | 127 | > 💰 In the future, if the donations allow it, I would like to share some of the success with those who helped me the most. For me open source is share of code, share development knowledges and share donations! 128 | 129 | ## 🦄 Other Projects 130 | 131 | 132 | 133 | [![](https://img.shields.io/badge/%F0%9F%92%BB%20My-Portfolio-3498db.svg?style=flat&logo=)](https://ptk.dev/) 134 | 135 | 136 | 137 | 138 | 139 | [![](https://img.shields.io/badge/%F0%9F%A6%92%20Tools-Node%20Logger-9b59b6.svg?style=flat&logo=)](https://github.com/ptkdev/ptkdev-logger) [![](https://img.shields.io/badge/%F0%9F%A6%8C%20Tools-All%20Shields%20CLI-9b59b6.svg?style=flat&logo=)](https://github.com/ptkdev/all-shields-cli) [![](https://img.shields.io/badge/%F0%9F%96%A5%EF%B8%8F%20Tools-Aspect%20Ratio%2021%3A9-9b59b6.svg?style=flat&logo=)](https://github.com/ptkdev/chrome-extension-aspectratio219) [![](https://img.shields.io/badge/%F0%9F%9B%A1%20Tools-Badges%3A%20Available%20on-9b59b6.svg?style=flat&logo=)](https://availableon.badge.ptkdev.io/) [![](https://img.shields.io/badge/%F0%9F%90%BE%20Tools-JSON%20Token%20Replace-9b59b6.svg?style=flat&logo=)](https://github.com/ptkdev/json-token-replace) [![](https://img.shields.io/badge/%F0%9F%90%8D%20Tools-ESLint%3A%20snakecasejs-9b59b6.svg?style=flat&logo=)](https://github.com/ptkdev/eslint-plugin-snakecasejs) 140 | 141 | 142 | 143 | 144 | 145 | [![](https://img.shields.io/badge/%F0%9F%93%B8%20WebComponent-Instagram%20Widget-e74c3c.svg?style=flat&logo=)](https://github.com/ptkdev-components/webcomponent-instagram-widget) [![](https://img.shields.io/badge/%F0%9F%91%91%20WebComponent-My%20Patreon%20Box-e74c3c.svg?style=flat&logo=)](https://github.com/ptkdev-components/webcomponent-patreon-box) [![](https://img.shields.io/badge/%F0%9F%8F%9E%20WebComponent-Carousel%20Slideshow-e74c3c.svg?style=flat&logo=)](https://github.com/ptkdev-components/webcomponent-carousel-slideshow) 146 | 147 | 148 | 149 | 150 | 151 | [![](https://img.shields.io/badge/%F0%9F%8E%A8%20Themes-VSCode-f1c40f.svg?style=flat&logo=)](https://github.com/ptkdev/vscode-theme-dark-blood) [![](https://img.shields.io/badge/%F0%9F%93%9A%20Bot-GameBookChat-34495e.svg?style=flat&logo=)](https://t.me/gamebookchatbot) [![](https://img.shields.io/badge/%F0%9F%91%94%20Boilerplate-Svelte-f368e0.svg?style=flat&logo=)](https://github.com/ptkdev?q=svelte) [![](https://img.shields.io/badge/%F0%9F%91%94%20Boilerplate-WebComponents-f368e0.svg?style=flat&logo=)](https://github.com/ptkdev?q=webcomponent) [![](https://img.shields.io/badge/%F0%9F%91%94%20Boilerplate-BOT-f368e0.svg?style=flat&logo=)](https://github.com/ptkdev?q=bot) [![](https://img.shields.io/badge/%F0%9F%91%94%20Boilerplate-Node-f368e0.svg?style=flat&logo=)](https://github.com/ptkdev?q=node) [![](https://img.shields.io/badge/%F0%9F%92%85%20App-Me%20in%20Gifs-2ecc71.svg?style=flat&logo=)](https://meingifs.pics/) [![](https://img.shields.io/badge/%F0%9F%93%B1%20App-Stickers-2ecc71.svg?style=flat&logo=)](https://github.com/ptkdev/ptkdev-stickers#-install-free) 152 | 153 | 154 | 155 | ## 💫 License 156 | 157 | - Code and Contributions have **MIT License** 158 | - Images and logos have **CC BY-NC 4.0 License** 159 | - Documentations and Translations have **CC BY 4.0 License** 160 | 161 | ###### Copyleft (c) 2022 [Patryk Rzucidło](https://ptk.dev) ([@PTKDev](https://twitter.com/ptkdev)) <[support@ptkdev.io](mailto:support@ptkdev.io)> 162 | -------------------------------------------------------------------------------- /app/functions/commands/hears.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Telegraf Hears 3 | * ===================== 4 | * 5 | * @contributors: Patryk Rzucidło [@ptkdev] (https://ptk.dev) 6 | * Alì Shadman [@AliShadman95] (https://github.com/AliShadman95) 7 | * Alessandro Di Maria [@ImAl3x03] (https://github.com/ImAl3x03) 8 | * 9 | * @license: MIT License 10 | * 11 | */ 12 | import { InlineKeyboard } from "grammy"; 13 | import { CronJob } from "cron"; 14 | import bot from "@app/core/token"; 15 | import translate from "@translations/translate"; 16 | import db from "@routes/api/database"; 17 | import telegram from "@routes/api/telegram"; 18 | import logger from "@app/functions/utils/logger"; 19 | import { similarity } from "@app/functions/utils/utils"; 20 | import { vote } from "@app/functions/utils/vote"; 21 | 22 | import type { MasterInterface } from "@app/types/master.interfaces"; 23 | import type { QuestionsInterface } from "@app/types/question.interfaces"; 24 | 25 | /** 26 | * hears: any taxt from bot chat 27 | * ===================== 28 | * Listen any text user write 29 | * 30 | */ 31 | const hears = async (): Promise => { 32 | const cron_run: boolean[] = []; 33 | bot.on("message:text", async (ctx) => { 34 | logger.info("hears: text", "hears.ts:on(text)"); 35 | const lang = await telegram.api.message.getLanguage(ctx); 36 | 37 | if (telegram.api.message.getChatID(ctx) > 0) { 38 | // is chat with bot 39 | const master: MasterInterface = await db.master.get({ 40 | username: telegram.api.message.getUsername(ctx), 41 | }); 42 | 43 | logger.debug(`master: ${JSON.stringify(master)}`); 44 | logger.debug(`${master?.username} === ${telegram.api.message.getUsername(ctx)}`); 45 | if (master?.username === telegram.api.message.getUsername(ctx)) { 46 | const text = telegram.api.message.getText(ctx).split("##"); 47 | 48 | const json = telegram.api.message.getFullUser(ctx); 49 | json.question = text[0]?.trim()?.toLowerCase() || ""; 50 | json.description = text[1]?.trim() || ""; 51 | json.group_id = master?.group_id || 0; 52 | json.message_thread_id = master?.message_thread_id; 53 | 54 | if (json.question === undefined || json.question === "") { 55 | await telegram.api.message.send( 56 | ctx, 57 | telegram.api.message.getChatID(ctx), 58 | translate(lang.language, "hears_missing_question"), 59 | ); 60 | } else if (json.description === undefined || json.description === "") { 61 | await telegram.api.message.send( 62 | ctx, 63 | telegram.api.message.getChatID(ctx), 64 | translate(lang.language, "hears_missing_tip"), 65 | ); 66 | } else { 67 | if (master?.win_message_id > 0) { 68 | await telegram.api.message.removeMessageMarkup(master?.group_id, master?.win_message_id); 69 | } 70 | 71 | if (master?.pin_id > 0) { 72 | await telegram.api.message.unpin(ctx, master?.group_id, master?.pin_id); 73 | } 74 | 75 | await db.master.update({ username: telegram.api.message.getUsername(ctx) }, json); 76 | 77 | const master_in_multi_groups = await db.master.getMultiple({ 78 | username: telegram.api.message.getUsername(ctx), 79 | }); 80 | 81 | master_in_multi_groups.forEach(async (master_in_group) => { 82 | const quiz = await telegram.api.message.send( 83 | ctx, 84 | master_in_group?.group_id, 85 | `⏱ ${json.description || ""}`, 86 | { 87 | message_thread_id: master_in_group.message_thread_id, 88 | }, 89 | ); 90 | 91 | await telegram.api.message.send( 92 | ctx, 93 | telegram.api.message.getChatID(ctx), 94 | translate(lang.language, "hears_question_success"), 95 | ); 96 | 97 | if (quiz) { 98 | await telegram.api.message.pin(ctx, master_in_group?.group_id, quiz?.message_id, { 99 | disable_notification: true, 100 | message_thread_id: master_in_group.message_thread_id, 101 | }); 102 | 103 | master_in_group.pin_id = quiz?.message_id || 0; 104 | await db.master.update( 105 | { username: telegram.api.message.getUsername(ctx) }, 106 | master_in_group, 107 | ); 108 | } else { 109 | await db.master.remove({ 110 | group_id: master_in_group?.group_id, 111 | }); 112 | } 113 | }); 114 | } 115 | } else { 116 | await telegram.api.message.send( 117 | ctx, 118 | telegram.api.message.getChatID(ctx), 119 | translate(lang.language, "haers_not_you_master"), 120 | ); 121 | } 122 | } 123 | 124 | if (telegram.api.message.getChatID(ctx) < 0) { 125 | // is group 126 | const master: MasterInterface = await db.master.get({ 127 | group_id: telegram.api.message.getChatID(ctx), 128 | }); 129 | 130 | if (cron_run[`${telegram.api.message.getChatID(ctx)}`] === undefined && master.timezone !== "") { 131 | cron_run[`${telegram.api.message.getChatID(ctx)}`] = true; 132 | new CronJob( 133 | "59 23 * * 0-4", 134 | async function () { 135 | telegram.api.message.send( 136 | ctx, 137 | telegram.api.message.getChatID(ctx), 138 | translate(lang.language, "master_off"), 139 | ); 140 | 141 | const json = { 142 | id: "0", 143 | is_bot: false, 144 | first_name: telegram.api.bot.getUsername(ctx), 145 | username: telegram.api.bot.getUsername(ctx), 146 | language_code: "", 147 | question: "", 148 | description: "Night Mode, Bot Spento", 149 | score_2021: 0, 150 | score_2022: 0, 151 | score_2023: 0, 152 | score_1_2023: 0, 153 | score_2_2023: 0, 154 | score_3_2023: 0, 155 | score_4_2023: 0, 156 | score_5_2023: 0, 157 | score_6_2023: 0, 158 | score_7_2023: 0, 159 | score_8_2023: 0, 160 | score_9_2023: 0, 161 | score_10_2023: 0, 162 | score_11_2023: 0, 163 | score_12_2023: 0, 164 | score_2024: 0, 165 | score_1_2024: 0, 166 | score_2_2024: 0, 167 | score_3_2024: 0, 168 | score_4_2024: 0, 169 | score_5_2024: 0, 170 | score_6_2024: 0, 171 | score_7_2024: 0, 172 | score_8_2024: 0, 173 | score_9_2024: 0, 174 | score_10_2024: 0, 175 | score_11_2024: 0, 176 | score_12_2024: 0, 177 | score_2025: 0, 178 | score_1_2025: 0, 179 | score_2_2025: 0, 180 | score_3_2025: 0, 181 | score_4_2025: 0, 182 | score_5_2025: 0, 183 | score_6_2025: 0, 184 | score_7_2025: 0, 185 | score_8_2025: 0, 186 | score_9_2025: 0, 187 | score_10_2025: 0, 188 | score_11_2025: 0, 189 | score_12_2025: 0, 190 | pin_id: 0, 191 | win_message_id: 0, 192 | timezone: "Europe/Rome", 193 | off: true, 194 | group_id: telegram.api.message.getChatID(ctx), 195 | message_thread_id: telegram.api.message.getThreadID(ctx), 196 | }; 197 | 198 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 199 | }, 200 | null, 201 | true, 202 | "Europe/Rome", 203 | ); 204 | 205 | new CronJob( 206 | "0 1 * * 6,0", 207 | async function () { 208 | telegram.api.message.send( 209 | ctx, 210 | telegram.api.message.getChatID(ctx), 211 | translate(lang.language, "master_off"), 212 | ); 213 | 214 | const json = { 215 | id: "0", 216 | is_bot: false, 217 | first_name: telegram.api.bot.getUsername(ctx), 218 | username: telegram.api.bot.getUsername(ctx), 219 | language_code: "", 220 | question: "", 221 | description: "Night Mode, Bot Spento", 222 | score_2021: 0, 223 | score_2022: 0, 224 | score_2023: 0, 225 | score_1_2023: 0, 226 | score_2_2023: 0, 227 | score_3_2023: 0, 228 | score_4_2023: 0, 229 | score_5_2023: 0, 230 | score_6_2023: 0, 231 | score_7_2023: 0, 232 | score_8_2023: 0, 233 | score_9_2023: 0, 234 | score_10_2023: 0, 235 | score_11_2023: 0, 236 | score_12_2023: 0, 237 | score_2024: 0, 238 | score_1_2024: 0, 239 | score_2_2024: 0, 240 | score_3_2024: 0, 241 | score_4_2024: 0, 242 | score_5_2024: 0, 243 | score_6_2024: 0, 244 | score_7_2024: 0, 245 | score_8_2024: 0, 246 | score_9_2024: 0, 247 | score_10_2024: 0, 248 | score_11_2024: 0, 249 | score_12_2024: 0, 250 | score_2025: 0, 251 | score_1_2025: 0, 252 | score_2_2025: 0, 253 | score_3_2025: 0, 254 | score_4_2025: 0, 255 | score_5_2025: 0, 256 | score_6_2025: 0, 257 | score_7_2025: 0, 258 | score_8_2025: 0, 259 | score_9_2025: 0, 260 | score_10_2025: 0, 261 | score_11_2025: 0, 262 | score_12_2025: 0, 263 | pin_id: 0, 264 | win_message_id: 0, 265 | timezone: "Europe/Rome", 266 | off: true, 267 | group_id: telegram.api.message.getChatID(ctx), 268 | message_thread_id: telegram.api.message.getThreadID(ctx), 269 | }; 270 | 271 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 272 | }, 273 | null, 274 | true, 275 | "Europe/Rome", 276 | ); 277 | 278 | new CronJob( 279 | "0 9 * * *", 280 | async function () { 281 | telegram.api.message.send( 282 | ctx, 283 | telegram.api.message.getChatID(ctx), 284 | translate(lang.language, "master_on"), 285 | ); 286 | 287 | const programming_languages = [ 288 | "Java", 289 | "Python", 290 | "JavaScript", 291 | "Ruby", 292 | "C#", 293 | "Swift", 294 | "Kotlin", 295 | "Go", 296 | "TypeScript", 297 | "PHP", 298 | "Rust", 299 | "Scala", 300 | "C++", 301 | "Dart", 302 | "Lua", 303 | "R", 304 | "MATLAB", 305 | "Julia", 306 | "Perl", 307 | "Objective-C", 308 | "Visual Basic", 309 | "Groovy", 310 | "Shell", 311 | "Haskell", 312 | "F#", 313 | "Erlang", 314 | "Lisp", 315 | "Clojure", 316 | "Prolog", 317 | "SQL", 318 | "Kotlin", 319 | "COBOL", 320 | "Fortran", 321 | "Pascal", 322 | "Ada", 323 | "Assembly", 324 | "BASIC", 325 | "Smalltalk", 326 | "Tcl", 327 | "Scheme", 328 | "Swift", 329 | "Objective-C", 330 | "C", 331 | "PowerShell", 332 | "Visual Basic .NET", 333 | "Delphi", 334 | "ActionScript", 335 | "Scratch", 336 | ]; 337 | 338 | const json = { 339 | id: "0", 340 | is_bot: false, 341 | first_name: telegram.api.bot.getUsername(ctx), 342 | username: telegram.api.bot.getUsername(ctx), 343 | language_code: "", 344 | question: programming_languages[Math.floor(Math.random() * programming_languages.length)], 345 | description: "linguaggio di programmazione", 346 | score_2021: 0, 347 | score_2022: 0, 348 | score_2023: 0, 349 | score_1_2023: 0, 350 | score_2_2023: 0, 351 | score_3_2023: 0, 352 | score_4_2023: 0, 353 | score_5_2023: 0, 354 | score_6_2023: 0, 355 | score_7_2023: 0, 356 | score_8_2023: 0, 357 | score_9_2023: 0, 358 | score_10_2023: 0, 359 | score_11_2023: 0, 360 | score_12_2023: 0, 361 | score_2024: 0, 362 | score_1_2024: 0, 363 | score_2_2024: 0, 364 | score_3_2024: 0, 365 | score_4_2024: 0, 366 | score_5_2024: 0, 367 | score_6_2024: 0, 368 | score_7_2024: 0, 369 | score_8_2024: 0, 370 | score_9_2024: 0, 371 | score_10_2024: 0, 372 | score_11_2024: 0, 373 | score_12_2024: 0, 374 | score_2025: 0, 375 | score_1_2025: 0, 376 | score_2_2025: 0, 377 | score_3_2025: 0, 378 | score_4_2025: 0, 379 | score_5_2025: 0, 380 | score_6_2025: 0, 381 | score_7_2025: 0, 382 | score_8_2025: 0, 383 | score_9_2025: 0, 384 | score_10_2025: 0, 385 | score_11_2025: 0, 386 | score_12_2025: 0, 387 | pin_id: 0, 388 | win_message_id: 0, 389 | timezone: "Europe/Rome", 390 | off: false, 391 | group_id: telegram.api.message.getChatID(ctx), 392 | message_thread_id: telegram.api.message.getThreadID(ctx), 393 | }; 394 | 395 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 396 | }, 397 | null, 398 | true, 399 | "Europe/Rome", 400 | ); 401 | } 402 | 403 | if (master.off) { 404 | return; 405 | } 406 | 407 | const username = telegram.api.message.getUsername(ctx); 408 | 409 | if (master.username === username) { 410 | return; 411 | } 412 | 413 | if (telegram.api.message.getText(ctx).trim().toLowerCase() == master?.question?.trim()?.toLowerCase()) { 414 | if (telegram.api.message.getUsername(ctx)) { 415 | const user_score: MasterInterface = await db.scores.get({ 416 | group_id: telegram.api.message.getChatID(ctx), 417 | id: telegram.api.message.getUserID(ctx), 418 | }); 419 | 420 | logger.debug(`user_score: ${JSON.stringify(user_score)}`); 421 | 422 | const user_questions: QuestionsInterface = await db.questions.get({ 423 | group_id: telegram.api.message.getChatID(ctx), 424 | user_id: telegram.api.message.getUserID(ctx), 425 | }); 426 | 427 | const buttons = new InlineKeyboard(); 428 | buttons.text(`👍 0`, `upvote ${master.id}`); 429 | buttons.text(`👎 0`, `downvote ${master.id}`); 430 | 431 | const win_message = await telegram.api.message.send( 432 | ctx, 433 | master?.group_id, 434 | translate(lang.language, "hears_win", { 435 | first_name: telegram.api.message.getUserFirstName(ctx), 436 | username: telegram.api.message.getUsername(ctx), 437 | bot_username: telegram.api.bot.getUsername(ctx), 438 | master: master.username, 439 | answer: master.question, 440 | tip: master.description, 441 | score: user_questions 442 | ? (user_score?.[`score_${new Date().getFullYear()}`] || 0) + 443 | 10 + 444 | user_questions[`upvotes_${new Date().getFullYear()}`] - 445 | user_questions[`downvotes_${new Date().getFullYear()}`] 446 | : (user_score?.[`score_${new Date().getFullYear()}`] || 0) + 10, 447 | }), 448 | { reply_markup: buttons, parse_mode: "HTML" }, 449 | ); 450 | 451 | if (master?.win_message_id > 0) { 452 | await telegram.api.message.removeMessageMarkup(master?.group_id, master?.win_message_id); 453 | } 454 | 455 | if (master?.pin_id > 0) { 456 | await telegram.api.message.unpin(ctx, master?.group_id, master?.pin_id); 457 | } 458 | 459 | const json: MasterInterface = telegram.api.message.getFullUser(ctx); 460 | json.question = ""; 461 | json.description = ""; 462 | json.group_id = telegram.api.message.getChatID(ctx); 463 | json.win_message_id = win_message?.message_id || 0; 464 | json.message_thread_id = telegram.api.message.getThreadID(ctx); 465 | 466 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, json); 467 | 468 | if (user_score.group_id < 0) { 469 | user_score[`score_${new Date().getFullYear()}`] += 10; 470 | user_score[`score_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] += 10; 471 | await db.scores.update( 472 | { 473 | group_id: telegram.api.message.getChatID(ctx), 474 | id: telegram.api.message.getUserID(ctx), 475 | }, 476 | user_score, 477 | ); 478 | } else { 479 | const json_score: MasterInterface = telegram.api.message.getFullUser(ctx); 480 | json_score[`score_${new Date().getFullYear()}`] = 10; 481 | json_score[`score_${new Date().getMonth() + 1}_${new Date().getFullYear()}`] += 10; 482 | await db.scores.add(json_score); 483 | } 484 | } else { 485 | await telegram.api.message.send( 486 | ctx, 487 | master?.group_id || 0, 488 | translate(lang.language, "hears_win_but_not_master", { 489 | first_name: telegram.api.message.getUserFirstName(ctx), 490 | master_first_name: master.first_name, 491 | master_username: master.username, 492 | }), 493 | ); 494 | } 495 | return; 496 | } else { 497 | await db.master.update({ group_id: telegram.api.message.getChatID(ctx) }, master); 498 | } 499 | 500 | const similarityPercentage: number = similarity( 501 | telegram.api.message.getText(ctx).trim().toLowerCase(), 502 | master?.question?.trim()?.toLowerCase() || "", 503 | ); 504 | 505 | if ( 506 | similarityPercentage >= 0.7 && 507 | telegram.api.message.getText(ctx).trim().toLowerCase().split("").length > 4 508 | ) { 509 | await telegram.api.message.send( 510 | ctx, 511 | master.group_id, 512 | translate(lang.language, "hot_answer", { 513 | first_name: telegram.api.message.getUserFirstName(ctx), 514 | username: telegram.api.message.getUsername(ctx), 515 | }), 516 | ); 517 | } 518 | } 519 | }); 520 | 521 | bot.callbackQuery(/upvote (.*)/, async (ctx) => { 522 | const match: any = ctx.match; 523 | 524 | await vote(ctx, "upvote", match.input.replace("upvote ", "")); 525 | }); 526 | 527 | bot.callbackQuery(/downvote (.*)/, async (ctx) => { 528 | const match: any = ctx.match; 529 | 530 | await vote(ctx, "downvote", match.input.replace("downvote ", "")); 531 | }); 532 | }; 533 | 534 | export { hears }; 535 | export default hears; 536 | --------------------------------------------------------------------------------