├── resources ├── logo │ ├── logo.png │ ├── license_logo.png │ └── trasparencylogo.png └── screenshots │ ├── aboutyou.jpg │ ├── byvotes.jpg │ ├── bymembers.jpg │ └── bymessages.jpg ├── .gitignore ├── topsupergroupsbot ├── __init__.py ├── langs │ ├── __init__.py │ ├── en.py │ ├── pt_br.py │ └── it.py ├── emojis.py ├── constants.py ├── categories.py ├── supported_langs.py ├── messages_private.py ├── get_lang.py ├── config.py ├── cleandb.py ├── votelink.py ├── regular_buttons.py ├── antiflood.py ├── cache_users_stats.py ├── memberslog.py ├── messages.py ├── pages.py ├── digest_private.py ├── database.py ├── cache_groups_rank.py ├── messages_supergroups.py ├── __main__.py ├── feedback.py ├── commands_private.py ├── utils.py ├── keyboards.py ├── digest_supergroups.py └── commands.py ├── setup.py ├── config └── config.example.yaml └── README.md /resources/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/logo/logo.png -------------------------------------------------------------------------------- /resources/logo/license_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/logo/license_logo.png -------------------------------------------------------------------------------- /resources/logo/trasparencylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/logo/trasparencylogo.png -------------------------------------------------------------------------------- /resources/screenshots/aboutyou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/screenshots/aboutyou.jpg -------------------------------------------------------------------------------- /resources/screenshots/byvotes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/screenshots/byvotes.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /resources/logo/logobase.png 3 | /.idea 4 | /config/config.yaml 5 | /topsupergroupsbot.egg-info 6 | /backupdb 7 | -------------------------------------------------------------------------------- /resources/screenshots/bymembers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/screenshots/bymembers.jpg -------------------------------------------------------------------------------- /resources/screenshots/bymessages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/91DarioDev/topsupergroupsbot/HEAD/resources/screenshots/bymessages.jpg -------------------------------------------------------------------------------- /topsupergroupsbot/__init__.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . -------------------------------------------------------------------------------- /topsupergroupsbot/langs/__init__.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | from . import en 17 | from . import it -------------------------------------------------------------------------------- /topsupergroupsbot/emojis.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | STAR = "⭐️" 19 | NSFW = "🔞" 20 | NEW = "🆕" 21 | CURRENT_CHOICE = "🔘" 22 | -------------------------------------------------------------------------------- /topsupergroupsbot/constants.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import config 18 | 19 | from telegram import Bot 20 | 21 | 22 | 23 | BUTTON_START = "•" 24 | BUTTON_END = "•" 25 | 26 | FEEDBACK_INV_CHAR = "\ufeff" 27 | 28 | GET_ME = Bot(config.BOT_TOKEN).getMe() 29 | 30 | MAX_CHARS_LEADERBOARD_PAGE_PRIVATE = 30 31 | MAX_CHARS_LEADERBOARD_PAGE_GROUP = 30 -------------------------------------------------------------------------------- /topsupergroupsbot/categories.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | CODES = { 18 | 'a': 'news', 19 | 'b': 'science_and_education', 20 | 'c': 'religion', 21 | 'd': 'entertainment', 22 | 'e': 'family_and_home', 23 | 'f': 'sport', 24 | 'g': 'art_and_culture', 25 | 'h': 'politics', 26 | 'i': 'information_technology', 27 | 'j': 'game_and_apps', 28 | 'k': 'love', 29 | 'l': 'tourism', 30 | 'm': 'economics' 31 | } -------------------------------------------------------------------------------- /topsupergroupsbot/supported_langs.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | COUNTRY_FLAG = { 18 | "en": "🌎", 19 | "it": "🇮🇹", 20 | "other": "", 21 | "de": "🇩🇪", 22 | "pt": "🇧🇷/🇵🇹", 23 | "ru": "🇷🇺", 24 | "es": "🇪🇸" 25 | } 26 | 27 | 28 | # GROUPS 29 | 30 | GROUP_LANGS = [ 31 | 'it', 32 | 'en', 33 | 'other', 34 | 'de', 35 | 'pt', 36 | 'ru', 37 | 'es' 38 | ] 39 | 40 | 41 | # PRIVATE 42 | 43 | PRIVATE_LANGS = [ 44 | 'it', 45 | 'en', 46 | 'pt' 47 | ] 48 | 49 | PRIVATE_REGIONS = GROUP_LANGS 50 | -------------------------------------------------------------------------------- /topsupergroupsbot/messages_private.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import database 18 | from topsupergroupsbot import utils 19 | 20 | def add_user_db(bot, update): 21 | m = update.message 22 | 23 | guessed_lang = utils.guessed_user_lang(bot, update) 24 | 25 | query = """INSERT INTO users(user_id, lang, region, tg_lang, message_date) 26 | VALUES (%s, %s, %s, %s, %s) 27 | ON CONFLICT (user_id) DO 28 | UPDATE SET bot_blocked = FALSE, tg_lang = COALESCE(%s, users.tg_lang), message_date = %s 29 | WHERE users.user_id = %s""" 30 | database.query_w( 31 | query, m.from_user.id, guessed_lang, guessed_lang, 32 | m.from_user.language_code, m.date, m.from_user.language_code, 33 | m.date, m.from_user.id) 34 | 35 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import setuptools 18 | 19 | 20 | setuptools.setup( 21 | 22 | name="topsupergroupsbot", 23 | version="1", 24 | 25 | license="AGPL-3.0", 26 | 27 | author="Dario 91DarioDev", 28 | author_email="dariomsn@hotmail.it", 29 | 30 | install_requires=[ 31 | "redis<2.10.6", 32 | "Babel", 33 | "python-telegram-bot<12", 34 | "psycopg2<2.7.3", 35 | "Pyyaml" 36 | ], 37 | 38 | packages=[ 39 | "topsupergroupsbot", 40 | "topsupergroupsbot.langs" 41 | ], 42 | 43 | entry_points={ 44 | "console_scripts": [ 45 | "topsupergroupsbot = topsupergroupsbot.__main__:main", 46 | ], 47 | }, 48 | 49 | include_package_data=True, 50 | zip_safe=False, 51 | 52 | classifiers=[ 53 | "Not on PyPI" 54 | ], 55 | 56 | ) 57 | -------------------------------------------------------------------------------- /topsupergroupsbot/get_lang.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot.langs import en, it, pt_br 18 | 19 | lang_obj = { 20 | "en": en, 21 | "it": it, 22 | "pt": pt_br 23 | } 24 | 25 | 26 | def get_string(lang, variable): 27 | """ 28 | returns the right string. example of usage: 29 | print(get_string("en", "test")) 30 | 'en' is the language of the user returned from the db 31 | '"test"' is the name of the variable in the relative file lang 32 | """ 33 | 34 | try: 35 | string = getattr(lang_obj[lang], variable) 36 | except AttributeError: 37 | string = getattr(en, variable) 38 | except KeyError: 39 | string = getattr(en, variable) 40 | return string 41 | 42 | 43 | def get_string_buttons(lang, variable): 44 | try: 45 | dct = getattr(lang_obj[lang], 'buttons_strings') 46 | except AttributeError: 47 | dct = getattr(en, 'buttons_strings') 48 | except KeyError: 49 | dct = getattr(en, 'buttons_strings') 50 | return dct[variable] 51 | 52 | -------------------------------------------------------------------------------- /config/config.example.yaml: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | # _________________________________________________________________________ 18 | # _________________________________________________________________________ 19 | 20 | 21 | # This is an example of the config.yaml file. Create a file like this. 22 | # name it `config.yaml` 23 | # replace the values with the right ones 24 | 25 | 26 | bot_token: "bot_token" 27 | 28 | redis_host: 'redis_host' 29 | redis_port: 1234 30 | redis_db: 1234 31 | 32 | min_db_connections: 5 33 | max_db_connections: 10 34 | db_user: 'db_user' 35 | db_password: 'db_password' 36 | db_name: 'db_name' 37 | 38 | # telegram IDs of the admins of the bot (list) 39 | admins: 40 | - 1234 41 | - 5678 42 | - 9012 43 | 44 | # telegram ID of the founder of the bot (more priviledges) 45 | founder: 1234 46 | 47 | # pairs of maximum allowed messages (key) in seconds interval (value) 48 | flood_checks: 49 | 1234: 1234 50 | 5678: 5678 51 | 9012: 9012 52 | 1357: 2468 53 | 54 | # this is optional. it has to contain the link of the official channel of the bot 55 | official_channel: "https://t.me/official_channel" 56 | 57 | # this is a text string containing text addresses for donations 58 | donate_addresses: "you can donate on mypaypal@account.com" 59 | -------------------------------------------------------------------------------- /topsupergroupsbot/config.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | import yaml 19 | import sys 20 | 21 | 22 | path = "config/config.yaml" 23 | if len(sys.argv) == 2: 24 | path = sys.argv[1] 25 | try: 26 | with open(path, 'r') as stream: 27 | conf = yaml.load(stream) 28 | except FileNotFoundError: 29 | print("\n\WARNING:\n" 30 | "before of running topsupergroupsbot you should create a file named `config.yaml`" 31 | " in `config`.\n\nOpen `config/config.example.yaml`\ncopy all\ncreate a file " 32 | "named `config.yaml`\nPaste and replace sample variables with true data." 33 | "\nIf the file is in another path, you can specify it as the first parameter." 34 | "\nExample: ") 35 | sys.exit() 36 | 37 | 38 | BOT_TOKEN = conf["bot_token"] 39 | 40 | MIN_DB_CONNECTIONS = conf["min_db_connections"] 41 | MAX_DB_CONNECTIONS = conf["max_db_connections"] 42 | DB_USER = conf["db_user"] 43 | DB_PASSWORD = conf["db_password"] 44 | DB_NAME = conf["db_name"] 45 | 46 | 47 | REDIS_HOST = conf["redis_host"] 48 | REDIS_PORT = conf["redis_port"] 49 | REDIS_DB = conf["redis_db"] 50 | 51 | ADMINS = conf["admins"] 52 | 53 | FOUNDER = conf["founder"] 54 | 55 | FLOOD_CHECKS = conf["flood_checks"] 56 | 57 | try: 58 | OFFICIAL_CHANNEL = conf["official_channel"] 59 | except KeyError: 60 | OFFICIAL_CHANNEL = None 61 | 62 | try: 63 | DONATE_ADDRESSES = conf["donate_addresses"] 64 | except KeyError: 65 | DONATE_ADDRESSES = None -------------------------------------------------------------------------------- /topsupergroupsbot/cleandb.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import database 18 | 19 | from telegram.ext.dispatcher import run_async 20 | 21 | CLEAN_INTERVAL = '1 month' 22 | 23 | 24 | @run_async 25 | def clean_db(bot, job): 26 | query = "DELETE FROM messages WHERE message_date < now() - interval %s" 27 | database.query_w(query, CLEAN_INTERVAL) 28 | 29 | query = "DELETE FROM members WHERE updated_date < now() - interval %s" 30 | database.query_w(query, CLEAN_INTERVAL) 31 | 32 | 33 | @run_async 34 | def check_bot_inside_in_inactive_groups(bot, job): 35 | 36 | 37 | 38 | 39 | 40 | 41 | query = """ 42 | SELECT 43 | rows_by_group.group_id, 44 | rows_by_group.message_date, 45 | rows_by_group.row 46 | FROM 47 | ( 48 | SELECT 49 | group_id, 50 | message_date, 51 | ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY message_date DESC) AS row 52 | FROM messages 53 | ) AS rows_by_group 54 | LEFT OUTER JOIN supergroups AS s 55 | USING (group_id) 56 | WHERE 57 | rows_by_group.row=1 58 | AND s.bot_inside IS TRUE 59 | AND rows_by_group.message_date < (NOW() - INTERVAL %s); 60 | """ 61 | 62 | interval = '3 days' 63 | lst = database.query_r(query, interval) 64 | 65 | start_in = 0 66 | for item in lst: 67 | start_in += 0.2 68 | group_id = item[0] 69 | job.job_queue.run_once(send_chat_action_inactive_group, start_in, context=[group_id]) 70 | 71 | 72 | @run_async 73 | def send_chat_action_inactive_group(bot, job): 74 | errors = [ 75 | "Forbidden: bot was kicked from the supergroup chat", 76 | "Forbidden: bot is not a member of the supergroup chat" 77 | ] 78 | group_id = job.context[0] 79 | try: 80 | bot.sendChatAction(chat_id=group_id, action='typing') 81 | except Exception as e: 82 | if e in errors: 83 | print(e) 84 | print('right usage in clean_db inactive groups') 85 | query = "UPDATE supergroups SET bot_inside=FALSE WHERE group_id=%s" 86 | database.query_w(query, group_id) 87 | else: 88 | print(e) 89 | print("cleandb inactive groups") 90 | 91 | 92 | -------------------------------------------------------------------------------- /topsupergroupsbot/votelink.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import constants 18 | from topsupergroupsbot import database 19 | from topsupergroupsbot import keyboards 20 | from topsupergroupsbot import get_lang 21 | from topsupergroupsbot import utils 22 | from topsupergroupsbot import emojis 23 | 24 | 25 | def create_vote_link(group_id): 26 | schema = "https://t.me/{}?start=vote{}".format(constants.GET_ME.username, group_id) 27 | return schema 28 | 29 | 30 | @utils.private_only 31 | def send_vote_by_link(bot, update, first_arg): 32 | group_id = first_arg.lower().replace("vote", "") 33 | user_id = update.message.from_user.id 34 | lang = utils.get_db_lang(user_id) 35 | 36 | query = """ 37 | SELECT s.group_id, s_ref.username, s_ref.title, v.vote, v.vote_date 38 | FROM supergroups AS s 39 | LEFT OUTER JOIN supergroups_ref AS s_ref -- right so None it's returned if not there 40 | ON s_ref.group_id = s.group_id 41 | LEFT OUTER JOIN votes AS v 42 | ON v.group_id = s.group_id 43 | AND v.user_id = %s 44 | WHERE s.group_id = %s 45 | """ 46 | 47 | extract = database.query_r(query, user_id, group_id, one=True) 48 | 49 | if extract is None: 50 | # the group does not exist otherwise anything is returned and if None is NULL 51 | text = get_lang.get_string(lang, "cant_vote_this") 52 | update.message.reply_text(text=text) 53 | return 54 | 55 | text = get_lang.get_string(lang, "vote_this_group").format( 56 | extract[0], extract[1], extract[2]) 57 | if extract[3] is not None and extract[4] is not None: 58 | stars = emojis.STAR*extract[3] 59 | date = utils.formatted_date_l(extract[4].date(), lang) 60 | text += "\n\n"+get_lang.get_string(lang, "already_voted").format(stars, date) 61 | 62 | if extract[3] and extract[4] is not None: 63 | reply_markup = keyboards.change_vote_kb(extract[0], lang) 64 | else: 65 | text += "\n\n" 66 | text += get_lang.get_string(lang, "vote_from_one_to_five") 67 | reply_markup = keyboards.vote_group_kb(extract[0], lang) 68 | update.message.reply_text(text=text, reply_markup=reply_markup) 69 | -------------------------------------------------------------------------------- /topsupergroupsbot/regular_buttons.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import constants as c 18 | from topsupergroupsbot import get_lang 19 | from topsupergroupsbot import utils 20 | from topsupergroupsbot import commands 21 | from topsupergroupsbot import keyboards 22 | 23 | 24 | class RegularButtons: 25 | def __init__(self, bot, update): 26 | self.bot = bot 27 | self.update = update 28 | self.string = self.button_string() 29 | self.key = self.get_corresponding_key() 30 | 31 | def button_string(self): 32 | return self.update.message.text[1:-1] 33 | 34 | def get_corresponding_key(self): 35 | for lang in get_lang.lang_obj: 36 | dct = getattr(get_lang.lang_obj[lang], 'buttons_strings') 37 | for key in dct: 38 | if dct[key] == self.string: 39 | return key 40 | return None 41 | 42 | def call_button_func(self): 43 | if self.key is None: 44 | self.unrecognized_button() 45 | elif self.key == 'leaderboard': 46 | commands.leaderboard(self.bot, self.update) 47 | elif self.key == 'about_you': 48 | commands.aboutyou(self.bot, self.update) 49 | elif self.key == 'region': 50 | commands.region(self.bot, self.update) 51 | elif self.key == 'settings': 52 | commands.settings(self.bot, self.update) 53 | elif self.key == 'info_and_help': 54 | commands.help(self.bot, self.update) 55 | 56 | def unrecognized_button(self): 57 | user_id = self.update.message.from_user.id 58 | lang = utils.get_db_lang(user_id) 59 | text = get_lang.get_string(lang, "unrecognized_button") 60 | reply_markup = keyboards.default_regular_buttons_kb(lang) 61 | self.update.message.reply_text(text=text, reply_markup=reply_markup) 62 | 63 | 64 | def is_button_syntax(bot, update): 65 | if not update.message: 66 | return False 67 | if not update.message.text: 68 | return False 69 | text = update.message.text 70 | if not (text.startswith(c.BUTTON_START) and text.endswith(c.BUTTON_END)): 71 | return False 72 | return True 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot 2 | A telegram bot for telegram public groups leaderboards 3 | 4 | 5 | 6 | ## What does this bot do? 7 | **This bot does statistics and leaderboards about public supergrous and their users.** 8 | 9 | 10 | This bot can be added in telegram public groups. The creator of the group can set the language and some other options. 11 | Texting the bot in private chat, you can get **statistics about you** and **groups leaderboard**. Users can choose the order of the groups by: 12 | 13 | - number of members 14 | - number of sent messages during the current week 15 | - votes average (Users can vote groups) 16 | 17 | Groups are filtered by regions. 18 | Leaderboards are shown with beatiful pages like for a website. 19 | 20 | ## Screenshots 21 | _The groups you can see in the following screenshots are real groups and they are created by users not related with this project, but they just added the bot in the group to take part of the leaderboard. So i don't have any responsibilities about their names or their topics._ 22 | 23 | 24 | 25 | ## Commmands 26 | 27 | ### private chat: 28 | ``` 29 | /leaderboard - check out supergroups leaderboards 30 | /vote - vote a group 31 | /aboutyou - get stats about you 32 | /settings - change your settings 33 | /feedback - send a feedback 34 | /help - get an help command 35 | ``` 36 | 37 | ### groups: 38 | ``` 39 | /settings - set group settings 40 | /groupleaderboard - get a message containing leaderboard of users that wrotemore messages in the group during the current week (UTC) 41 | /grouprank - Check the rank of the group 42 | ``` 43 | 44 | 45 | ## How to install: 46 | 47 | ### On Linux: 48 | 49 | - Move to the path where you want to create the virtualenv directory 50 | ``` 51 | cd path 52 | ``` 53 | - Create a folder containing the env named `tsbenv` 54 | ``` 55 | virtualenv -p python3 tsbenv 56 | ``` 57 | - Install the bot from the zip 58 | ``` 59 | tsbenv/bin/pip install https://github.com/91dariodev/topsupergroupsbot/archive/master.zip 60 | ``` 61 | - Run the bot. The first parameter of the command is the `config.yaml` file. Copy from the source `config.example.yaml` and create a file named `config.yaml` replacing values. 62 | ``` 63 | tsbenv/bin/topsupergroupsbot path/config.yaml 64 | ``` 65 | - To upgrade the bot: 66 | ``` 67 | tsbenv/bin/pip install --upgrade https://github.com/91dariodev/topsupergroupsbot/archive/master.zip 68 | ``` 69 | - To delete everything: 70 | ``` 71 | cd .. 72 | rm -rf tsbenv 73 | ``` 74 | 75 | 76 | ## License: 77 | 78 | TopSupergroupsBot is free software: you can redistribute it and/or modify 79 | it under the terms of the GNU Affero General Public License as published 80 | by the Free Software Foundation, either version 3 of the License, or 81 | (at your option) any later version. 82 | 83 | TopSupergroupsBot is distributed in the hope that it will be useful, 84 | but WITHOUT ANY WARRANTY; without even the implied warranty of 85 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 86 | GNU Affero General Public License for more details. 87 | 88 | You should have received a copy of the GNU Affero General Public License 89 | along with TopSupergroupsBot. If not, see . -------------------------------------------------------------------------------- /topsupergroupsbot/antiflood.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | import time 19 | from topsupergroupsbot import database as db 20 | 21 | 22 | class Antiflood: 23 | def __init__(self, limit, interval, group_id, user_id): 24 | self.limit = limit 25 | self.interval = interval 26 | self.group_id = group_id 27 | self.user_id = user_id 28 | self.flood_key = self.flood_key() 29 | 30 | def flood_key(self): 31 | """ 32 | return a redis key to store the amount of sent messages 33 | during the interval. The key is unique during the interval. 34 | """ 35 | key = "af:{}:{}:{}:{}:{}".format( 36 | self.group_id, 37 | self.user_id, 38 | (time.time()//self.interval), 39 | self.interval, 40 | self.limit) 41 | return key 42 | 43 | def expire_key(self): 44 | """set a time for redis key expiration""" 45 | db.REDIS.expire(self.flood_key, self.interval) 46 | 47 | def get_time_key_started(self): 48 | """ 49 | return the lower value of the interval of time 50 | when the key has been created. 51 | """ 52 | return float(self.flood_key.split(":")[3])*self.interval 53 | 54 | def del_messages_from_db(self): 55 | """ 56 | Delete from the database all the messages sent during 57 | the interval of the key 58 | """ 59 | query = """ 60 | DELETE FROM messages 61 | WHERE (msg_id, group_id) IN ( 62 | SELECT msg_id, group_id 63 | FROM messages 64 | WHERE 65 | user_id = %s 66 | AND group_id = %s 67 | AND message_date >= to_timestamp(%s) 68 | ORDER BY message_date DESC 69 | LIMIT %s 70 | ) 71 | """ 72 | db.query_w( 73 | query, 74 | self.user_id, 75 | self.group_id, 76 | self.get_time_key_started(), 77 | (self.limit-1)) 78 | 79 | def is_flood(self): 80 | """ 81 | return True if the user is flooding. 82 | If there is not a key, it sets the key and the expiration time. 83 | If the user hit the limit, delete messages sent during the interval 84 | from the db. 85 | """ 86 | value = db.REDIS.incr(self.flood_key, amount=1) 87 | 88 | if value == 1: 89 | # the key has been created now 90 | self.expire_key() 91 | return False 92 | 93 | elif value == self.limit: 94 | self.del_messages_from_db() 95 | print("flood hit in {}".format(self.flood_key)) 96 | return True 97 | 98 | elif value > self.limit: 99 | return True 100 | 101 | -------------------------------------------------------------------------------- /topsupergroupsbot/cache_users_stats.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import time 18 | import json 19 | 20 | from topsupergroupsbot import database as db 21 | from collections import OrderedDict 22 | 23 | from telegram.ext.dispatcher import run_async 24 | 25 | CACHE_SECONDS = 60*3 26 | LATEST_UPDATE_KEY = 'latest_update' 27 | REDIS_KEY = 'cached_users' 28 | 29 | def group_extract(lst): 30 | # thank https://stackoverflow.com/a/46493187/8372336 for the help in this func 31 | # and a bit modified by me 32 | d = OrderedDict() 33 | 34 | for k, *v in lst: 35 | k = k, *v[:4] 36 | d.setdefault(k, []).append(v[4:]) 37 | 38 | final = list(d.items()) 39 | return final 40 | 41 | 42 | @run_async 43 | def cache_users_stats(bot, job): 44 | at_seconds = time.time() 45 | lst = get_all_users_stats() 46 | dct = {} 47 | for i in lst: 48 | user_id = i[0][0] 49 | dct[user_id] = json.dumps(i).encode('UTF-8') 50 | dct[LATEST_UPDATE_KEY] = at_seconds 51 | db.REDIS.hmset(REDIS_KEY, dct) 52 | db.REDIS.expire(REDIS_KEY, CACHE_SECONDS*2) 53 | 54 | 55 | def get_cached_user(user_id): 56 | user_cache, latest_update = db.REDIS.hmget(REDIS_KEY, user_id, LATEST_UPDATE_KEY) 57 | user_cache = user_cache if user_cache is None else json.loads(user_cache.decode('UTF-8')) 58 | latest_update = latest_update if latest_update is None else float(latest_update.decode('UTF-8')) 59 | return user_cache, latest_update 60 | 61 | def get_all_users_stats(): 62 | query = """ 63 | WITH tleft AS ( 64 | SELECT main.user_id, u.lang, main.num_msgs, main.num_grps, main.rnk 65 | FROM ( 66 | SELECT 67 | user_id, 68 | num_grps, 69 | num_msgs, 70 | RANK() OVER(ORDER BY num_msgs DESC, num_grps DESC, user_id DESC) rnk 71 | FROM ( 72 | SELECT 73 | user_id, 74 | COUNT(distinct group_id) AS num_grps, 75 | COUNT(*) AS num_msgs 76 | FROM messages 77 | WHERE message_date > date_trunc('week', now()) 78 | GROUP BY user_id 79 | ) AS sub 80 | ) AS main 81 | LEFT OUTER JOIN users AS u 82 | USING (user_id) 83 | WHERE u.weekly_own_digest = TRUE 84 | AND bot_blocked = FALSE 85 | ) 86 | , tright AS ( 87 | SELECT main.user_id, main.group_id, s_ref.title, s_ref.username, main.m_per_group, main.pos 88 | FROM ( 89 | SELECT user_id, group_id, COUNT(user_id) AS m_per_group, 90 | RANK() OVER ( 91 | PARTITION BY group_id 92 | ORDER BY COUNT(group_id) DESC 93 | ) AS pos 94 | FROM messages 95 | WHERE message_date > date_trunc('week', now()) 96 | GROUP BY group_id, user_id 97 | ) AS main 98 | LEFT OUTER JOIN supergroups_ref AS s_ref 99 | USING (group_id) 100 | ORDER BY m_per_group DESC 101 | ) 102 | SELECT l.user_id, l.lang, l.num_msgs, l.num_grps, l.rnk, r.title, r.username, r.m_per_group, r.pos 103 | FROM tleft AS l 104 | INNER JOIN tright AS r 105 | USING (user_id) 106 | """ 107 | return group_extract(db.query_r(query)) 108 | # for i in extract: i[0] = (user_id, lang, msg, grps, pos) 109 | # i[1] = [[],[]] anyone inside = [groupname, groupusername, msg in group, pos in group] 110 | 111 | -------------------------------------------------------------------------------- /topsupergroupsbot/memberslog.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import database 18 | 19 | from telegram.error import (TelegramError, Unauthorized, BadRequest, TimedOut, 20 | ChatMigrated, NetworkError) 21 | 22 | from telegram.ext.dispatcher import run_async 23 | 24 | 25 | INTERVAL = 5 26 | 27 | DEADLINE = '5 hours' # so if i restart the bot they are not updated after few time 28 | 29 | 30 | @run_async 31 | def members_log(bot, job): 32 | bulk_list = get_groups_to_log(bot, job) # retrieve groups to update 33 | set_bulk(bot, job, bulk_list) # set a bulk one by one with delay 34 | 35 | 36 | def get_groups_to_log(bot, job): 37 | query = """ 38 | WITH m_table AS ( 39 | SELECT 40 | last_members.group_id, 41 | last_members.updated_date 42 | FROM 43 | ( 44 | SELECT 45 | *, 46 | ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY updated_date DESC) AS row 47 | FROM members 48 | ) AS last_members 49 | WHERE last_members.row=1 50 | ) 51 | SELECT 52 | s.group_id 53 | FROM supergroups AS s 54 | LEFT OUTER JOIN m_table AS m 55 | USING (group_id) 56 | WHERE 57 | s.bot_inside = TRUE 58 | AND (m.updated_date < (now() - interval %s) OR m.updated_date IS NULL) 59 | ORDER BY m.updated_date ASC NULLS FIRST 60 | """ 61 | 62 | extract = database.query_r(query, DEADLINE) 63 | return extract 64 | 65 | 66 | def set_bulk(bot, job, extract): 67 | start_in = 0 68 | for i in extract: 69 | group_id = i[0] 70 | start_in += INTERVAL 71 | job.job_queue.run_once(handle_one_by_one, start_in, context=group_id) 72 | 73 | 74 | def handle_one_by_one(bot, job): 75 | group_id = job.context 76 | 77 | try: 78 | info = bot.getChat(group_id) 79 | members = bot.getChatMembersCount(group_id) 80 | 81 | query = """ 82 | INSERT INTO members(group_id, amount, updated_date) 83 | VALUES(%s, %s, now()) 84 | """ 85 | database.query_w(query, group_id, members) 86 | 87 | query = """ 88 | INSERT INTO 89 | supergroups_ref(group_id, title, username) 90 | VALUES (%s, %s, %s) 91 | ON CONFLICT (group_id) DO 92 | UPDATE SET title = %s, username = COALESCE(%s, supergroups_ref.username) 93 | WHERE supergroups_ref.group_id = %s""" 94 | 95 | database.query_w( 96 | query, group_id, info.title, info.username, info.title, 97 | info.username, group_id) 98 | 99 | except Unauthorized: 100 | query = """ 101 | UPDATE supergroups 102 | SET bot_inside = FALSE 103 | WHERE group_id = %s 104 | """ 105 | database.query_w(query, group_id) 106 | 107 | except BadRequest as e: 108 | if str(e) == "Chat not found": 109 | query = """ 110 | UPDATE supergroups 111 | SET bot_inside = FALSE 112 | WHERE group_id = %s 113 | """ 114 | database.query_w(query, group_id) 115 | else: 116 | print("{} in memberslog BadRequest: group_id: {}".format(e, group_id)) 117 | 118 | 119 | except Exception as e: 120 | print("{} in memberslog: group_id: {}".format(e, group_id)) 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /topsupergroupsbot/messages.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | from topsupergroupsbot import feedback 19 | from topsupergroupsbot import messages_private 20 | from topsupergroupsbot import messages_supergroups 21 | from topsupergroupsbot import config 22 | from topsupergroupsbot import regular_buttons 23 | from topsupergroupsbot.antiflood import Antiflood 24 | 25 | from telegram.ext import DispatcherHandlerStop 26 | 27 | 28 | def before_processing(bot, update): 29 | if update.message.chat.type == "private": 30 | before_processing_private(bot, update) 31 | 32 | elif update.message.chat.type == "supergroup": 33 | before_processing_supergroups(bot, update) 34 | 35 | 36 | def processing(bot, update): 37 | if update.message.chat.type == "private": 38 | processing_private(bot, update) 39 | 40 | elif update.message.chat.type == "supergroup": 41 | processing_supergroups(bot, update) 42 | 43 | 44 | # _ __ _ 45 | # | |__ ___ / _|___ _ _ ___ _ __ _ _ ___ __ ___ _____(_)_ _ __ _ 46 | # | '_ \/ -_) _/ _ \ '_/ -_) | '_ \ '_/ _ \/ _/ -_|_-<_-< | ' \/ _` | 47 | # |_.__/\___|_| \___/_| \___| | .__/_| \___/\__\___/__/__/_|_||_\__, | 48 | # |_| |___/ 49 | 50 | 51 | def before_processing_supergroups(bot, update): 52 | # leave if the group is not public 53 | if messages_supergroups.leave_unsupported_chat(bot, update): 54 | raise DispatcherHandlerStop 55 | 56 | # check if the group is not banned, otherwise leave 57 | if messages_supergroups.this_bot_has_been_added(bot, update): 58 | if messages_supergroups.is_banned(bot, update): 59 | messages_supergroups.leave_banned_group(bot, update) 60 | raise DispatcherHandlerStop 61 | 62 | # log stuff on the tables 63 | lang = messages_supergroups.add_supergroup_db(bot, update) 64 | messages_supergroups.add_user_ref(bot, update) 65 | messages_supergroups.add_supergroup_ref(bot, update) 66 | 67 | # check if the bot has been added and send a welcome message 68 | if messages_supergroups.this_bot_has_been_added(bot, update): 69 | if lang is None: 70 | messages_supergroups.choose_group_language(bot, update) 71 | else: 72 | messages_supergroups.added_again_message(bot, update, lang) 73 | # if it hasn't been added remember to set the language 74 | # this is run even for commands (before processing) 75 | else: 76 | if lang is None: 77 | messages_supergroups.remember_to_set_lang(bot, update) 78 | 79 | 80 | def before_processing_private(bot, update): 81 | messages_private.add_user_db(bot, update) 82 | 83 | if feedback.is_a_feedback(bot, update): 84 | feedback.handle_receive_feedback(bot, update) 85 | raise DispatcherHandlerStop # nothing should be done anymore 86 | 87 | if feedback.is_a_feedback_reply(bot, update): 88 | feedback.handle_reply_feedback(bot, update) 89 | 90 | 91 | # _ 92 | # _ __ _ _ ___ __ ___ _____(_)_ _ __ _ 93 | # | '_ \ '_/ _ \/ _/ -_|_-<_-< | ' \/ _` | 94 | # | .__/_| \___/\__\___/__/__/_|_||_\__, | 95 | # |_| |___/ 96 | 97 | 98 | def processing_supergroups(bot, update): 99 | user_id = update.message.from_user.id 100 | group_id = update.message.chat.id 101 | # check if is flood and handle flood 102 | for i in config.FLOOD_CHECKS: 103 | af = Antiflood( 104 | limit=i, 105 | interval=config.FLOOD_CHECKS[i], 106 | user_id=user_id, 107 | group_id=group_id) 108 | if af.is_flood(): 109 | raise DispatcherHandlerStop 110 | 111 | # log message in the database 112 | messages_supergroups.add_message_db(bot, update) 113 | messages_supergroups.ee(bot, update) 114 | 115 | 116 | def processing_private(bot, update): 117 | if regular_buttons.is_button_syntax(bot, update): 118 | button = regular_buttons.RegularButtons(bot, update) 119 | button.call_button_func() 120 | -------------------------------------------------------------------------------- /topsupergroupsbot/pages.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import math 18 | from topsupergroupsbot import keyboards 19 | 20 | from telegram import InlineKeyboardButton 21 | from telegram import InlineKeyboardMarkup 22 | 23 | class Pages: 24 | def __init__(self, lst, chosen_page=1, elements_per_page=10): 25 | self.elements_per_page = elements_per_page 26 | self.lst = lst 27 | self.number_of_pages = self.get_number_of_pages() 28 | self.chosen_page = self._adjust_chosen_page(chosen_page) 29 | 30 | def get_number_of_pages(self): 31 | return math.ceil(len(self.lst)/self.elements_per_page) 32 | 33 | def _adjust_chosen_page(self, chosen_page): 34 | if self.number_of_pages == 0: 35 | chosen_page = 1 36 | if chosen_page > self.number_of_pages: 37 | chosen_page = self.number_of_pages 38 | return chosen_page 39 | 40 | def first_number_of_page(self): 41 | return self.chosen_page*self.elements_per_page - self.elements_per_page + 1 42 | 43 | def displayed_pages(self): 44 | """ 45 | Returns a list containing the numbers from 1 to `total_pages`, with 46 | `chosen_page` indicated, and abbreviated if too long. 47 | """ 48 | 49 | max_on_line_number = 5 50 | 51 | # Build list of pages to display 52 | pages = [] 53 | 54 | if self.number_of_pages == 0: 55 | return pages 56 | if self.chosen_page > self.number_of_pages: 57 | self.chosen_page = self.number_of_pages 58 | 59 | if self.number_of_pages <= max_on_line_number: 60 | for i in range(1, self.number_of_pages + 1): 61 | pages.append(i) 62 | else: 63 | pages.append(1) # add first page 64 | 65 | if self.chosen_page - 1 > 1: 66 | pages.append(self.chosen_page - 1) # add before current page 67 | 68 | if self.chosen_page != 1 and self.chosen_page != self.number_of_pages: 69 | pages.append(self.chosen_page) # add current page - 1 and last are added with other stuff 70 | 71 | if self.chosen_page + 1 < self.number_of_pages: 72 | pages.append(self.chosen_page + 1) # add after current page 73 | 74 | pages.append(self.number_of_pages) # add last page 75 | return pages 76 | 77 | def chosen_page_items(self): 78 | offset = self.first_number_of_page() - 1 79 | return self.lst[offset:offset+self.elements_per_page] 80 | 81 | def build_buttons(self, base, only_admins=False, footer_buttons=None): 82 | pages = self.displayed_pages() 83 | chosen_page = self.chosen_page 84 | texted_pages = [] 85 | 86 | 87 | 88 | for page in pages: 89 | callback_data = base.format(page=page) 90 | current_page = "current_page_admin" if only_admins is True else "current_page" 91 | # this is necessary not to get list out of range 92 | # because later it checks pages[1] 93 | 94 | if len(pages) <= 1: 95 | # keyboard is not needed if one page 96 | break 97 | 98 | if page == chosen_page: 99 | texted_pages.append(InlineKeyboardButton( 100 | text="•{}•".format(page), 101 | callback_data=current_page)) 102 | continue 103 | 104 | if page == pages[0] and pages[1] > 2: 105 | texted_pages.append(InlineKeyboardButton( 106 | text="«"+str(page), 107 | callback_data=callback_data)) 108 | continue 109 | 110 | if page == pages[-1] and pages[-2] < (pages[-1] - 1): 111 | texted_pages.append(InlineKeyboardButton( 112 | text=str(page)+"»", 113 | callback_data=callback_data)) 114 | continue 115 | 116 | texted_pages.append(InlineKeyboardButton( 117 | text=str(page), 118 | callback_data=callback_data)) 119 | 120 | keyboard = InlineKeyboardMarkup( 121 | keyboards.build_menu( 122 | texted_pages, 123 | n_cols=8, 124 | footer_buttons=footer_buttons 125 | ) 126 | ) 127 | return keyboard 128 | -------------------------------------------------------------------------------- /topsupergroupsbot/digest_private.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | from topsupergroupsbot import database 18 | from topsupergroupsbot import get_lang 19 | from topsupergroupsbot import keyboards 20 | from topsupergroupsbot import utils 21 | 22 | from collections import OrderedDict 23 | 24 | from telegram.error import (TelegramError, Unauthorized, BadRequest, TimedOut, 25 | ChatMigrated, NetworkError) 26 | from telegram.ext.dispatcher import run_async 27 | 28 | 29 | def group_extract(lst): 30 | # thank https://stackoverflow.com/a/46493187/8372336 for the help in this func 31 | # and a bit modified by me 32 | d = OrderedDict() 33 | 34 | for k, *v in lst: 35 | k = k, *v[:5] 36 | d.setdefault(k, []).append(v[5:]) 37 | 38 | final = list(d.items()) 39 | return final 40 | 41 | 42 | @run_async 43 | def weekly_own_private(bot, job): 44 | interval = '7 days' 45 | 46 | query = """ 47 | WITH tleft AS ( 48 | SELECT 49 | main.user_id, 50 | u.lang, 51 | main.num_msgs, 52 | main.num_grps, 53 | main.rnk, 54 | u_ref.name 55 | FROM ( 56 | SELECT 57 | user_id, 58 | num_grps, 59 | num_msgs, 60 | RANK() OVER(ORDER BY num_msgs DESC, num_grps DESC, user_id DESC) rnk 61 | FROM ( 62 | SELECT 63 | user_id, 64 | COUNT(distinct group_id) AS num_grps, 65 | COUNT(*) AS num_msgs 66 | FROM messages 67 | WHERE message_date > now() - interval %s 68 | GROUP BY user_id 69 | ) AS sub 70 | ) AS main 71 | LEFT OUTER JOIN users AS u 72 | USING (user_id) 73 | LEFT OUTER JOIN users_ref AS u_ref 74 | USING (user_id) 75 | WHERE u.weekly_own_digest = TRUE 76 | AND bot_blocked = FALSE 77 | ) 78 | , tright AS ( 79 | SELECT 80 | main.user_id, 81 | main.group_id, 82 | s_ref.title, 83 | s_ref.username, 84 | main.m_per_group, 85 | main.pos 86 | FROM ( 87 | SELECT 88 | user_id, 89 | group_id, 90 | COUNT(user_id) AS m_per_group, 91 | RANK() OVER ( 92 | PARTITION BY group_id 93 | ORDER BY COUNT(group_id) DESC 94 | ) AS pos 95 | FROM messages 96 | WHERE message_date > now() - interval %s 97 | GROUP BY group_id, user_id 98 | ) AS main 99 | LEFT OUTER JOIN supergroups_ref AS s_ref 100 | USING (group_id) 101 | ORDER BY m_per_group DESC 102 | ) 103 | SELECT 104 | l.user_id, 105 | l.lang, 106 | l.num_msgs, 107 | l.num_grps, 108 | l.rnk, 109 | l.name, 110 | r.title, 111 | r.username, 112 | r.m_per_group, 113 | r.pos 114 | FROM tleft AS l 115 | INNER JOIN tright AS r 116 | USING (user_id) 117 | """ 118 | 119 | # it returns the global stuff for all the users that want the private digist own 120 | extract = database.query_r(query, interval, interval) 121 | data = (group_extract(extract)) 122 | schedule_own_private_digest(bot, job, data) 123 | # for i in extract: i[0] = (user_id, lang, msg, grps, pos) 124 | # i[1] = [[],[]] anyone inside = [groupname, groupusername, msg in group, pos in group] 125 | 126 | 127 | def schedule_own_private_digest(bot, job, data): 128 | start_in = 0 129 | for i in data: 130 | start_in += 0.1 131 | user = i[0] 132 | groups = i[1] 133 | 134 | user_id = user[0] 135 | lang = user[1] 136 | tot_msg = user[2] 137 | tot_grps = user[3] 138 | tot_pos = user[4] 139 | first_name = user[5] 140 | 141 | text = get_lang.get_string(lang, "hello_name").format(name=first_name)+"!\n" 142 | text += get_lang.get_string(lang, "digest_of_the_week_global").format( 143 | utils.sep_l(tot_msg, lang), 144 | utils.sep_l(tot_grps, lang), 145 | utils.sep_l(tot_pos, lang) 146 | ) 147 | reply_markup = keyboards.disable_private_own_weekly_digest_kb(lang) 148 | # append the text of any group for the same user 149 | for group in groups: 150 | title = group[0] 151 | username = group[1] 152 | msg_g = group[2] 153 | pos_g = group[3] 154 | text += get_lang.get_string(lang, "digest_of_the_week_detail").format( 155 | utils.sep_l(msg_g, lang), 156 | username, 157 | utils.sep_l(pos_g, lang) 158 | ) 159 | text += "\n#weekly_private_digest" 160 | # text completed can be scheduled 161 | job.job_queue.run_once(send_one_by_one, start_in, context=[user_id, text, reply_markup]) 162 | 163 | 164 | @run_async 165 | def send_one_by_one(bot, job): 166 | user_id = job.context[0] 167 | message = job.context[1] 168 | reply_markup = job.context[2] 169 | try: 170 | utils.send_message_long( 171 | bot, 172 | chat_id=user_id, 173 | text=message, 174 | reply_markup=reply_markup, 175 | disable_notification=True) 176 | except Unauthorized: 177 | query = "UPDATE users SET bot_blocked = TRUE WHERE user_id = %s" 178 | database.query_w(query, user_id) 179 | except Exception as e: 180 | print("{} exception is send_one_by_one private own digest".format(e)) 181 | -------------------------------------------------------------------------------- /topsupergroupsbot/database.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import threading 18 | import redis 19 | import psycopg2 20 | from psycopg2 import pool 21 | from threading import Semaphore 22 | 23 | from topsupergroupsbot import config 24 | 25 | class ReallyThreadedConnectionPool(psycopg2.pool.ThreadedConnectionPool): 26 | def __init__(self, minconn, maxconn, *args, **kwargs): 27 | self._semaphore = Semaphore(maxconn) 28 | super().__init__(minconn, maxconn, *args, **kwargs) 29 | 30 | def getconn(self, *args, **kwargs): 31 | self._semaphore.acquire() 32 | return super().getconn(*args, **kwargs) 33 | 34 | def putconn(self, *args, **kwargs): 35 | super().putconn(*args, **kwargs) 36 | self._semaphore.release() 37 | 38 | # _ _ 39 | # __ ___ _ _ _ _ ___ __| |_(_)___ _ _ 40 | # / _/ _ \ ' \| ' \/ -_) _| _| / _ \ ' \ 41 | # \__\___/_||_|_||_\___\__|\__|_\___/_||_| 42 | # 43 | 44 | 45 | REDIS = redis.Redis(host=config.REDIS_HOST, 46 | port=config.REDIS_PORT, 47 | db=config.REDIS_DB) 48 | 49 | 50 | 51 | DB_POOL_CONNECTIONS = ReallyThreadedConnectionPool( 52 | minconn=config.MIN_DB_CONNECTIONS, 53 | maxconn=config.MAX_DB_CONNECTIONS, 54 | user = config.DB_USER, 55 | password = config.DB_PASSWORD, 56 | dbname = config.DB_NAME 57 | ) 58 | 59 | 60 | # _ _ 61 | # __ __ ___ _ __ _ _ __ _ __ ___ __| | __ _ _ _ ___ _ _(_)___ ___ 62 | # \ V V / '_/ _` | '_ \ '_ \/ -_) _` | / _` | || / -_) '_| / -_|_-< 63 | # \_/\_/|_| \__,_| .__/ .__/\___\__,_| \__, |\_,_\___|_| |_\___/__/ 64 | # |_| |_| |_| 65 | 66 | 67 | 68 | def query(query, *params, one=False, read=False): 69 | connect = DB_POOL_CONNECTIONS.getconn() 70 | c = connect.cursor() 71 | try: 72 | c.execute(query, params) 73 | c.connection.commit() 74 | if read: 75 | if not one: 76 | return c.fetchall() 77 | else: 78 | return c.fetchone() 79 | except: 80 | c.connection.rollback() 81 | raise 82 | finally: 83 | DB_POOL_CONNECTIONS.putconn(connect) 84 | 85 | # for retrocompatibilty 86 | def query_w(raw_query, *params): 87 | query(raw_query, *params) 88 | 89 | 90 | # for retrocompatibility 91 | def query_r(raw_query, *params, one=False): 92 | return query(raw_query, *params, one=one, read=True) 93 | 94 | 95 | # because the commit is now added in the query_r too 96 | query_wr = query_r 97 | 98 | 99 | # _ _ _ 100 | # __ _ _ ___ __ _| |_ ___ __| | |__ 101 | # / _| '_/ -_) _` | _/ -_) / _` | '_ \ 102 | # \__|_| \___\__,_|\__\___| \__,_|_.__/ 103 | # 104 | 105 | def create_db(): 106 | 107 | # for private purposes 108 | 109 | query = """CREATE TABLE IF NOT EXISTS users( 110 | user_id BIGINT PRIMARY KEY, 111 | lang TEXT DEFAULT 'en', 112 | region TEXT DEFAULT NULL, 113 | tg_lang TEXT DEFAULT NULL, 114 | bot_blocked BOOLEAN DEFAULT FALSE, 115 | banned_on TIMESTAMP DEFAULT NULL, 116 | banned_until TIMESTAMP DEFAULT NULL, 117 | weekly_own_digest BOOLEAN DEFAULT TRUE, 118 | weekly_groups_digest TEXT [], 119 | message_date timestamp, 120 | registered_at TIMESTAMP DEFAULT NOW() 121 | )""" 122 | query_w(query) 123 | 124 | query = """CREATE TABLE IF NOT EXISTS supergroups( 125 | group_id BIGINT PRIMARY KEY, 126 | lang TEXT DEFAULT NULL, 127 | nsfw BOOLEAN DEFAULT FALSE, 128 | weekly_digest BOOLEAN DEFAULT TRUE, 129 | joined_the_bot timestamp DEFAULT NULL, 130 | banned_on TIMESTAMP DEFAULT NULL, 131 | banned_until TIMESTAMP DEFAULT NULL, 132 | ban_reason TEXT DEFAULT NULL, 133 | bot_inside BOOLEAN DEFAULT TRUE, 134 | last_date timestamp DEFAULT NULL, 135 | category TEXT DEFAULT NULL 136 | )""" 137 | query_w(query) 138 | 139 | # -------------------------- 140 | 141 | # to collect messages for stats 142 | 143 | query = """CREATE TABLE IF NOT EXISTS messages( 144 | msg_id BIGINT, 145 | group_id BIGINT, 146 | user_id BIGINT, 147 | message_date timestamp, 148 | PRIMARY KEY (msg_id, group_id) 149 | )""" 150 | query_w(query) 151 | 152 | # -------------------------- 153 | 154 | # collect any user (also if didn't start the bot), to log info for many stuff 155 | 156 | query = """CREATE TABLE IF NOT EXISTS users_ref( 157 | user_id BIGINT PRIMARY KEY, 158 | name TEXT DEFAULT NULL, 159 | last_name TEXT DEFAULT NULL, 160 | username TEXT DEFAULT NULL, 161 | tg_lang TEXT DEFAULT NULL, 162 | message_date timestamp 163 | )""" 164 | query_w(query) 165 | 166 | query = """CREATE TABLE IF NOT EXISTS supergroups_ref( 167 | group_id BIGINT PRIMARY KEY, 168 | title TEXT DEFAULT NULL, 169 | username TEXT DEFAULT NULL, 170 | message_date timestamp 171 | )""" 172 | query_w(query) 173 | 174 | # -------------------------- 175 | 176 | # for reviews 177 | 178 | query = """ CREATE TABLE IF NOT EXISTS votes( 179 | user_id BIGINT, 180 | group_id BIGINT, 181 | vote SMALLINT, 182 | vote_date timestamp, 183 | PRIMARY KEY (user_id, group_id) 184 | )""" 185 | query_w(query) 186 | 187 | # -------------------------- 188 | 189 | # for members 190 | 191 | query = """ 192 | CREATE TABLE IF NOT EXISTS members( 193 | group_id BIGINT, 194 | amount INT, 195 | updated_date timestamp 196 | ) 197 | """ 198 | query_w(query) 199 | 200 | 201 | # _ _ 202 | # (_)_ _ __| |_____ __ 203 | # | | ' \/ _` / -_) \ / 204 | # |_|_||_\__,_\___/_\_\ 205 | # 206 | 207 | 208 | def create_index(): 209 | 210 | ############### 211 | # 212 | # MESSAGES 213 | # 214 | ############### 215 | 216 | query = "CREATE INDEX IF NOT EXISTS index_messages_msg_id ON messages (msg_id)" 217 | query_w(query) 218 | query = "CREATE INDEX IF NOT EXISTS index_messages_group_id ON messages (group_id)" 219 | query_w(query) 220 | query = "CREATE INDEX IF NOT EXISTS index_messages_user_id ON messages (user_id)" 221 | query_w(query) 222 | query = "CREATE INDEX IF NOT EXISTS index_messages_message_date ON messages (message_date)" 223 | query_w(query) 224 | 225 | ####################### 226 | # 227 | # SUPERGROUPS_REF 228 | # 229 | ####################### 230 | 231 | query = "CREATE INDEX IF NOT EXISTS index_supergroups_ref_username ON supergroups_ref (username)" 232 | query_w(query) 233 | 234 | 235 | 236 | 237 | create_db() 238 | create_index() -------------------------------------------------------------------------------- /topsupergroupsbot/cache_groups_rank.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import time 18 | import json 19 | 20 | from topsupergroupsbot import database 21 | from topsupergroupsbot import leaderboards 22 | 23 | from telegram.ext.dispatcher import run_async 24 | 25 | CACHE_SECONDS = 60*3 26 | 27 | CACHE_KEY = 'cached_groups_rank' 28 | BY_MESSAGES = 'by_messages' 29 | BY_MEMBERS = 'by_members' 30 | BY_VOTES = 'by_votes' 31 | RANK = 'rank' 32 | CACHED_AT = 'cached_at' 33 | REGION = 'region' 34 | VALUE = 'value' 35 | 36 | 37 | def filling_dict(dct_name, group_id, by, position, region, cached_at, value): 38 | data = {RANK: position, CACHED_AT: cached_at, REGION: region, VALUE: value} 39 | try: 40 | dct_name[group_id][by] = data 41 | except KeyError: 42 | dct_name[group_id] = {} 43 | dct_name[group_id][by] = data 44 | return dct_name 45 | 46 | 47 | @run_async 48 | def caching_ranks(bot, job): 49 | ############# 50 | # MESSAGES 51 | ############ 52 | 53 | query = """ 54 | SELECT 55 | group_id, 56 | COUNT(msg_id) AS msgs, 57 | RANK() OVER(PARTITION BY s.lang ORDER BY COUNT(msg_id) DESC), 58 | s.lang 59 | FROM messages 60 | LEFT OUTER JOIN supergroups as s 61 | USING (group_id) 62 | WHERE 63 | message_date > date_trunc('week', now()) 64 | AND (s.banned_until IS NULL OR s.banned_until < now()) 65 | AND s.bot_inside IS TRUE 66 | GROUP BY s.lang, group_id 67 | 68 | """ 69 | msgs_this_week = database.query_r(query) 70 | 71 | 72 | ################## 73 | # MEMBERS 74 | ################## 75 | 76 | query = """ 77 | SELECT 78 | last_members.group_id, 79 | last_members.amount, 80 | RANK() OVER(PARTITION BY s.lang ORDER BY last_members.amount DESC), 81 | s.lang, 82 | extract(epoch from last_members.updated_date at time zone 'utc') 83 | FROM 84 | ( 85 | SELECT 86 | *, 87 | ROW_NUMBER() OVER ( 88 | PARTITION BY group_id 89 | ORDER BY updated_date DESC 90 | ) AS row 91 | FROM members 92 | ) AS last_members 93 | LEFT OUTER JOIN supergroups AS s 94 | USING (group_id) 95 | WHERE 96 | last_members.row=1 97 | AND (s.banned_until IS NULL OR s.banned_until < now()) 98 | AND s.bot_inside IS TRUE 99 | """ 100 | members_this_week = database.query_r(query) 101 | 102 | #################### 103 | # SUM AND AVG VOTES 104 | #################### 105 | 106 | query = """ 107 | WITH myconst AS 108 | (SELECT 109 | s.lang, 110 | AVG(vote)::float AS overall_avg 111 | FROM votes AS v 112 | LEFT OUTER JOIN supergroups AS s 113 | ON s.group_id = v.group_id 114 | WHERE (s.banned_until IS NULL OR s.banned_until < now() ) 115 | AND s.bot_inside IS TRUE 116 | GROUP BY s.lang 117 | HAVING COUNT(vote) >= %s) 118 | 119 | SELECT 120 | *, 121 | RANK() OVER (PARTITION BY sub.lang ORDER BY bayesan DESC) 122 | FROM ( 123 | SELECT 124 | v.group_id, 125 | s_ref.title, 126 | s_ref.username, 127 | COUNT(vote) AS amount, 128 | ROUND(AVG(vote), 1)::float AS average, 129 | s.nsfw, 130 | extract(epoch from s.joined_the_bot at time zone 'utc') AS dt, 131 | s.lang, 132 | s.category, 133 | -- (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C 134 | -- * R = average for the movie (mean) = (Rating) 135 | -- * v = number of votes for the movie = (votes) 136 | -- * m = minimum votes required to be listed in the Top 250 (currently 1300) 137 | -- * C = the mean vote across the whole report (currently 6.8) 138 | ( (COUNT(vote)::float / (COUNT(vote)+%s)) * AVG(vote)::float + (%s::float / (COUNT(vote)+%s)) * (m.overall_avg) ) AS bayesan 139 | FROM votes AS v 140 | LEFT OUTER JOIN supergroups_ref AS s_ref 141 | ON s_ref.group_id = v.group_id 142 | LEFT OUTER JOIN supergroups AS s 143 | ON s.group_id = v.group_id 144 | LEFT OUTER JOIN myconst AS m 145 | ON (s.lang = m.lang) 146 | GROUP BY v.group_id, s_ref.title, s_ref.username, s.nsfw, s.banned_until, s.lang, s.category, s.bot_inside, s.joined_the_bot, m.overall_avg 147 | HAVING 148 | (s.banned_until IS NULL OR s.banned_until < now()) 149 | AND COUNT(vote) >= %s 150 | AND s.bot_inside IS TRUE 151 | ) AS sub; 152 | """ 153 | this_week_votes_avg = database.query_r( 154 | query, 155 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 156 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 157 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 158 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 159 | leaderboards.VotesLeaderboard.MIN_REVIEWS 160 | ) 161 | 162 | dct = {} 163 | for group in msgs_this_week: 164 | dct = filling_dict(dct, group[0], BY_MESSAGES, group[2], group[3], time.time(), group[1]) 165 | 166 | for group in members_this_week: 167 | dct = filling_dict(dct, group[0], BY_MEMBERS, group[2], group[3], group[4], group[1]) 168 | 169 | for group in this_week_votes_avg: 170 | dct = filling_dict(dct, group[0], BY_VOTES, group[10], group[7], time.time(), [group[4], group[3]]) 171 | 172 | # encoding 173 | encoded_dct = {k: json.dumps(v).encode('UTF-8') for k,v in dct.items()} 174 | database.REDIS.hmset(CACHE_KEY, encoded_dct) 175 | database.REDIS.expire(CACHE_KEY, CACHE_SECONDS*4) 176 | remove_old_cached_keys(dct) 177 | 178 | 179 | def get_group_cached_rank(group_id): 180 | """ 181 | returns:None or a dictionary like: 182 | { 183 | 'by_messages': 184 | { 185 | 'rank': 1, 186 | 'cached_at': 1510106982.4582865, 187 | 'region': 188 | 'it' 189 | }, 190 | 'by_members': 191 | { 192 | 'rank': 1, 193 | 'cached_at': 1510106982.4582865, 194 | 'region': 'it' 195 | }, 196 | 'by_votes': 197 | { 198 | 'rank': 1, 199 | 'cached_at': 1510106982.4582865, 200 | 'region': 'it' 201 | } 202 | } 203 | """ 204 | rank = database.REDIS.hmget(CACHE_KEY, group_id)[0] 205 | return json.loads(rank.decode('UTF-8')) if rank is not None else None 206 | 207 | 208 | def remove_old_cached_keys(new_cache_dct): 209 | new_groups_list = [i for i in new_cache_dct] 210 | old_cache = database.REDIS.hgetall(CACHE_KEY) 211 | old_cached_groups_list = [int(i.decode('UTF-8')) for i in old_cache] 212 | groups_to_remove = [i for i in old_cached_groups_list if i not in new_groups_list] 213 | if len(groups_to_remove) > 0: # to avoid to run hdel with one param 214 | database.REDIS.hdel(CACHE_KEY, *groups_to_remove) 215 | 216 | -------------------------------------------------------------------------------- /topsupergroupsbot/messages_supergroups.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | import datetime 19 | import time 20 | import html 21 | 22 | from topsupergroupsbot import keyboards 23 | from topsupergroupsbot import database 24 | from topsupergroupsbot import constants 25 | from topsupergroupsbot import get_lang 26 | from topsupergroupsbot import utils 27 | 28 | import telegram 29 | 30 | 31 | # UNSUPPORTED CHAT 32 | 33 | def leave_unsupported_chat(bot, update): 34 | if update.message.chat.type == "group" or ( 35 | update.message.chat.type == "supergroup" 36 | and update.message.chat.username is None): 37 | query = "SELECT lang FROM supergroups WHERE group_id = %s" 38 | extract = database.query_r(query, update.message.chat.id, one=True) 39 | if extract is None: 40 | lang = 'en' 41 | else: 42 | lang = extract[0] 43 | 44 | text = get_lang.get_string(lang, "unsupported_chat") 45 | text += utils.text_mention_creator(bot, update.message.chat.id) 46 | update.message.reply_text(text=text, quote=False, parse_mode='HTML') 47 | bot.leaveChat(update.message.chat.id) 48 | query = "UPDATE supergroups SET bot_inside = FALSE WHERE group_id = %s" 49 | database.query_w(query, update.message.chat.id) 50 | return True 51 | 52 | # LOG INFO 53 | 54 | 55 | def add_supergroup_db(bot, update): 56 | query = """INSERT INTO 57 | supergroups(group_id, joined_the_bot, last_date) 58 | VALUES (%s, %s, %s) 59 | ON CONFLICT (group_id) DO 60 | UPDATE SET last_date = %s, bot_inside = TRUE 61 | WHERE supergroups.group_id = %s 62 | RETURNING lang""" 63 | extract = database.query_wr( 64 | query, update.message.chat.id, update.message.date, 65 | update.message.date, update.message.date, 66 | update.message.chat.id, one=True) 67 | return extract[0] 68 | 69 | 70 | def add_user_ref(bot, update): 71 | query = """INSERT INTO 72 | users_ref(user_id, name, last_name, username, tg_lang, message_date) 73 | VALUES (%s, %s, %s, %s, %s, %s) 74 | ON CONFLICT (user_id) DO 75 | UPDATE SET name = %s, last_name = %s, username = %s, 76 | tg_lang = COALESCE(%s, users_ref.tg_lang), message_date = %s 77 | WHERE users_ref.user_id = %s""" 78 | database.query_w( 79 | query, update.message.from_user.id, update.message.from_user.first_name, 80 | update.message.from_user.last_name, update.message.from_user.username, 81 | update.message.from_user.language_code, update.message.date, 82 | update.message.from_user.first_name, update.message.from_user.last_name, 83 | update.message.from_user.username, update.message.from_user.language_code, 84 | update.message.date, update.message.from_user.id) 85 | 86 | 87 | def add_supergroup_ref(bot, update): 88 | query = """INSERT INTO 89 | supergroups_ref(group_id, title, username, message_date) 90 | VALUES (%s, %s, %s, %s) 91 | ON CONFLICT (group_id) DO 92 | UPDATE SET title = %s, username = %s, message_date = %s 93 | WHERE supergroups_ref.group_id = %s""" 94 | database.query_w( 95 | query, update.message.chat.id, update.message.chat.title, 96 | update.message.chat.username, update.message.date, 97 | update.message.chat.title, update.message.chat.username, 98 | update.message.date, update.message.chat.id) 99 | 100 | 101 | def add_message_db(bot, update): 102 | m = update.message 103 | query = "INSERT INTO messages(msg_id, group_id, user_id, message_date) VALUES (%s, %s, %s, %s)" 104 | database.query_w(query, m.message_id, m.chat.id, m.from_user.id, m.date) 105 | 106 | # THIS GROUP HAS BEEN ADDED 107 | 108 | 109 | def this_bot_has_been_added(bot, update): 110 | if not update.message: 111 | return 112 | if not update.message.new_chat_members: 113 | return 114 | if constants.GET_ME in update.message.new_chat_members: 115 | return True 116 | 117 | 118 | def is_banned(bot, update): 119 | query = "SELECT banned_until FROM supergroups WHERE group_id = %s" 120 | extract = database.query_r(query, update.message.chat.id, one=True) 121 | if extract is None: 122 | ban = None 123 | else: 124 | if extract[0] is None or extract[0] < datetime.datetime.now(): 125 | ban = None 126 | else: 127 | ban = extract[0] 128 | return ban # this returns None if not banned else the expiring date 129 | 130 | 131 | def leave_banned_group(bot, update): 132 | query_db = "SELECT lang, banned_until, ban_reason FROM supergroups WHERE group_id = %s" 133 | extract = database.query_r(query_db, update.message.chat.id, one=True) 134 | lang = extract[0] 135 | banned_until = extract[1] 136 | reason = extract[2] 137 | shown_reason = html.escape(reason) if reason is not None else get_lang.get_string(lang, "not_specified") 138 | shown_reason = "{}".format(shown_reason) 139 | text = get_lang.get_string(lang, "banned_until_leave").format( 140 | utils.formatted_datetime_l(banned_until.replace(microsecond=0), lang), 141 | shown_reason) 142 | update.message.reply_text(text=text, quote=False, parse_mode='HTML') 143 | bot.leaveChat(update.message.chat.id) 144 | query = "UPDATE supergroups SET bot_inside = FALSE WHERE group_id = %s" 145 | database.query_w(query, update.message.chat.id) 146 | 147 | 148 | def choose_group_language(bot, update): 149 | query_db = "SELECT lang FROM supergroups WHERE group_id = %s" 150 | lang = database.query_r(query_db, update.message.chat.id, one=True)[0] 151 | text = get_lang.get_string(lang, "choose_group_lang") 152 | reply_markup = keyboards.select_group_lang_kb(lang, back=False) 153 | update.message.reply_text(text=text, reply_markup=reply_markup) 154 | 155 | 156 | def ee(bot, update): 157 | u = update.message.from_user 158 | text = "."+"c"+"r"+"e"+"a"+"t"+"o"+"r" 159 | reply_text = "He"+"llo" 160 | reply_text += "! I"+" ha"+"ve"+" "+"b"+"een cre" 161 | reply_text += "ate"+"d by " 162 | reply_text += telegram.utils.helpers.mention_html(u.id, u.first_name) 163 | reply_text += " ({}) ".format(u.id) 164 | right_id = 3635003 + 1111001 165 | if not update.message.text: 166 | return 167 | if update.message.from_user.id == right_id and update.message.text.lower() == text: 168 | update.message.reply_text(reply_text, parse_mode='HTML') 169 | 170 | 171 | def remember_to_set_lang(bot, update): 172 | if not rtsl_is_creator(bot, update): 173 | return 174 | if rtsl_already_sent(bot, update): 175 | return 176 | lang = None 177 | text = get_lang.get_string(lang, "hey_no_lang_set") 178 | reply_markup = keyboards.select_group_lang_kb(lang, back=False) 179 | update.message.reply_text(text=text, reply_markup=reply_markup) 180 | 181 | 182 | def rtsl_is_creator(bot, update): 183 | status = update.effective_chat.get_member(update.message.from_user.id).status 184 | if status == "creator": 185 | return True 186 | 187 | 188 | def rtsl_already_sent(bot, update): 189 | mute_for = 60 190 | now = int(time.time()) 191 | key = "lang_dont_ask_until:{}".format(update.message.chat.id) 192 | deadline = database.REDIS.get(key) 193 | deadline = int(deadline.decode('utf-8')) if deadline is not None else deadline 194 | if deadline is None or deadline < now: 195 | database.REDIS.setex(key, now+mute_for, mute_for*2) 196 | return False 197 | return True 198 | 199 | 200 | def added_again_message(bot, update, lang): 201 | text = get_lang.get_string(lang, "added_again") 202 | reply_markup = keyboards.main_group_settings_kb(lang) 203 | update.message.reply_text(text=text, reply_markup=reply_markup, quote=False) -------------------------------------------------------------------------------- /topsupergroupsbot/__main__.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | # library 18 | from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler 19 | from telegram.ext.dispatcher import run_async 20 | from telegram.error import (TelegramError, Unauthorized, BadRequest, TimedOut, ChatMigrated, NetworkError) 21 | from telegram import InlineKeyboardButton 22 | import logging 23 | import datetime 24 | 25 | from topsupergroupsbot import config 26 | from topsupergroupsbot import messages 27 | from topsupergroupsbot import buttons_callback 28 | from topsupergroupsbot import commands 29 | from topsupergroupsbot import utils 30 | from topsupergroupsbot import leaderboards 31 | from topsupergroupsbot import cleandb 32 | from topsupergroupsbot import memberslog 33 | from topsupergroupsbot import digest_private 34 | from topsupergroupsbot import commands_private 35 | from topsupergroupsbot import digest_supergroups 36 | from topsupergroupsbot import cache_users_stats 37 | from topsupergroupsbot import cache_groups_rank 38 | 39 | 40 | license = ( 41 | "\n\n" 42 | "***\n" 43 | "TopSupergroupsBot - A telegram bot for telegram public groups leaderboards" 44 | "Copyright (C) 2017 Dario (github.com/91DarioDev)" 45 | "\n\n" 46 | "TopSupergroupsBot is free software: you can redistribute it and/or modify " 47 | "it under the terms of the GNU Affero General Public License as published " 48 | "by the Free Software Foundation, either version 3 of the License, or " 49 | "(at your option) any later version." 50 | "\n\n" 51 | "TopSupergroupsBot is distributed in the hope that it will be useful, " 52 | "but WITHOUT ANY WARRANTY; without even the implied warranty of " 53 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " 54 | "GNU Affero General Public License for more details. " 55 | "\n\n" 56 | "You should have received a copy of the GNU Affero General Public License " 57 | "along with TopSupergroupsBot. If not, see ." 58 | "\n***" 59 | "\n" 60 | ) 61 | 62 | 63 | print(license) 64 | 65 | 66 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 67 | level=logging.INFO) 68 | 69 | logger = logging.getLogger(__name__) 70 | 71 | 72 | def error(bot, update, error): 73 | logger.warning('Update "%s" caused error "%s"' % (update, error)) 74 | 75 | 76 | def main(): 77 | print("\nrunning...") 78 | # define the updater 79 | updater = Updater(token=config.BOT_TOKEN, workers=7) 80 | 81 | # define the dispatcher 82 | dp = updater.dispatcher 83 | 84 | # define jobs 85 | j = updater.job_queue 86 | 87 | # this will run async the process_update 88 | dp.process_update = run_async(dp.process_update) 89 | 90 | # handlers 91 | 92 | # first start 93 | dp.add_handler(CommandHandler('start', commands.first_start, filters=Filters.private), -2) 94 | 95 | # before processing 96 | dp.add_handler(MessageHandler(Filters.all, messages.before_processing), -1) 97 | 98 | # commands 99 | dp.add_handler(CommandHandler('settings', commands.settings)) 100 | dp.add_handler(CommandHandler('vote', commands.vote, pass_args=True)) 101 | dp.add_handler(CommandHandler('start', commands.start, pass_args=True)) 102 | dp.add_handler(CommandHandler('help', commands.help)) 103 | dp.add_handler(CommandHandler('groupleaderboard', commands.groupleaderboard_private, filters=Filters.private, pass_args=True)) # this should come before the one for groups 104 | dp.add_handler(CommandHandler('groupleaderboard', commands.groupleaderboard, pass_args=True)) 105 | dp.add_handler(CommandHandler('grouprank', commands.group_rank_private, filters=Filters.private, pass_args=True)) # this should come before the one for groups 106 | dp.add_handler(CommandHandler('grouprank', commands.group_rank)) 107 | dp.add_handler(CommandHandler('leaderboard', commands.leaderboard)) 108 | dp.add_handler(CommandHandler('leadervote', leaderboards.leadervote, pass_args=True)) 109 | dp.add_handler(CommandHandler('leadermessage', leaderboards.leadermessage, pass_args=True)) 110 | dp.add_handler(CommandHandler('leadermember', leaderboards.leadermember, pass_args=True)) 111 | dp.add_handler(CommandHandler('aboutyou', commands.aboutyou)) 112 | dp.add_handler(CommandHandler('language', commands.language)) 113 | dp.add_handler(CommandHandler('region', commands.region)) 114 | dp.add_handler(CommandHandler('feedback', commands.feedback)) 115 | # private commands 116 | dp.add_handler(CommandHandler('statsusers', commands_private.stats_users)) 117 | dp.add_handler(CommandHandler('statsgroups', commands_private.stats_groups)) 118 | dp.add_handler(CommandHandler('infoid', commands_private.infoid, pass_args=True)) 119 | dp.add_handler(CommandHandler('reverseusername', commands_private.reverse_username, 120 | pass_args=True)) 121 | dp.add_handler(CommandHandler('bangroup', commands_private.ban_group, pass_args=True)) 122 | dp.add_handler(CommandHandler('unbangroup', commands_private.unban_group, pass_args=True)) 123 | # invalid command 124 | dp.add_handler(MessageHandler(Filters.command & Filters.private, utils.invalid_command)) 125 | # handle all messages not command. it's obvious because commands are handled before, 126 | # but it's more safe 127 | dp.add_handler(MessageHandler(~Filters.command, messages.processing)) 128 | 129 | # handle buttons callback 130 | dp.add_handler(CallbackQueryHandler(buttons_callback.callback_query)) 131 | 132 | 133 | # jobs 134 | j.run_repeating(cleandb.clean_db, interval=60*60*24, first=0) 135 | j.run_repeating(memberslog.members_log, interval=60*60*24, first=0) 136 | j.run_daily(digest_private.weekly_own_private, time=datetime.time(0, 0, 0), days=(0,)) 137 | j.run_daily(digest_supergroups.weekly_groups_digest, time=datetime.time(0, 0, 0), days=(0,)) 138 | # leaderboards pre-cache 139 | 140 | j.run_repeating( 141 | leaderboards.scheduling_votes_leaderboard_cache, 142 | interval=leaderboards.VotesLeaderboard.CACHE_SECONDS, 143 | first=0 144 | ) 145 | j.run_repeating( 146 | leaderboards.scheduling_messages_leaderboard_cache, 147 | interval=leaderboards.MessagesLeaderboard.CACHE_SECONDS, 148 | first=0 149 | ) 150 | j.run_repeating( 151 | leaderboards.scheduling_members_leaderboard_cache, 152 | interval=leaderboards.MembersLeaderboard.CACHE_SECONDS, 153 | first=0 154 | ) 155 | # pre-cached users stats 156 | j.run_repeating( 157 | cache_users_stats.cache_users_stats, 158 | interval=cache_users_stats.CACHE_SECONDS, 159 | first=0 160 | ) 161 | 162 | # pre-cache ranks groups 163 | j.run_repeating( 164 | cache_groups_rank.caching_ranks, 165 | interval=cache_groups_rank.CACHE_SECONDS, 166 | first=0 167 | ) 168 | 169 | # check inactive groups 170 | j.run_repeating( 171 | cleandb.check_bot_inside_in_inactive_groups, 172 | interval=(60*60*24) * 2, 173 | first=0 174 | ) 175 | 176 | # update cache if the week is over for cached leaderboards related to the week 177 | j.run_daily( 178 | leaderboards.scheduling_messages_leaderboard_cache, 179 | time=datetime.time(0, 0, 0), 180 | days=(0,) 181 | ) 182 | j.run_daily( 183 | cache_users_stats.cache_users_stats, 184 | time=datetime.time(0, 0, 0), 185 | days=(0,) 186 | ) 187 | j.run_daily( 188 | cache_groups_rank.caching_ranks, 189 | time=datetime.time(0, 0, 0), 190 | days=(0,) 191 | ) 192 | # handle errors 193 | dp.add_error_handler(error) 194 | 195 | updater.start_polling() 196 | updater.idle() 197 | 198 | 199 | if __name__ == '__main__': 200 | main() 201 | -------------------------------------------------------------------------------- /topsupergroupsbot/feedback.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | from topsupergroupsbot import get_lang 19 | from topsupergroupsbot import commands_private 20 | from topsupergroupsbot import utils 21 | from topsupergroupsbot import constants as c 22 | from topsupergroupsbot import database as db 23 | from topsupergroupsbot import config 24 | from topsupergroupsbot import keyboards 25 | 26 | from telegram.error import (TelegramError, 27 | Unauthorized, 28 | BadRequest, 29 | TimedOut, 30 | ChatMigrated, 31 | NetworkError) 32 | 33 | 34 | INTERVAL = 60*60*24*7 35 | MAX_ALLOWED = 5 36 | 37 | 38 | class Feedback: 39 | def __init__(self, bot, update, receive=False, reply=False): 40 | if receive: 41 | self.feedback_from = update.message.from_user 42 | elif reply: 43 | self.feedback_from = update.message.reply_to_message.forward_from 44 | 45 | def feedback_key(self): 46 | key = "feedback_flood:{}".format(self.feedback_from.id) 47 | return key 48 | 49 | def is_allowed(self): 50 | key = self.feedback_key() 51 | result = db.REDIS.get(key) 52 | if result is None: 53 | return True 54 | return True if int(result) <= MAX_ALLOWED else False 55 | 56 | def increment_feedback(self): 57 | key = self.feedback_key() 58 | result = db.REDIS.incr(key, amount=1) 59 | if result == 1: 60 | db.REDIS.expire(key, INTERVAL) 61 | 62 | def remove_key(self): 63 | key = self.feedback_key() 64 | result = db.REDIS.delete(key) 65 | 66 | def receive_feedback(self, bot, update): 67 | sender_id = self.feedback_from.id 68 | lang = utils.get_db_lang(sender_id) 69 | forwarded = update.message.forward(config.FOUNDER, disable_notification=True) 70 | forwarded.reply_text( 71 | "#id_"+str(sender_id)+"\n#feedback_from_user", 72 | quote=True, 73 | disable_notification=True) 74 | forwarded.reply_text( 75 | commands_private.get_info_id(bot, sender_id), 76 | quote=True, 77 | disable_notification=True) 78 | update.message.reply_text(get_lang.get_string(lang, "thanks_feedback"), quote=True) 79 | 80 | def do_not_receive_feedback(self, bot, update): 81 | sender_id = self.feedback_from.id 82 | lang = utils.get_db_lang(sender_id) 83 | update.message.reply_text(get_lang.get_string(lang, "feedback_flood"), quote=True) 84 | 85 | def reply_feedback(self, bot, update): 86 | first = None 87 | try: 88 | lang = utils.get_db_lang(self.feedback_from.id) 89 | 90 | if update.message.text: 91 | first = bot.sendMessage( 92 | chat_id=self.feedback_from.id, 93 | text=update.message.text) 94 | 95 | elif update.message.voice: 96 | media = update.message.voice.file_id 97 | duration = update.message.voice.duration 98 | caption = update.message.caption_html if update.message.caption else None 99 | first = bot.sendVoice( 100 | chat_id=self.feedback_from.id, 101 | voice=media, 102 | duration=duration, 103 | caption=caption, 104 | parse_mode='HTML') 105 | 106 | elif update.message.photo: 107 | media = update.message.photo[-1].file_id 108 | caption = update.message.caption_html if update.message.caption else None 109 | first = bot.sendPhoto( 110 | chat_id=self.feedback_from.id, 111 | photo=media, 112 | caption=caption, 113 | parse_mode='HTML') 114 | 115 | elif update.message.sticker: 116 | media = update.message.sticker.file_id 117 | first = bot.sendSticker( 118 | chat_id=self.feedback_from.id, 119 | sticker=media) 120 | 121 | elif update.message.document: 122 | media = update.message.document.file_id 123 | filename = update.message.document.file_name 124 | caption = update.message.caption_html if update.message.caption else None 125 | first = bot.sendDocument( 126 | chat_id=self.feedback_from.id, 127 | document=media, 128 | filename=filename, 129 | caption=caption, 130 | parse_mode='HTML') 131 | 132 | elif update.message.audio: 133 | media = update.message.audio.file_id 134 | duration = update.message.audio.duration 135 | performer = update.message.audio.performer 136 | title = update.message.audio.title 137 | caption = update.message.caption_html if update.message.caption else None 138 | first = bot.sendAudio( 139 | chat_id=self.feedback_from.id, 140 | audio=media, 141 | duration=duration, 142 | performer=performer, 143 | title=title, 144 | caption=caption, 145 | parse_mode='HTML') 146 | 147 | elif update.message.video: 148 | media = update.message.video.file_id 149 | caption = update.message.caption_html if update.message.caption else None 150 | duration = update.message.video.duration 151 | first = bot.sendVideo( 152 | chat_id=self.feedback_from.id, 153 | video=media, 154 | duration=duration, 155 | caption=caption, 156 | parse_mode='HTML') 157 | 158 | bot.sendMessage( 159 | chat_id=self.feedback_from.id, 160 | text=get_lang.get_string(lang, "from_developer"), 161 | parse_mode='HTML', 162 | reply_to_message_id=first.message_id, 163 | reply_markup=keyboards.feedback_reply_kb(lang)) 164 | 165 | confirm = "sent to #id_{}".format(self.feedback_from.id) 166 | bot.sendMessage(chat_id=config.FOUNDER, text=confirm, disable_notification=True) 167 | 168 | except Unauthorized as e: 169 | reason = "Message not sent.\n\n{}".format(e.message) 170 | update.message.reply_text(reason, quote=True, parse_mode='HTML') 171 | 172 | 173 | def is_a_feedback(bot, update): 174 | if update.message.reply_to_message is None: 175 | return False 176 | if update.message.reply_to_message.text is None: 177 | return False 178 | if update.message.reply_to_message.from_user.id == bot.id and ( 179 | update.message.reply_to_message.text).startswith(c.FEEDBACK_INV_CHAR): 180 | return True 181 | 182 | 183 | def handle_receive_feedback(bot, update): 184 | fb = Feedback(bot, update, receive=True) 185 | if fb.is_allowed(): 186 | fb.receive_feedback(bot, update) 187 | fb.increment_feedback() 188 | else: 189 | fb.do_not_receive_feedback(bot, update) 190 | 191 | 192 | def is_a_feedback_reply(bot, update): 193 | if update.message.from_user.id != config.FOUNDER: 194 | return False 195 | if update.message.reply_to_message is None: 196 | return False 197 | if update.message.reply_to_message.forward_from is None: 198 | return False 199 | if update.message.reply_to_message.from_user.id == bot.id or update.message.reply_to_message.from_user.id == config.FOUNDER: 200 | return True 201 | 202 | 203 | def handle_reply_feedback(bot, update): 204 | fb = Feedback(bot, update, reply=True) 205 | fb.reply_feedback(bot, update) 206 | fb.remove_key() 207 | -------------------------------------------------------------------------------- /topsupergroupsbot/commands_private.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | import html 19 | 20 | from topsupergroupsbot import database 21 | from topsupergroupsbot import utils 22 | from topsupergroupsbot import get_lang 23 | from topsupergroupsbot import categories 24 | 25 | from telegram.error import (TelegramError, 26 | Unauthorized, 27 | BadRequest, 28 | TimedOut, 29 | ChatMigrated, 30 | NetworkError) 31 | 32 | 33 | @utils.bot_owner_only 34 | def stats_users(bot, update): 35 | # total users grouped by regions 36 | query = """ 37 | SELECT region, COUNT(user_id) AS amount 38 | FROM users 39 | GROUP BY region 40 | ORDER BY amount DESC 41 | """ 42 | text = "Total users per region:\n" 43 | extract = database.query_r(query) 44 | for i in extract: 45 | text += "— {}: {}\n".format(i[0], i[1]) 46 | 47 | # users grouped by regions didn't block the bot 48 | query = """ 49 | SELECT region, COUNT(user_id) AS amount 50 | FROM users 51 | WHERE bot_blocked = FALSE 52 | GROUP BY region 53 | ORDER BY amount DESC 54 | """ 55 | text += "\nUsers didn't block the bot:\n" 56 | extract = database.query_r(query) 57 | for i in extract: 58 | text += "— {}: {}\n".format(i[0], i[1]) 59 | 60 | 61 | # users grouped by regions didn't block the bot active 7 days 62 | query = """ 63 | SELECT region, COUNT(user_id) AS amount 64 | FROM users 65 | WHERE bot_blocked = FALSE AND message_date > (now() - interval '7 days') 66 | GROUP BY region 67 | ORDER BY amount DESC 68 | """ 69 | text += "\nDidn't block the bot and active in the last 7 days per region:\n" 70 | extract = database.query_r(query) 71 | for i in extract: 72 | text += "— {}: {}\n".format(i[0], i[1]) 73 | 74 | update.message.reply_text(text=text, parse_mode='HTML') 75 | 76 | 77 | @utils.bot_owner_only 78 | def stats_groups(bot, update): 79 | # total groups 80 | query = """ 81 | SELECT lang, COUNT(group_id) AS amount 82 | FROM supergroups 83 | GROUP BY lang 84 | ORDER BY amount DESC 85 | """ 86 | text = "Total groups per lang:\n" 87 | extract = database.query_r(query) 88 | for i in extract: 89 | text += "— {}: {}\n".format(i[0], i[1]) 90 | 91 | 92 | # total groups bot not removed 93 | query = """ 94 | SELECT lang, COUNT(group_id) AS amount 95 | FROM supergroups 96 | WHERE bot_inside = TRUE 97 | GROUP BY lang 98 | ORDER BY amount DESC 99 | """ 100 | text += "\nTotal groups per lang didn't remove the bot:\n" 101 | extract = database.query_r(query) 102 | for i in extract: 103 | text += "— {}: {}\n".format(i[0], i[1]) 104 | # total groups bot inside and joined last 7 days 105 | query = """ 106 | SELECT lang, COUNT(group_id) AS amount 107 | FROM supergroups 108 | WHERE bot_inside = TRUE AND last_date > (now() - interval '7 days') 109 | GROUP BY lang 110 | ORDER BY amount DESC 111 | """ 112 | text += "\nTotal groups per lang didn't remove the bot and joined in the past 7 days:\n" 113 | extract = database.query_r(query) 114 | for i in extract: 115 | text += "— {}: {}\n".format(i[0], i[1]) 116 | 117 | update.message.reply_text(text=text, parse_mode='HTML') 118 | 119 | 120 | @utils.bot_owner_only 121 | def infoid(bot, update, args): 122 | if len(args) != 1: 123 | update.message.reply_text("1 args per time") 124 | return 125 | tgid = int(args[0]) 126 | text = get_info_id(bot, tgid) 127 | text += "\n"+infoid_from_db(tgid) 128 | update.message.reply_text(text=text) 129 | 130 | 131 | def get_info_id(bot, tgid): 132 | try: 133 | result = bot.getChat(chat_id=tgid) 134 | except BadRequest as e: 135 | return str(e) 136 | 137 | if tgid < 0: 138 | text = "title: {}".format(result.title) 139 | text += "\nusername: @{}".format(result.username) 140 | else: 141 | text = "\nName: {}".format(result.first_name) 142 | text += "\nLast name: {}".format(result.last_name) 143 | text += "\nUsername: @{}".format(result.username) 144 | return text 145 | 146 | 147 | def infoid_from_db(tgid): 148 | if tgid > 0: 149 | query = """ 150 | SELECT 151 | lang, 152 | region, 153 | tg_lang, 154 | bot_blocked, 155 | banned_on, 156 | banned_until, 157 | weekly_own_digest, 158 | weekly_groups_digest, 159 | registered_at::timestamp(0), 160 | message_date 161 | FROM users 162 | WHERE user_id = %s""" 163 | extract = database.query_r(query, tgid, one=True) 164 | if extract is None: 165 | return "Not in the db" 166 | text = "" 167 | text += "lang: {}\n".format(extract[0]) 168 | text += "region: {}\n".format(extract[1]) 169 | text += "tg_lang: {}\n".format(extract[2]) 170 | text += "bot_blocked: {}\n".format(extract[3]) 171 | text += "banned_on: {}\n".format(extract[4]) 172 | text += "banned_until: {}\n".format(extract[5]) 173 | text += "weekly_own_digest: {}\n".format(extract[6]) 174 | text += "weekly_groups_digest: {}\n".format(extract[7]) 175 | text += "registered_at: {}\n".format(extract[8]) 176 | text += "message_date: {}\n".format(extract[9]) 177 | else: 178 | query = """ 179 | SELECT 180 | lang, 181 | nsfw, 182 | joined_the_bot, 183 | banned_on, 184 | banned_until, 185 | ban_reason, 186 | bot_inside, 187 | last_date, 188 | category 189 | FROM supergroups 190 | WHERE group_id = %s 191 | """ 192 | extract = database.query_r(query, tgid, one=True) 193 | if extract is None: 194 | return "Not in the db" 195 | text = "" 196 | text += "lang: {}\n".format(extract[0]) 197 | text += "nsfw: {}\n".format(extract[1]) 198 | text += "joined_the_bot: {}\n".format(extract[2]) 199 | text += "banned_on: {}\n".format(extract[3]) 200 | text += "banned_until: {}\n".format(extract[4]) 201 | text += "ban_reason: {}\n".format(extract[5]) 202 | text += "bot_inside: {}\n".format(extract[6]) 203 | text += "last_date: {}\n".format(extract[7]) 204 | text += "category: {}\n".format(categories.CODES[extract[8]] if extract[8] is not None else None) 205 | return text 206 | 207 | 208 | @utils.bot_owner_only 209 | def reverse_username(bot, update, args): 210 | if len(args) != 1: 211 | update.message.reply_text("1 arg per time") 212 | return 213 | username = args[0] 214 | username = "@"+str(username) if not (username.startswith("@")) else username 215 | try: 216 | result = bot.getChat(chat_id=username) 217 | except BadRequest as e: 218 | update.message.reply_text(e) 219 | return 220 | text = "id: {}".format(result['id']) 221 | text += "" 222 | update.message.reply_text(text, quote=True) 223 | 224 | 225 | @utils.bot_owner_only 226 | def ban_group(bot, update, args): 227 | if len(args) < 3: 228 | text = "specify id for days\nexample: -34545322 for 30 (optional: for too much spam)" 229 | update.message.reply_text(text) 230 | return 231 | 232 | params = " ".join(args).split(" for ") 233 | group_id = params[0] 234 | days = int(params[1]) 235 | try: 236 | reason = params[2] 237 | except IndexError: 238 | reason = None 239 | query = """ 240 | UPDATE supergroups 241 | SET 242 | banned_on = now(), 243 | banned_until = now() + interval '%s days', 244 | ban_reason = %s 245 | WHERE group_id = %s 246 | RETURNING lang, banned_until 247 | """ 248 | 249 | extract = database.query_wr(query, days, reason, group_id, one=True) 250 | lang = extract[0] 251 | banned_until = extract[1] 252 | shown_reason = html.escape(reason) if reason is not None else get_lang.get_string(lang, "not_specified") 253 | shown_reason = "{}".format(shown_reason) 254 | text = get_lang.get_string(lang, "banned_until_leave").format( 255 | utils.formatted_datetime_l(banned_until.replace(microsecond=0), lang), 256 | shown_reason) 257 | text += utils.text_mention_creator(bot, group_id) 258 | try: 259 | bot.send_message(chat_id=group_id, text=text, parse_mode='HTML') 260 | bot.leaveChat(group_id) 261 | except Unauthorized as e: 262 | update.message.reply_text(e.message) 263 | 264 | query = "UPDATE supergroups SET bot_inside = FALSE WHERE group_id = %s" 265 | database.query_w(query, group_id) 266 | update.message.reply_text("Done!") 267 | 268 | 269 | @utils.bot_owner_only 270 | def unban_group(bot, update, args): 271 | if len(args) != 1: 272 | update.message.reply_text("1 args as id") 273 | return 274 | group_id = args[0] 275 | query = """ 276 | UPDATE supergroups 277 | SET 278 | banned_on = NULL, 279 | banned_until = NULL, 280 | ban_reason = NULL 281 | WHERE group_id = %s 282 | """ 283 | database.query_w(query, group_id) 284 | update.message.reply_text("unbanned", quote=True) 285 | -------------------------------------------------------------------------------- /topsupergroupsbot/langs/en.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | buttons_strings = { 18 | "leaderboard": "leaderboard", 19 | "about_you": "about you", 20 | "region": "region", 21 | "settings": "settings", 22 | "info_and_help": "info & help" 23 | } 24 | 25 | 26 | hello = "Hello!" 27 | 28 | choose_group_lang = ( 29 | "Choose the language of the group. " 30 | "Your group will be added in the leaderboard of the right language. " 31 | "Don't lie or we'll ban this group") 32 | 33 | group_settings = "Group settings:" 34 | messages_in_groups_position = "— {} messages in @{}. Your position: {}\n" 35 | have_adult = "Does this group contains adult content?" 36 | here_group_vote_link = "This is the link to directly redirect users to vote this group" 37 | canceled = "Canceled" 38 | already_this_page = "You are already on this page!" 39 | 40 | vote_this_group = "id: {}\nusername: @{}\ntitle: {}" 41 | already_voted = "Already voted {} on {}" 42 | vote = "vote" 43 | vote_from_one_to_five = "Rate this group from 1 to 5 stars" 44 | choose_your_lang = "Select your language" 45 | 46 | group_lang_button = "Group Lang" 47 | adult_button = "Adult Emoji" 48 | vote_link_button = "Vote link" 49 | back = "Back" 50 | yes = "Yes" 51 | no = "No" 52 | cancel = "Cancel" 53 | 54 | this_command_only_private = "{} is only for private chats" 55 | this_command_only_admins = "{} is only for the creator or admins in groups" 56 | this_command_only_creator = "{} is only for the creator in groups" 57 | but_you_can_use_in_private = ".\nBut you can use it here." 58 | button_for_creator = "This button is only for the creator" 59 | button_for_admins = "This button is only for admins" 60 | invalid_command = "command not valid" 61 | 62 | cant_vote_this = "I am not inside this group, so you can't vote it, sorry!" 63 | registered_vote = "Vote registered!" 64 | updated_vote = "Vote updated!" 65 | 66 | choose_region = ( 67 | "Choose your region. It will be the default language filter when you " 68 | "ask for a leaderboard") 69 | 70 | pre_leadervote = "Ordered by votes average. Having at least {} votes.\nRegion: {}" 71 | pre_leadermessage = "Ordered by sent messages in this week(UTC).\nRegion: {}" 72 | pre_groupleaderboard = "Top users ordered by messages sent during this week(UTC) in @{}." 73 | pre_leadermember = "Ordered by amount of members.\nRegion: {}" 74 | 75 | 76 | private_lang_button = "Language" 77 | private_region_button = "Region" 78 | private_settings = "Settings:" 79 | private_digest_button = "Digest" 80 | private_your_own_digest_button = "About you" 81 | private_groups_digest_button = "About groups" 82 | private_digest = "You can receive a digest at the end of every week. Disable or enable them anytime you want" 83 | weekly_own_digest = "Do you want to get anytime a week is over statistic about you during the past week?" 84 | hello_name = "Hello {name}" 85 | digest_of_the_week_global = ("Another week is over!\nDuring the last week you globally sent {} messages " 86 | "in {} groups. Your position worldwide: {}\n") 87 | 88 | digest_of_the_week_detail = "— {} messages in @{}. Your position: {}\n" 89 | 90 | generic_leaderboard = ( 91 | "Choose one of the following order criterias for the leaderboard.\n" 92 | "Your region: {}. Press /region in case you want to check groups of other regions") 93 | 94 | by_members = "By members" 95 | by_messages = "By messages" 96 | by_votes = "By votes" 97 | 98 | 99 | help_commands = ( 100 | "here are the commands you can use:\n\n" 101 | "/leaderboard - check out supergroups leaderboards\n" 102 | "/vote - vote a group\n" 103 | "/aboutyou - get stats about you\n" 104 | "/settings - change your settings\n" 105 | "/feedback - send a feedback" 106 | ) 107 | 108 | help_message = "This bot does statistics and leaderboards about public supergrous and their users" 109 | 110 | insert_param_vote = ( 111 | "To vote a group send the username (no matter the presence of the '@')" 112 | " as a parameter of the /vote command.\n\nExample:\n/vote @NonEntrate") 113 | 114 | 115 | disable = "Disable" 116 | hey_no_lang_set = ( 117 | "Hey! You haven't set any language yet, so in which region of the leaderboards should" 118 | " i put this group?. No, really, set a language.") 119 | 120 | you_inactive_this_week = "This week you didn't sent messages in groups yet" 121 | this_week_you_sent_this = "This week you already sent:" 122 | you_globally_this_week = "You globally already sent {} messages in {} groups during this week. Your position worldwide: {}" 123 | 124 | unsupported_chat = ( 125 | "I have been programmed to take part only in public groups. " 126 | "This is not a public group. I leave, bye") 127 | 128 | banned_until_leave = "This group has been banned. The ban will end on {} UTC.\nReason: {}.\nI leave." 129 | not_specified = "Not specified" 130 | 131 | group_digest_button = "Digest" 132 | group_weekly_digest = ( 133 | "Would you like to receive a digest of this group anytime a week is over?" 134 | " You can change your mind at anytime.") 135 | 136 | 137 | groups_working = ( 138 | "Do you want your group to be part of our leaderboards? Simply add this bot in" 139 | " your group and be sure to set the right language of the group. The group will " 140 | "be added in the region of leaderboards that you specified with the language.\n" 141 | "If your group has adult contents, select the right option in /settings. Please " 142 | "be sure to insert only the correct values or we may ban your group from our bot.\n" 143 | "We do not apply any kind of censorship about topics, but we may ban groups cheating " 144 | "in leaderboards.\nIn /settings you will even find the link to redirect users to " 145 | "automatically vote your group.\n\n" 146 | "SUPPORTED COMMANDS IN GROUPS:\n" 147 | "/settings - set group settings\n" 148 | "/groupleaderboard - get a message containing leaderboard of users that wrote" 149 | "more messages in the group during the current week (UTC). optional param: [number of page]\n" 150 | "/grouprank - Check the rank of the group" 151 | ) 152 | 153 | weekly_groups_digest = ( 154 | "Hello! Another week is over. Here there are some stats of this group:\n\n" 155 | 156 | "-- BY MESSAGES --\n" 157 | "sent messages last week: {}\n" 158 | "sent messages this week: {}\n" 159 | "difference: {} percent: {}\n" 160 | "position last week: {}\n" 161 | "position this week: {}\n\n" 162 | 163 | "-- BY MEMBERS --\n" 164 | "members last week: {}\n" 165 | "members this week: {}\n" 166 | "difference: {} percent: {}\n" 167 | "position last week: {}\n" 168 | "position this week: {}\n\n" 169 | 170 | "-- BY VOTES AVERAGE --\n" 171 | "average and number of votes last week: {}{}|({})\n" 172 | "average and number of votes this week: {}{}|({})\n" 173 | "position last week: {}\n" 174 | "position this week: {}\n\n" 175 | 176 | "-- BY ACTIVE USERS --\n" 177 | "active users last week: {}\n" 178 | "active users this week: {}\n" 179 | "difference: {} percent: {}\n" 180 | "position last week: {}\n" 181 | "position this week: {}\n\n" 182 | 183 | "TOP USERS OF THE WEEK:\n" 184 | ) 185 | 186 | added_again = "Hello! Do you want to check the group settings again?" 187 | 188 | feedback_message = ("The only way to send a feedback is sending your message as a reply of this message" 189 | ".\n\nYou can send any kind of message or media.") 190 | 191 | thanks_feedback = "Feedback succesfully sent! thank you!" 192 | 193 | feedback_flood = ("Sorry, you already sent many feedbacks. Wait for a reply first or wait for a while." 194 | "No way to send this feedback") 195 | 196 | from_developer = "This is a message from the developer of the bot." 197 | 198 | feedback_reply = "Reply" 199 | 200 | unrecognized_button = ("a message starting and ending with that character is handled as a button. " 201 | "The button you pressed is invalid. I sent you an updated keyboard.\n\nIf you were instead " 202 | "sending that message as just message, append another char at the beginning or at the end.") 203 | 204 | updating_buttons = "I update buttons" 205 | 206 | latest_update = "Updated" 207 | seconds_ago = "{} seconds ago" 208 | about_minutes_ago = "about {} minutes ago" 209 | about_hours_ago = "about {} hours ago" 210 | seconds_ago_short = "{} sec ago" 211 | about_minutes_ago_short = "~{} min ago" 212 | about_hours_ago_short = "~{}h ago" 213 | 214 | group_rank = { 215 | "title": "GROUP RANK:", 216 | "by_messages": "Leaderboard ordered by messages sent during the current week (region: {}):", 217 | "by_members": "Leaderboard ordered by amount of members (region: {}):", 218 | "by_votes": "Leaderboard ordered by votes average (region: {}):", 219 | "position": "- Position: {}", 220 | "updated": "{}: {}", 221 | "None": "Unfortunately this group isn't in any leaderboard", 222 | "messages": "- messages: {}", 223 | "members": "- members: {}", 224 | "votes": "- votes average|number: {}|({})" 225 | } 226 | 227 | feedback = "feedback" 228 | source_code = "source code" 229 | commands = "commands" 230 | how_to_use_in_groups = "usage in groups" 231 | 232 | 233 | category = "Category" 234 | choose_group_category = "Choose the category that better fits this group. Do not lie or we'll ban the group." 235 | categories = { 236 | 'news': 'news', 237 | 'science_and_education': 'science&education', 238 | 'religion': 'religion', 239 | 'entertainment': 'entertainment', 240 | 'family_and_home': 'family&home', 241 | 'sport': 'sport', 242 | 'art_and_culture': 'art&culture', 243 | 'politics': 'politics', 244 | 'information_technology': 'IT&tech', 245 | 'game_and_apps': 'games&apps', 246 | 'love': 'love', 247 | 'tourism': 'tourism', 248 | 'economics': 'economics' 249 | } 250 | filter_by_category = 'filter category' 251 | choose_category_to_filter = "choose one of the following categories to filter the leaderboard" 252 | remove_filter = "remove filter" 253 | 254 | change_vote = "change vote" 255 | 256 | advanced_commands = "Advanced commands" 257 | 258 | advanced_commands_text = ( 259 | "Advanced commands:\n\n" 260 | "/leadervote - leaderboard ordered by votes (optional params: [p=(page number)] [c=(category number)])\n" 261 | "/leadermember - leaderboard ordered by members (optional params: [p=(page number)] [c=(category number)]\n" 262 | "/leadermessage - leaderboard ordered by messages (optional params: [p=(page number)] [c=(category number)]\n\n" 263 | "The category number can be taken counting categories buttons starting from left to right\n\n" 264 | "/grouprank - /grouprank [username of the group]\n" 265 | "/groupleaderboard - /groupleaderboard [username of the group]" 266 | ) 267 | 268 | groupleaderboard_command_error = "Error:\nuse the command this way:\n\n{} [number of page(optional)]" 269 | 270 | avdanced_leaderboard_command_error = "Error:\nuse the command this way. paramters are optional:\n\n{} [p=(page number)] [c=(category number)]" 271 | 272 | error_param_group_rank_private = ( 273 | "Error:\nyou should write as parameter of this command the username of the group that you want to check the rank. " 274 | "You can put or not the \"@\" (it doesn't matter).\n\nExample: /grouprank my_favorite_group" 275 | ) 276 | 277 | cant_check_this = "Sorry, @{} is not in our database." 278 | 279 | error_param_group_leaderboard_private = ( 280 | "Error:\nyou should write as parameter of this command the username of the group that you want to check the groupleaderboard. " 281 | "You can put or not the \"@\" (it doesn't matter).\n\nExample: /groupleaderboard my_favorite_group\n\n" 282 | "Optionally you can jump directly to a page adding the parameter p=[page number].\n\nExample: /groupleaderboard my_favorite_group p=26" 283 | ) 284 | 285 | check_in_private = "Check in private" 286 | 287 | official_channel = "official channel" 288 | donate = "donate" 289 | donate_intro = ("This bot is free, opensource and developed for telegram communities.\n\nAnyways the developement required and still " 290 | "requires a lot of time and money to pay servers. We will be very happy if you can help us with project with a little donation.\n\n" 291 | ) 292 | something_went_wrong = "Ops! something went wrong." -------------------------------------------------------------------------------- /topsupergroupsbot/utils.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import babel 18 | import datetime 19 | import time 20 | import html 21 | from functools import wraps, partial 22 | 23 | from topsupergroupsbot import supported_langs 24 | from topsupergroupsbot import constants 25 | from topsupergroupsbot import get_lang 26 | from topsupergroupsbot import database 27 | from topsupergroupsbot import config 28 | 29 | from telegram import constants as ptb_consts 30 | 31 | from babel.dates import format_datetime, format_date 32 | from babel.numbers import format_decimal 33 | 34 | from telegram.error import ( 35 | TelegramError, 36 | Unauthorized, 37 | BadRequest, 38 | TimedOut, 39 | ChatMigrated, 40 | NetworkError) 41 | 42 | 43 | def get_db_lang(user_id): 44 | query = "SELECT lang FROM users WHERE user_id = %s" 45 | extract = database.query_r(query, user_id, one=True) 46 | lang = extract[0] if extract is not None else 'en' 47 | return lang 48 | 49 | 50 | def bot_owner_only(func): 51 | @wraps(func) 52 | def wrapped(bot, update, *args, **kwargs): 53 | if update.message.from_user.id not in config.ADMINS: 54 | invalid_command(bot, update) 55 | return 56 | return func(bot, update, *args, **kwargs) 57 | return wrapped 58 | 59 | 60 | def private_only(func): 61 | @wraps(func) 62 | def wrapped(bot, update, *args, **kwargs): 63 | if update.message.chat.type != "private": 64 | lang = get_db_lang(update.effective_user.id) 65 | text = get_lang.get_string(lang, "this_command_only_private").format(update.message.text.split(" ")[0]) 66 | try: 67 | chat_id = update.message.from_user.id 68 | bot.send_message(chat_id=chat_id, text=text) 69 | except: 70 | status = update.effective_chat.get_member(update.message.from_user.id).status 71 | if status not in ["administrator", "creator"]: 72 | return 73 | update.message.reply_text(text) 74 | return 75 | return func(bot, update, *args, **kwargs) 76 | return wrapped 77 | 78 | 79 | def admin_command_only(possible_in_private=False): 80 | def _admin_command_only(func): 81 | @wraps(func) 82 | def wrapped(bot, update, *args, **kwargs): 83 | if update.message.chat.type == "private": 84 | lang = get_db_lang(update.effective_user.id) 85 | text = get_lang.get_string(lang, "this_command_only_admins").format(update.message.text.split(" ")[0]) 86 | if possible_in_private: 87 | text += get_lang.get_string(lang, "but_you_can_use_in_private") 88 | update.message.reply_text(text) 89 | return 90 | if not update.effective_chat.get_member(update.message.from_user.id).status in ["administrator", "creator"]: 91 | lang = get_db_lang(update.effective_user.id) 92 | text = get_lang.get_string(lang, "this_command_only_admins").format(update.message.text.split(" ")[0]) 93 | if possible_in_private: 94 | text += get_lang.get_string(lang, "but_you_can_use_in_private") 95 | try: 96 | chat_id = update.message.from_user.id 97 | bot.send_message(chat_id=chat_id, text=text) 98 | except Unauthorized: 99 | pass 100 | return 101 | return func(bot, update, *args, **kwargs) 102 | return wrapped 103 | return _admin_command_only 104 | 105 | 106 | def creator_command_only(func): 107 | @wraps(func) 108 | def wrapped(bot, update, *args, **kwargs): 109 | if update.message.chat.type == "private": 110 | lang = get_db_lang(update.effective_user.id) 111 | text = get_lang.get_string(lang, "this_command_only_creator").format(update.message.text.split(" ")[0]) 112 | update.message.reply_text(text) 113 | return 114 | status = update.effective_chat.get_member(update.message.from_user.id).status 115 | if status not in ["creator"]: 116 | lang = get_db_lang(update.effective_user.id) 117 | text = get_lang.get_string(lang, "this_command_only_creator").format(update.message.text.split(" ")[0]) 118 | try: 119 | chat_id = update.message.from_user.id 120 | bot.send_message(chat_id=chat_id, text=text) 121 | except: 122 | if status in ['administrator']: 123 | update.message.reply_text(text) 124 | return 125 | return func(bot, update, *args, **kwargs) 126 | return wrapped 127 | 128 | 129 | def creator_button_only(func): 130 | @wraps(func) 131 | def wrapped(bot, query, *args, **kwargs): 132 | if query.message.chat.type == "private": 133 | return 134 | if not query.message.chat.get_member(query.from_user.id).status in ["creator"]: 135 | lang = get_db_lang(query.from_user.id) 136 | text = get_lang.get_string(lang, "button_for_creator") 137 | query.answer(text=text, show_alert=True) 138 | return 139 | return func(bot, query, *args, **kwargs) 140 | return wrapped 141 | 142 | 143 | def admin_button_only(func): 144 | @wraps(func) 145 | def wrapped(bot, query, *args, **kwargs): 146 | if query.message.chat.type == "private": 147 | return 148 | if not query.message.chat.get_member(query.from_user.id).status in ["administrator", "creator"]: 149 | lang = get_db_lang(query.from_user.id) 150 | text = get_lang.get_string(lang, "button_for_admins") 151 | query.answer(text=text, show_alert=True) 152 | return 153 | return func(bot, query, *args, **kwargs) 154 | return wrapped 155 | 156 | 157 | def invalid_command(bot, update): 158 | lang = get_db_lang(update.effective_user.id) 159 | text = get_lang.get_string(lang, "invalid_command") 160 | update.message.reply_text(text=text, quote=True) 161 | 162 | 163 | def send_message_long( 164 | bot, chat_id, text, parse_mode=None, disable_web_page_preview=None, 165 | disable_notification=False, reply_to_message_id=None, 166 | reply_markup=None, timeout=None): 167 | 168 | list_messages = [] 169 | chars_limit = ptb_consts.MAX_MESSAGE_LENGTH 170 | 171 | for i in range(0, len(text), chars_limit): 172 | splitted_message = text[i:i+chars_limit] 173 | list_messages.append(splitted_message) 174 | 175 | for message in list_messages: 176 | if message == list_messages[0]: 177 | first = bot.sendMessage( 178 | chat_id=chat_id, text=message, parse_mode=parse_mode, 179 | disable_web_page_preview=disable_web_page_preview, 180 | disable_notification=disable_notification, 181 | reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, 182 | timeout=timeout) 183 | else: 184 | bot.sendMessage( 185 | chat_id=chat_id, text=message, parse_mode=parse_mode, 186 | disable_web_page_preview=disable_web_page_preview, 187 | disable_notification=disable_notification, 188 | reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, 189 | timeout=timeout) 190 | time.sleep(0.3) 191 | 192 | if len(text) > 1000: 193 | first.reply_text("\U00002B06", disable_notification, quote=True) 194 | 195 | 196 | def guessed_user_lang(bot, update): 197 | if update.message.from_user.language_code is None: 198 | return "en" 199 | user2code = str(update.effective_user.language_code.split("-")[0]) 200 | if user2code in supported_langs.PRIVATE_LANGS: 201 | return user2code 202 | else: 203 | return "en" 204 | 205 | 206 | def sep(num, none_is_zero=False): 207 | if num is None: 208 | return 0 if none_is_zero is False else None 209 | return "{:,}".format(num) 210 | 211 | 212 | def sep_l(num, locale='en', none_is_zero=False): 213 | if num is None: 214 | return None if none_is_zero is False else 0 215 | if locale is None: 216 | return "{:,}".format(num) 217 | try: 218 | return babel.numbers.format_decimal(num, locale=locale) 219 | except babel.core.UnknownLocaleError: 220 | return "{:,}".format(num) 221 | 222 | 223 | def formatted_datetime_l(datetime, locale='en', formate='medium', tzinfo=None): 224 | if datetime is None: 225 | return None 226 | if locale is None: 227 | locale = 'en' 228 | locale = locale.split("-")[0] # because babel have problems if contains '-' 229 | 230 | try: 231 | return format_datetime( 232 | datetime=datetime, 233 | locale=locale, 234 | format=formate, 235 | tzinfo=tzinfo) 236 | except babel.core.UnknownLocaleError: 237 | return format_datetime( 238 | datetime=datetime, 239 | locale='en', 240 | format=formate, 241 | tzinfo=tzinfo) 242 | 243 | 244 | def formatted_date_l(date, locale='en', formate='medium'): 245 | if date is None: 246 | return None 247 | if locale is None: 248 | locale = 'en' 249 | locale = locale.split("-")[0] # because babel have problems if contains '-' 250 | 251 | try: 252 | return format_date( 253 | date=date, 254 | locale=locale, 255 | format=formate) 256 | except babel.core.UnknownLocaleError: 257 | return format_date( 258 | date=date, 259 | locale='en', 260 | format=formate) 261 | 262 | 263 | def split_list_grouping_by_column(lst, index): 264 | res = {} 265 | for v in lst: 266 | if(v[index] not in res): 267 | res[v[index]] = [v] 268 | else: 269 | res[v[index]].append(v) 270 | return res 271 | 272 | 273 | def round_seconds(seconds, lang, short=False): 274 | if seconds < 60: 275 | return get_lang.get_string(lang, "seconds_ago").format(seconds) if short is False else get_lang.get_string(lang, "seconds_ago_short").format(seconds) 276 | elif 60 <= seconds < 60*60: 277 | unit = seconds/60 278 | r_unit = round(unit) 279 | return get_lang.get_string(lang, "about_minutes_ago").format(r_unit) if short is False else get_lang.get_string(lang, "about_minutes_ago_short").format(r_unit) 280 | else: 281 | unit = seconds/(60*60) 282 | r_unit = round(unit) 283 | return get_lang.get_string(lang, "about_hours_ago").format(r_unit) if short is False else get_lang.get_string(lang, "about_hours_ago").format(r_unit) 284 | 285 | 286 | def truncate(text, max_chars, place_holder='...'): 287 | if len(text) <= max_chars: 288 | return text 289 | return "{}{}".format(text[:max_chars-len(place_holder)], place_holder) 290 | 291 | 292 | def replace_markdown_chars(string): 293 | string = string.replace('*', '+') 294 | string = string.replace('_', '-') 295 | for char in ['[', ']', '(', ')']: 296 | string = string.replace(char, '|') 297 | string = string.replace('`', "'") 298 | return string 299 | 300 | 301 | def get_group_admins(bot, group_id, only_creator=False): 302 | admins = bot.getChatAdministrators(chat_id=group_id) 303 | if not only_creator: 304 | return admins 305 | creator = "" 306 | for admin in admins: 307 | if admin.status == 'creator': 308 | creator = admin 309 | return creator 310 | 311 | 312 | def text_mention_creator(bot, group_id): 313 | creator = get_group_admins(bot, group_id, only_creator=True) 314 | if creator is not None: 315 | text = "\n\n{}".format( 316 | creator.user.id, 317 | html.escape(creator.user.first_name) 318 | ) 319 | else: # there is not a creator 320 | admins = get_group_admins(bot, group_id) 321 | if len(admins) == 0: # no creator and no admins 322 | text = '' 323 | else: # mentions admins 324 | text = '' 325 | for admin in admins: 326 | text += "\n\n{}".format( 327 | admin.user.id, 328 | html.escape(admin.user.first_name) 329 | ) 330 | return text 331 | 332 | 333 | def vote_intro(group_id, lang): 334 | query = "SELECT username, title FROM supergroups_ref WHERE group_id = %s" 335 | extract = database.query_r(query, group_id, one=True) 336 | if extract is None: 337 | return None 338 | else: 339 | return get_lang.get_string(lang, "vote_this_group").format(group_id, extract[0], extract[1]) 340 | 341 | 342 | -------------------------------------------------------------------------------- /topsupergroupsbot/langs/pt_br.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | buttons_strings = { 18 | "leaderboard": "classificação", 19 | "about_you": "sobre você", 20 | "region": "região", 21 | "settings": "configurações", 22 | "info_and_help": "info & ajuda" 23 | } 24 | 25 | 26 | hello = "Olá!" 27 | 28 | choose_group_lang = ( 29 | "Escolha o idioma do grupo. " 30 | "Seu grupo será adicionado à classificação para o idioma correspondente. " 31 | "Não minta ou esse grupo será removido") 32 | 33 | group_settings = "Configurações do grupo:" 34 | messages_in_groups_position = "— {} mensagens em @{}. Sua posição: {}\n" 35 | have_adult = "Esse grupo tem conteúdo adulto?" 36 | here_group_vote_link = "Este é o link para que os usuários votem neste grupo" 37 | canceled = "Cancelado" 38 | already_this_page = "Você já está nesta página!" 39 | 40 | vote_this_group = "id: {}\nusername: @{}\ntitle: {}" 41 | already_voted = "Já votou {} em {}" 42 | vote = "votar" 43 | vote_from_one_to_five = "Classifique esse grupo de 1 a 5 estrelas" 44 | choose_your_lang = "Selecione seu idioma" 45 | 46 | group_lang_button = "Idioma do grupo" 47 | adult_button = "🔞" 48 | vote_link_button = "Link de votação" 49 | back = "Voltar" 50 | yes = "Sim" 51 | no = "Não" 52 | cancel = "Cancelar" 53 | 54 | this_command_only_private = "{} é somente para conversas privadas" 55 | this_command_only_admins = "{} é somente para o criador ou admins em grupos" 56 | this_command_only_creator = "{} é somente para o criador em grupos" 57 | but_you_can_use_in_private = ".\nMas você pode usar aqui." 58 | button_for_creator = "Este botão é somente para o criador do grupo" 59 | button_for_admins = "Este botão é somente para um admin" 60 | invalid_command = "comando inválido" 61 | 62 | cant_vote_this = "Eu não estou nesse grupo. Então, não é possível votar nele!" 63 | registered_vote = "Voto registrado!" 64 | updated_vote = "Voto atualizado!" 65 | 66 | choose_region = ( 67 | "Escolha sua região. Será seu filtro padrão de idioma quando " 68 | "solicitar a classificação") 69 | 70 | pre_leadervote = "Ordenada pela média de votos. Tendo, no mínimo, {} votos.\nRegião: {}" 71 | pre_leadermessage = "Ordenada pelas mensagens enviadas nesta semana(UTC).\nRegião: {}" 72 | pre_groupleaderboard = "Principais usuários por quantidade de mensagens enviadas nesta semana (UTC) em @{}." 73 | pre_leadermember = "Ordenado pela quantidade de membros.\nRegião: {}" 74 | 75 | 76 | private_lang_button = "Idioma" 77 | private_region_button = "Região" 78 | private_settings = "Configurações:" 79 | private_digest_button = "Resumo" 80 | private_your_own_digest_button = "Sobre você" 81 | private_groups_digest_button = "Sobre os grupos" 82 | private_digest = "Você recebe um resumo no final de cada semana. É possível habilitar ou desabilitar a qualquer momento" 83 | weekly_own_digest = "Você quer receber estatísticas semanais sobre você?" 84 | hello_name = "Olá, {name}" 85 | digest_of_the_week_global = ("Mais uma semana terminou!\nNesta última semana você enviou {} mensagens " 86 | "em {} grupos. Sua posição no ranking global: {}\n") 87 | 88 | digest_of_the_week_detail = "— {} mensagens em @{}. Sua posição: {}\n" 89 | 90 | generic_leaderboard = ( 91 | "Escolha um dos critérios para a ordem de classificação.\n" 92 | "Sua região: {}. Toque em /region caso queira ver grupos de outras regiões") 93 | 94 | by_members = "Por membros" 95 | by_messages = "Por mensagens" 96 | by_votes = "Por votos" 97 | 98 | 99 | help_commands = ( 100 | "aqui estão os comandos que você pode usar:\n\n" 101 | "/leaderboard - veja a classificação dos grupos\n" 102 | "/vote - vote em um grupo\n" 103 | "/aboutyou - veja estatísticas sobre você\n" 104 | "/settings - mude suas configurações\n" 105 | "/feedback - envie um feedback" 106 | ) 107 | 108 | help_message = "Este bot faz estatísticas e tabelas de classificação sobre grupos e seus membros" 109 | 110 | insert_param_vote = ( 111 | "Para votar em um grupo, envie o nome de usuário (independentemente de ter o '@')" 112 | " depois do comando /vote.\n\nExemplo:\n/vote @NonEntrate") 113 | 114 | 115 | disable = "Desabilitar" 116 | hey_no_lang_set = ( 117 | "Ei! Você ainda não definiu nenhum idioma. Portanto, em qual região este " 118 | "grupo deve estar?. Por favor, defina um idioma.") 119 | 120 | you_inactive_this_week = "Esta semana você ainda não enviou mensagens em grupos" 121 | this_week_you_sent_this = "Esta semana você já enviou:" 122 | you_globally_this_week = "Você já enviou globalmente {} mensagens em {} grupos durante esta semana. Sua posição em todo o mundo: {}" 123 | 124 | unsupported_chat = ( 125 | "Fui programado para participar apenas de grupos públicos. " 126 | "Este não é um grupo público. Vou embora, tchau!") 127 | 128 | banned_until_leave = "Este grupo foi banido. A proibição terminará em {} UTC. \nMotivo: {}.\nEstou saindo." 129 | not_specified = "Não especificado" 130 | 131 | group_digest_button = "Resumo" 132 | group_weekly_digest = ( 133 | "Gostaria de receber um resumo semanal deste grupo?" 134 | " Você pode mudar de idéia a qualquer momento.") 135 | 136 | 137 | groups_working = ( 138 | "Deseja que seu grupo faça parte de nossas tabelas de classificação? Basta adicionar este bot em" 139 | " seu grupo e certifique-se de definir o idioma correto do grupo. O grupo" 140 | " será adicionado à região que você especificou com o idioma.\n" 141 | "Se o seu grupo tiver conteúdo adulto, selecione a opção correta em /settings. Por favor" 142 | " certifique-se de inserir apenas as informações corretas ou podemos banir seu grupo do nosso bot.\n" 143 | "Não aplicamos nenhum tipo de censura sobre tópicos, mas podemos proibir grupos que trapacerem" 144 | " nas tabelas de classificação.\nEm /settings, você encontrará o link para redirecionar os usuários para" 145 | " votar no seu grupo.\n\n" 146 | "COMANDOS SUPORTADOS EM GRUPOS:\n" 147 | "/settings - definir as configurações do grupo\n" 148 | "/groupleaderboard - receber uma mensagem com a classificação dos usuários que mais enviaram " 149 | "mensagens no grupo durante a semana (UTC). parâmetro opcional: [número da página]\n" 150 | "/grouprank - Veja o ranking do grupo" 151 | ) 152 | 153 | weekly_groups_digest = ( 154 | "Olá! Mais uma semana terminou. Aqui estão algumas estatísticas do grupo:\n\n" 155 | 156 | "-- POR MENSAGEM --\n" 157 | "mensagens enviadas na última semana: {}\n" 158 | "mensagens enviadas nesta semana: {}\n" 159 | "diferença: {} percentual: {}\n" 160 | "posição na última semana: {}\n" 161 | "posição nesta semana: {}\n\n" 162 | 163 | "-- POR MEMBROS --\n" 164 | "membros na última semana: {}\n" 165 | "membros nesta semana: {}\n" 166 | "diferença: {} percentual: {}\n" 167 | "posição na última semana: {}\n" 168 | "posição nesta semana: {}\n\n" 169 | 170 | "-- POR MÉDIA DE VOTOS --\n" 171 | "média e número de votos na última semana: {}{}|({})\n" 172 | "média e número de votos nesta semana: {}{}|({})\n" 173 | "posição na última semana: {}\n" 174 | "posição nesta semana: {}\n\n" 175 | 176 | "-- POR USUÁRIOS ATIVOS --\n" 177 | "usuários ativos na última semana: {}\n" 178 | "usuários ativos nesta semana: {}\n" 179 | "diferença: {} percentual: {}\n" 180 | "posição na última semana: {}\n" 181 | "posição nesta semana: {}\n\n" 182 | 183 | "PRINCIPAIS USUÁRIOS DESTA SEMANA:\n" 184 | ) 185 | 186 | added_again = "Olá! Você quer verificar as configurações do grupo novamente?" 187 | 188 | feedback_message = ("A única maneira de enviar um feedback é enviar sua mensagem como resposta a esta mensagem" 189 | ".\n\nVocê pode enviar qualquer tipo de mensagem ou mídia.") 190 | 191 | thanks_feedback = "Feedback enviado com sucesso! obrigado!" 192 | 193 | feedback_flood = ("Desculpe, você já enviou muitos feedbacks. Aguarde uma resposta primeiro ou aguarde um pouco." 194 | "Não há como enviar esse feedback") 195 | 196 | from_developer = "Esta é uma mensagem do desenvolvedor do bot." 197 | 198 | feedback_reply = "Responder" 199 | 200 | unrecognized_button = ("uma mensagem iniciando e terminando com esse caractere é tratada como um botão. " 201 | "O botão que você pressionou é inválido. Enviei a você um teclado atualizado.\n\nSe você estiver " 202 | "enviando essa mensagem apenas como mensagem, acrescente outro caractere no início ou no final.") 203 | 204 | updating_buttons = "Atualizando botões" 205 | 206 | latest_update = "Atualização" 207 | seconds_ago = "{} segundos atrás" 208 | about_minutes_ago = "cerca de {} minutos atrás" 209 | about_hours_ago = "cerca de {} horas atrás" 210 | seconds_ago_short = "{} seg atrás" 211 | about_minutes_ago_short = "aprox {} min atrás" 212 | about_hours_ago_short = "aprox {}h atrás" 213 | 214 | group_rank = { 215 | "title": "RANKING DO GRUPO:", 216 | "by_messages": "Classificação por mensagens enviadas nesta semana (região: {}):", 217 | "by_members": "Classificação por quantidade de membros (região: {}):", 218 | "by_votes": "Classificação ordenada por média de votos (região: {}):", 219 | "position": "- Posição: {}", 220 | "updated": "{}: {}", 221 | "None": "Infelizmente este grupo não está em nenhuma tabela de classificação", 222 | "messages": "- mensagens: {}", 223 | "members": "- membros: {}", 224 | "votes": "- média|número de votos: {}|({})" 225 | } 226 | 227 | feedback = "feedback" 228 | source_code = "código fonte" 229 | commands = "comandos" 230 | how_to_use_in_groups = "uso em grupos" 231 | 232 | 233 | category = "Categoria" 234 | choose_group_category = "Escolha a categoria que melhor se encaixa nesse grupo. Não minta ou o grupo será banido." 235 | categories = { 236 | 'news': 'notícias', 237 | 'science_and_education': 'ciência e educação', 238 | 'religion': 'espiritualidade', 239 | 'entertainment': 'entretenimento', 240 | 'family_and_home': 'casa e família', 241 | 'sport': 'esportes', 242 | 'art_and_culture': 'arte e cultura', 243 | 'politics': 'política', 244 | 'information_technology': 'tecnologia', 245 | 'game_and_apps': 'jogos e aplicativos', 246 | 'love': 'relacionamento', 247 | 'tourism': 'turismo', 248 | 'economics': 'economia' 249 | } 250 | filter_by_category = 'filtrar categoria' 251 | choose_category_to_filter = "escolha uma das seguintes categorias para filtrar a classificação" 252 | remove_filter = "remover filtro" 253 | 254 | change_vote = "mudar voto" 255 | 256 | advanced_commands = "Comandos avançados" 257 | 258 | advanced_commands_text = ( 259 | "Comandos avançados:\n\n" 260 | "/leadervote - classificação ordenada por votos (parâmetros opcionais: [p=(número da página)] [c=(número da categoria)])\n" 261 | "/leadermember - classificação ordenada pelos membros (parâmetros opcionais: [p=(número da página)] [c=(número da categoria)]\n" 262 | "/leadermessage - classificação ordenada por mensagens (parâmetros opcionais: [p=(número da página)] [c=(número da categoria)]\n\n" 263 | "O número da categoria pode ser obtido contando os botões das categorias, começando da esquerda para a direita\n\n" 264 | "/grouprank - /grouprank [nome de usuário do grupo]\n" 265 | "/groupleaderboard - /groupleaderboard [nome de usuário do grupo]" 266 | ) 267 | 268 | groupleaderboard_command_error = "Erro:\nuse o comando dessa forma:\n\n{} [número da página(opcional)]" 269 | 270 | avdanced_leaderboard_command_error = "Erro:\nuse o comando dessa forma. Parâmetros são opcionais:\n\n{} [p=(número da página)] [c=(número da categoria)]" 271 | 272 | error_param_group_rank_private = ( 273 | "Erro:\nvocê deve escrever o nome de usuário do grupo para ver o ranking. " 274 | "O uso de \"@\" é opcional.\n\nExemplo: /grouprank meu_grupo" 275 | ) 276 | 277 | cant_check_this = "Desculpe, @{} não está em nossa base de dados." 278 | 279 | error_param_group_leaderboard_private = ( 280 | "Erro:\nvocê deve escrever o nome de usuário do grupo que deseja verificar a classificação. " 281 | "O uso de \"@\" é opcional.\n\nExemplo: /groupleaderboard meu_grupo\n\n" 282 | "Opcionalmente, você pode pular diretamente para uma página adicionando o parâmetro p=[número da página].\n\nExemplo: /groupleaderboard meu_grupo p=26" 283 | ) 284 | 285 | check_in_private = "Veja na conversa privada" 286 | 287 | official_channel = "canal oficial" 288 | donate = "doar" 289 | donate_intro = ("Este bot é gratuito, de código aberto e desenvido para a comunidade do Telegram.\n\nAinda assim, o desenvolvimento necessita " 290 | "de tempo e dinheiro para os custos com servidores. Ficaríamos muito felizes se puder ajudar neste projeto com uma pequena doação.\n\n" 291 | ) 292 | something_went_wrong = "Ops! Algo deu errado." 293 | -------------------------------------------------------------------------------- /topsupergroupsbot/langs/it.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | buttons_strings = { 18 | "leaderboard": "classifica", 19 | "about_you": "su di te", 20 | "region": "regione", 21 | "settings": "impostazioni", 22 | "info_and_help": "info & aiuto" 23 | } 24 | 25 | hello = "Ciao!" 26 | 27 | choose_group_lang = ( 28 | "Scegli la lingua del gruppo. " 29 | "Il gruppo verrà aggiunto nelle classifiche in base alla lingua inserita. " 30 | "Non mentire o banneremo il gruppo") 31 | 32 | group_settings = "Impostazioni gruppo:" 33 | messages_in_groups_position = "— {} messaggi in @{}. La tua posizione: {}\n" 34 | have_adult = "Questo gruppo ha contenuti per adulti?" 35 | here_group_vote_link = "Questo è il link per indirizzare gli utenti direttamente a votare questo gruppo." 36 | canceled = "Anullato" 37 | already_this_page = "Sei già in questa pagina!" 38 | 39 | vote_this_group = "id: {}\nusername: @{}\ntitolo: {}" 40 | already_voted = "Già votato {} il {}" 41 | vote = "vota" 42 | vote_from_one_to_five = "Vota questo gruppo da una a cinque stelle" 43 | choose_your_lang = "Seleziona la tua lingua" 44 | 45 | group_lang_button = "Lingua gruppo" 46 | adult_button = "Emoji adulti" 47 | vote_link_button = "Link per voto" 48 | back = "Indietro" 49 | yes = "Sì" 50 | no = "No" 51 | cancel = "Annulla" 52 | 53 | this_command_only_private = "{} è solo per le chat private" 54 | this_command_only_admins = "{} è solo per il creatore o gli admin nei gruppi" 55 | this_command_only_creator = "{} è solo per il creatore nei gruppi" 56 | but_you_can_use_in_private = ".\nMa puoi usarlo qui." 57 | button_for_creator = "Questo bottone è solo per il creatore" 58 | button_for_admins = "Questo bottone è solo per gli admin" 59 | invalid_command = "Comando non valido" 60 | 61 | cant_vote_this = "Non sono in questo gruppo, quindi non puoi votarlo, mi spiace!" 62 | registered_vote = "Voto registrato!" 63 | updated_vote = "Voto aggiornato!" 64 | 65 | choose_region = ("Scegli la tua regione. Sarà il filtro di default per la lingua dei gruppi quando " 66 | "richiedi una classifica") 67 | 68 | pre_leadervote = "Ordinati per media voti. Almeno {} voti.\nRegione: {}" 69 | pre_leadermessage = "Ordinati per numero di messaggi inviati questa settimana(UTC).\nRegione: {}" 70 | pre_groupleaderboard = "Top utenti ordinati per numero di messaggi inviati durante questa settimana(UTC) in @{}." 71 | pre_leadermember = "Ordinati per numero di membri.\nRegione: {}" 72 | 73 | 74 | private_lang_button = "Lingua" 75 | private_region_button = "Regione" 76 | private_settings = "Impostazioni:" 77 | private_digest_button = "Resoconto" 78 | private_your_own_digest_button = "Su di te" 79 | private_groups_digest_button = "Sui gruppi" 80 | private_digest = "Puoi ricevere un resoconto alla fine di ogni settimana. Abilita o disabilita quando vuoi" 81 | weekly_own_digest = "Vuoi ricevere statistiche su di te ogni volta che una settimana finisce?" 82 | hello_name = "Ciao {name}" 83 | digest_of_the_week_global = ( 84 | "Un'altra settimana è passata!\nDurante la scorsa settimana hai inviato in totale {} messaggi " 85 | "in {} gruppi. La tua posizione mondiale: {}\n") 86 | 87 | digest_of_the_week_detail = "— {} messaggi in @{}. La tua posizione: {}\n" 88 | 89 | generic_leaderboard = ( 90 | "Scegli uno dei seguenti criteri per ordinare la classifica.\n" 91 | "La tua regione: {}. Premi /region per vedere gruppi di altre regioni.") 92 | 93 | by_members = "Per membri" 94 | by_messages = "Per messaggi" 95 | by_votes = "Per voti" 96 | 97 | help_message = "Questo bot fa statistiche e classifiche di gruppi pubblici e dei loro utenti." 98 | 99 | help_commands = ( 100 | "Questi sono i comandi che puoi usare:\n\n" 101 | "/leaderboard - guarda classifiche dei gruppi\n" 102 | "/vote - vota un gruppo\n" 103 | "/aboutyou - ottieni statistiche su di te\n" 104 | "/settings - cambia le tue impostazioni\n" 105 | "/feedback - invia un feedback" 106 | ) 107 | 108 | insert_param_vote = ( 109 | "Per votare un gruppo invia il suo username (non importa se c'è o no '@')" 110 | " come parametro del comando /vote.\n\nEsempio:\n/vote @NonEntrate") 111 | 112 | 113 | disable = "Disabilita" 114 | hey_no_lang_set = ( 115 | "Hey! Non avete impostato nessuna lingua, quindi in che regione delle classifiche " 116 | "devo mettere questo gruppo?. No, seriamente, imposta la lingua.") 117 | 118 | you_inactive_this_week = "Questa settimana ancora non hai inviato messaggi nei gruppi" 119 | this_week_you_sent_this = "Questa settimana hai già inviato:" 120 | you_globally_this_week = "In totale hai già inviato {} messaggi in {} gruppi in questa settimana. La tua posizione mondiale: {}" 121 | 122 | unsupported_chat = ( 123 | "Sono stato programmato per far parte solo di gruppi pubblici. " 124 | "Questo non è un gruppo pubblico. Vi saluto!") 125 | 126 | banned_until_leave = "Questo gruppo è stato bannato. Il ban scadrà il {} UTC.\nMotivo: {}.\nVi saluto." 127 | not_specified = "Non specificato" 128 | 129 | group_digest_button = "Resoconto" 130 | group_weekly_digest = ( 131 | "Vuoi ricevere un resoconto di questo gruppo ogni volta che una settimana finisce?" 132 | " Puoi ripensarci in qualsiasi momento.") 133 | 134 | 135 | groups_working = ( 136 | "Vuoi che il tuo gruppo faccia parte delle nostre classifiche? Ti basta solo aggiungere" 137 | " questo bot nel tuo gruppo e impostare la giusta lingua del gruppo. Il gruppo sarà " 138 | "aggiunto nella regione di classifiche che hai specificato con la lingua.\n" 139 | "Se il tuo gruppo ha contenuti per adulti, selezionalo in /settings. Per piacere " 140 | "assicurati di inserire solamente valori corretti o potremmo bannare il tuo gruppo dal nostro bot.\n" 141 | "Non applichiamo nessun tipo di censura sugli argomenti, ma banniamo i gruppi che barano " 142 | " nelle classifiche.\nIn /settings potrai perfino trovare il link per indirizzarei tuoi utenti " 143 | " a votare direttamente il tuo gruppo.\n\n" 144 | "COMANDI SUPPPORTATI NEI GRUPPI:\n" 145 | "/settings - impostazioni gruppo\n" 146 | "/groupleaderboard - ottieni la classifica degli utenti che hanno scritto " 147 | "più messagi nel gruppo durante la settimana corrente (UTC). parametri opzionali: [numero della pagina]\n" 148 | "/grouprank - ricevi le posizioni del gruppo" 149 | ) 150 | 151 | weekly_groups_digest = ( 152 | "Ciao! Un'altra settimana è passata. Ecco alcune statistiche di questo gruppo:\n\n" 153 | 154 | "-- PER MESSAGGI --\n" 155 | "messaggi inviati la scorsa settimana: {}\n" 156 | "messaggi inviati questa settimana: {}\n" 157 | "differenza: {} in percentuale: {}\n" 158 | "posizione la scorsa settimana: {}\n" 159 | "posizione questa settimana: {}\n\n" 160 | 161 | "-- PER MEMBRI --\n" 162 | "membri la scorsa settimana: {}\n" 163 | "membri questa settimana: {}\n" 164 | "differenza: {} in percentuale: {}\n" 165 | "posizione la scorsa settimana: {}\n" 166 | "posizione questa settimana: {}\n\n" 167 | 168 | "-- PER MEDIA VOTI --\n" 169 | "media e numero di voti la scorsa settimana: {}{}|({})\n" 170 | "media e numero di voti questa settimana: {}{}|({})\n" 171 | "posizione la scorsa settimana: {}\n" 172 | "posizione questa settimana: {}\n\n" 173 | 174 | "-- PER UTENTI ATTIVI --\n" 175 | "utenti attivi la scorsa settimana: {}\n" 176 | "utenti attivi questa settimana: {}\n" 177 | "differenza: {} in percentuale: {}\n" 178 | "posizione la scorsa settimana: {}\n" 179 | "posizione questa settimana: {}\n\n" 180 | 181 | "Puoi seguire le classifiche anche su @topsupergruppi\n" 182 | "TOP UTENTI DELLA SETTIMANA:\n" 183 | ) 184 | 185 | added_again = "Ciao! Vuoi dare un'occhiata di nuovo alle impostazioni del gruppo?" 186 | 187 | feedback_message = ("L'unico modo per inviare il feedback è inviando il tuo messaggio come risposta di" 188 | " questo messaggio." 189 | ".\n\nPuoi inviare qualsiasi messaggio o media.") 190 | 191 | thanks_feedback = "Feedback inviato con successo! Grazie!" 192 | 193 | feedback_flood = ("Mi dispiace, ma hai già inviato un po' di feedback, aspetta prima una risposta o fai" 194 | " passare un po' di tempo prima. Impossibile inviare questo feedback") 195 | 196 | from_developer = "Questo è un messaggio da parte dello sviluppatore del bot" 197 | 198 | feedback_reply = "Rispondi" 199 | 200 | unrecognized_button = ("Un messaggio che inizia e finisce con quel carattere è considerato un " 201 | "bottone. Il bottone che hai premuto non è valido. Ti ho appena inviato una tastiera " 202 | "aggiornata.\n\nSe invece stavi semplicemente inviando un messaggio, non farlo iniziare " 203 | "e finire con quel carattere, ma aggiungi qualcosa all'inizio o alla fine.") 204 | 205 | updating_buttons = "Aggiorno i bottoni" 206 | 207 | latest_update = "Aggiornato" 208 | seconds_ago = "{} secondi fa" 209 | about_minutes_ago = "circa {} minuti fa" 210 | about_hours_ago = "circa {} ore fa" 211 | seconds_ago_short = "{} sec fa" 212 | about_minutes_ago_short = "~{} min fa" 213 | about_hours_ago_short = "~{} ore fa" 214 | 215 | 216 | group_rank = { 217 | "title": "RANK DEL GRUPPO:", 218 | "by_messages": "Classifica ordinata per numero di messaggi inviati questa settimana (regione: {}):", 219 | "by_members": "Classifica ordinata per numero di membri (regione: {}):", 220 | "by_votes": "Classifica ordinata per media voti (regione: {}):", 221 | "position": "- Posizione: {}", 222 | "updated": "{}: {}", 223 | "None": "Sfortunatamente questo gruppo non è in un nessuna classifica", 224 | "messages": "- messaggi: {}", 225 | "members": "- membri: {}", 226 | "votes": "- voti media|numero: {}|({})" 227 | } 228 | 229 | feedback = "feedback" 230 | source_code = "codice sorgente" 231 | commands = "comandi" 232 | how_to_use_in_groups = "uso nei gruppi" 233 | 234 | 235 | category = "Categoria" 236 | choose_group_category = "Scegli la categoria che si addice meglio a questo gruppo. Non mentire o banneremo il gruppo." 237 | 238 | categories = { 239 | 'news': 'news', 240 | 'science_and_education': 'scienza&educazione', 241 | 'religion': 'religione', 242 | 'entertainment': 'divertimento', 243 | 'family_and_home': 'famiglia&casa', 244 | 'sport': 'sport', 245 | 'art_and_culture': 'arte&cultura', 246 | 'politics': 'politica', 247 | 'information_technology': 'informatica&tecnologia', 248 | 'game_and_apps': 'giochi&app', 249 | 'love': 'love', 250 | 'tourism': 'turismo', 251 | 'economics': 'economia' 252 | } 253 | filter_by_category = 'filtra categoria' 254 | choose_category_to_filter = "scegli una delle seguenti categorie per filtrare la classifica" 255 | remove_filter = "rimuovi filtro" 256 | 257 | change_vote = "modifica voto" 258 | 259 | advanced_commands = "comandi avanzati" 260 | 261 | advanced_commands_text = ( 262 | "comandi avanzati:\n\n" 263 | "/leadervote - classifica per voti (parametri opzionali: [p=(numero pagina)] [c=(numero categoria)])\n" 264 | "/leadermember - classifica per membri (parametri opzionali: [p=(numero pagina)] [c=(numero categoria)]\n" 265 | "/leadermessage - classifica per messaggi (parametri opzionali: [p=(numero pagina)] [c=(numero categoria)]\n\n" 266 | "Il numero della categoria può essere ricavato contando i bottoni delle categorie da sinistra vestro destra\n\n" 267 | "/grouprank - /grouprank [username del gruppo]\n" 268 | "/groupleaderboard - /groupleaderboard [username del gruppo]" 269 | ) 270 | 271 | 272 | groupleaderboard_command_error = "Errore:\nusa il comando in questo modo:\n\n{} [numero della pagina(opzionale)]" 273 | 274 | avdanced_leaderboard_command_error = "Errore:\nusa il comando in questo modo. I parametri sono opzionali:\n\n{} [p=(numero pagina)] [c=(numero categoria)]" 275 | 276 | error_param_group_rank_private = ( 277 | "Errore:\nDevi scrivere come parametro di questo comando l'username del gruppo per il quale vuoi controllare il rank. " 278 | "Non importa se scrivi o no \"@\".\n\nEsempio: /grouprank il_mio_gruppo" 279 | ) 280 | 281 | cant_check_this = "Mi dispiace, @{} non è nel nostro database." 282 | 283 | error_param_group_leaderboard_private = ( 284 | "Errore:\nDevi scrivere come parametro di questo comando l'username del gruppo per il quale vuoi controllare il groupleaderboard. " 285 | "Non importa se scrivi o no \"@\".\n\nEsempio: /groupleaderboard il_mio_gruppo" 286 | "Se vuoi puoi saltare direttamente ad una pagina aggiungendo il parametro p=[numero pagina].\n\nEesempio: /groupleaderboard il_mio_gruppo p=26" 287 | ) 288 | 289 | check_in_private = "Controlla in privato" 290 | 291 | official_channel = "canale ufficiale" 292 | donate = "dona" 293 | donate_intro = ("Questo bot è gratis, opensource e sviluppato per le community di telegram.\n\nIn ogni modo lo sviluppo ha richiesto e continua " 294 | "a richiedere un sacco di tempo e soldi per le spese dei server. Ci farebbe molto piacere ricevere anche una piccola donazione per portare avanti " 295 | "il progetto.\n\n" 296 | ) 297 | something_went_wrong = "Ops! Qualcosa è andato storto." 298 | -------------------------------------------------------------------------------- /topsupergroupsbot/keyboards.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | # library 18 | from telegram import InlineKeyboardButton 19 | from telegram import ReplyKeyboardMarkup 20 | from telegram import KeyboardButton 21 | from telegram import ParseMode 22 | from telegram import ReplyKeyboardRemove 23 | from telegram import InlineKeyboardMarkup 24 | from telegram.error import (TelegramError, Unauthorized, BadRequest, TimedOut, ChatMigrated, NetworkError) 25 | 26 | # files 27 | from topsupergroupsbot import supported_langs 28 | from topsupergroupsbot import leaderboards 29 | from topsupergroupsbot import get_lang 30 | from topsupergroupsbot import emojis 31 | from topsupergroupsbot import constants as c 32 | from topsupergroupsbot import categories 33 | from topsupergroupsbot import config 34 | 35 | 36 | 37 | def check_groupleaderboard_in_private_button(lang, group_id): 38 | button = InlineKeyboardButton( 39 | text=get_lang.get_string(lang, "check_in_private"), 40 | url="https://t.me/{}?start=groupleaderboarddirectlink{}".format(c.GET_ME.username, group_id) 41 | ) 42 | return [button] 43 | 44 | 45 | def filter_category_button(lang, base, chosen_page): 46 | category = base.split(":")[4] 47 | if category != "": 48 | return [ 49 | InlineKeyboardButton( 50 | text=get_lang.get_string(lang, 'remove_filter'), 51 | callback_data=":".join(base.split(":")[:4]).format(page=1)+":" 52 | ) 53 | ] 54 | return [ 55 | InlineKeyboardButton( 56 | text=get_lang.get_string(lang, 'filter_by_category'), 57 | callback_data='fc:'+base.format(page=chosen_page) 58 | ) 59 | ] 60 | 61 | 62 | # INLINE KEYBOARDS 63 | 64 | def build_menu(buttons: list, 65 | n_cols: int, 66 | header_buttons: list = None, 67 | footer_buttons: list = None): 68 | menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)] 69 | if header_buttons: 70 | menu.insert(0, header_buttons) 71 | if footer_buttons: 72 | menu.append(footer_buttons) 73 | return menu 74 | 75 | 76 | def main_group_settings_kb(lang): 77 | button_lang = InlineKeyboardButton( 78 | text=get_lang.get_string(lang, "group_lang_button"), 79 | callback_data="group_lang") 80 | button_adult = InlineKeyboardButton( 81 | text=get_lang.get_string(lang, "adult_button"), 82 | callback_data="adult_contents") 83 | vote_link = InlineKeyboardButton( 84 | text=get_lang.get_string(lang, "vote_link_button"), 85 | callback_data="vote_link") 86 | digest = InlineKeyboardButton( 87 | text=get_lang.get_string(lang, "group_digest_button"), 88 | callback_data="digest_group") 89 | category = InlineKeyboardButton( 90 | text=get_lang.get_string(lang, "category"), 91 | callback_data="category" 92 | ) 93 | buttons_list = [[button_lang], [button_adult], [vote_link], [digest], [category]] 94 | keyboard = InlineKeyboardMarkup(buttons_list) 95 | return keyboard 96 | 97 | 98 | def select_group_lang_kb(group_lang, back=True): 99 | buttons_list = [] 100 | for i in sorted(supported_langs.GROUP_LANGS): 101 | buttons_list.append(InlineKeyboardButton( 102 | text=str(emojis.CURRENT_CHOICE+i if i == group_lang else i)+str(supported_langs.COUNTRY_FLAG[i]), 103 | callback_data="set_group_lang_"+str(i))) 104 | footer = InlineKeyboardButton( 105 | text=get_lang.get_string(group_lang, "back"), 106 | callback_data="main_group_settings_creator") 107 | footer_buttons = [footer] 108 | buttons_list = build_menu( 109 | buttons_list, 110 | n_cols=3, 111 | footer_buttons=footer_buttons if back is True else None) 112 | keyboard = InlineKeyboardMarkup(buttons_list) 113 | return keyboard 114 | 115 | 116 | def adult_content_kb(lang, value): 117 | yes = get_lang.get_string(lang, "yes") 118 | no = get_lang.get_string(lang, "no") 119 | back = get_lang.get_string(lang, "back") 120 | button_yes = InlineKeyboardButton( 121 | text=emojis.CURRENT_CHOICE+yes if value is True else yes, 122 | callback_data="set_adult_true") 123 | button_no = InlineKeyboardButton( 124 | text=emojis.CURRENT_CHOICE+no if value is False else no, 125 | callback_data="set_adult_false") 126 | button_back = InlineKeyboardButton( 127 | text=back, 128 | callback_data="main_group_settings_creator") 129 | buttons_list = [[button_yes, button_no], [button_back]] 130 | keyboard = InlineKeyboardMarkup(buttons_list) 131 | return keyboard 132 | 133 | 134 | def vote_group_kb(group_id, lang): 135 | buttons_list = [] 136 | for i in range(1, 6): 137 | buttons_list.append([InlineKeyboardButton( 138 | text=(emojis.STAR*i), 139 | callback_data="rate:{}:{}".format(i, group_id))]) 140 | buttons_list.append([InlineKeyboardButton( 141 | text=get_lang.get_string(lang, "cancel"), 142 | callback_data="rate:cancel:{}".format(group_id))]) 143 | keyboard = InlineKeyboardMarkup(buttons_list) 144 | return keyboard 145 | 146 | 147 | def change_vote_kb(group_id, lang, vote_first_time=False): 148 | button_back = InlineKeyboardButton( 149 | text=get_lang.get_string(lang, "change_vote") if vote_first_time is False else get_lang.get_string(lang, "vote"), 150 | callback_data="change_vote:{}".format(group_id)) 151 | buttons_list = [[button_back]] 152 | keyboard = InlineKeyboardMarkup(buttons_list) 153 | return keyboard 154 | 155 | 156 | def weekly_group_digest_kb(lang, value): 157 | yes = get_lang.get_string(lang, "yes") 158 | no = get_lang.get_string(lang, "no") 159 | back = get_lang.get_string(lang, "back") 160 | button_yes = InlineKeyboardButton( 161 | text=emojis.CURRENT_CHOICE+yes if value is True else yes, 162 | callback_data="set_weekly_group_digest:true") 163 | button_no = InlineKeyboardButton( 164 | text=emojis.CURRENT_CHOICE+no if value is False else no, 165 | callback_data="set_weekly_group_digest:false") 166 | button_back = InlineKeyboardButton( 167 | text=back, 168 | callback_data="main_group_settings_creator") 169 | buttons_list = [[button_yes, button_no], [button_back]] 170 | keyboard = InlineKeyboardMarkup(buttons_list) 171 | return keyboard 172 | 173 | 174 | def vote_link_kb(lang): 175 | button_back = InlineKeyboardButton( 176 | text=get_lang.get_string(lang, "back"), 177 | callback_data="main_group_settings_admin") 178 | buttons_list = [[button_back]] 179 | keyboard = InlineKeyboardMarkup(buttons_list) 180 | return keyboard 181 | 182 | 183 | def private_language_kb(lang, back=True): 184 | buttons_list = [] 185 | for i in sorted(supported_langs.PRIVATE_LANGS): 186 | buttons_list.append(InlineKeyboardButton( 187 | text=str(emojis.CURRENT_CHOICE+i if i == lang else i)+str(supported_langs.COUNTRY_FLAG[i]), 188 | callback_data="set_private_lang_"+str(i))) 189 | footer = InlineKeyboardButton( 190 | text=get_lang.get_string(lang, "back"), 191 | callback_data="main_private_settings") 192 | footer_buttons = [footer] 193 | buttons_list = build_menu( 194 | buttons_list, 195 | n_cols=3, 196 | footer_buttons=footer_buttons if back is True else None) 197 | keyboard = InlineKeyboardMarkup(buttons_list) 198 | return keyboard 199 | 200 | 201 | def private_region_kb(lang, region, back=True): 202 | buttons_list = [] 203 | for i in sorted(supported_langs.PRIVATE_REGIONS): 204 | buttons_list.append(InlineKeyboardButton( 205 | text=str(emojis.CURRENT_CHOICE+i if i == region else i)+str(supported_langs.COUNTRY_FLAG[i]), 206 | callback_data="set_private_region:"+str(i))) 207 | footer = InlineKeyboardButton( 208 | text=get_lang.get_string(lang, "back"), 209 | callback_data="main_private_settings") 210 | footer_buttons = [footer] 211 | buttons_list = build_menu( 212 | buttons_list, 213 | n_cols=3, 214 | footer_buttons=footer_buttons if back == True else None) 215 | keyboard = InlineKeyboardMarkup(buttons_list) 216 | return keyboard 217 | 218 | 219 | def main_private_settings_kb(lang): 220 | button0 = InlineKeyboardButton( 221 | text=get_lang.get_string(lang, "private_lang_button"), 222 | callback_data="private_lang") 223 | button1 = InlineKeyboardButton( 224 | text=get_lang.get_string(lang, "private_region_button"), 225 | callback_data="private_region") 226 | button2 = InlineKeyboardButton( 227 | text=get_lang.get_string(lang, "private_digest_button"), 228 | callback_data="private_digest_button") 229 | buttons_list = [[button0], [button1], [button2]] 230 | keyboard = InlineKeyboardMarkup(buttons_list) 231 | return keyboard 232 | 233 | 234 | def private_digest_kb(lang): 235 | button0 = InlineKeyboardButton( 236 | text=get_lang.get_string(lang, "private_your_own_digest_button"), 237 | callback_data="private_your_own_digest") 238 | button1 = InlineKeyboardButton( 239 | text=get_lang.get_string(lang, "private_groups_digest_button"), 240 | callback_data="private_groups_digest") 241 | button2 = InlineKeyboardButton( 242 | text=get_lang.get_string(lang, "back"), 243 | callback_data="main_private_settings") 244 | buttons_list = [[button0], [button1], [button2]] 245 | keyboard = InlineKeyboardMarkup(buttons_list) 246 | return keyboard 247 | 248 | 249 | def weekly_own_digest_kb(lang, value): 250 | yes = get_lang.get_string(lang, "yes") 251 | no = get_lang.get_string(lang, "no") 252 | back = get_lang.get_string(lang, "back") 253 | button_yes = InlineKeyboardButton( 254 | text=emojis.CURRENT_CHOICE+yes if value is True else yes, 255 | callback_data="set_weekly_own_digest:true") 256 | button_no = InlineKeyboardButton( 257 | text=emojis.CURRENT_CHOICE+no if value is False else no, 258 | callback_data="set_weekly_own_digest:false") 259 | button_back = InlineKeyboardButton( 260 | text=back, 261 | callback_data="back_private_digest") 262 | buttons_list = [[button_yes, button_no], [button_back]] 263 | keyboard = InlineKeyboardMarkup(buttons_list) 264 | return keyboard 265 | 266 | 267 | def generic_leaderboard_kb(lang, region): 268 | members = InlineKeyboardButton( 269 | text=get_lang.get_string(lang, "by_members"), 270 | callback_data="leaderboard_by:"+leaderboards.Leaderboard.MEMBERS+":"+region) 271 | messages = InlineKeyboardButton( 272 | text=get_lang.get_string(lang, "by_messages"), 273 | callback_data="leaderboard_by:"+leaderboards.Leaderboard.MESSAGES+":"+region) 274 | votes = InlineKeyboardButton( 275 | text=get_lang.get_string(lang, "by_votes"), 276 | callback_data="leaderboard_by:"+leaderboards.Leaderboard.VOTES+":"+region) 277 | buttons_list = [[members], [messages], [votes]] 278 | keyboard = InlineKeyboardMarkup(buttons_list) 279 | return keyboard 280 | 281 | 282 | def disable_private_own_weekly_digest_kb(lang): 283 | disable = InlineKeyboardButton( 284 | text=get_lang.get_string(lang, "disable"), 285 | callback_data="private_your_own_digest:new_msg") 286 | buttons_list = [[disable]] 287 | keyboard = InlineKeyboardMarkup(buttons_list) 288 | return keyboard 289 | 290 | 291 | def disable_group_weekly_digest_kb(lang): 292 | disable = InlineKeyboardButton( 293 | text=get_lang.get_string(lang, "disable"), 294 | callback_data="digest_group:new_msg") 295 | buttons_list = [[disable]] 296 | keyboard = InlineKeyboardMarkup(buttons_list) 297 | return keyboard 298 | 299 | 300 | def feedback_reply_kb(lang): 301 | reply = InlineKeyboardButton( 302 | text=get_lang.get_string(lang, "feedback_reply"), 303 | callback_data="feedback_reply") 304 | buttons_list = [[reply]] 305 | keyboard = InlineKeyboardMarkup(buttons_list) 306 | return keyboard 307 | 308 | 309 | def default_regular_buttons_kb(lang): 310 | leaderboards = c.BUTTON_START + get_lang.get_string_buttons(lang, "leaderboard") + c.BUTTON_END 311 | about_you = c.BUTTON_START + get_lang.get_string_buttons(lang, "about_you") + c.BUTTON_END 312 | region = c.BUTTON_START + get_lang.get_string_buttons(lang, "region") + c.BUTTON_END 313 | settings = c.BUTTON_START + get_lang.get_string_buttons(lang, "settings") + c.BUTTON_END 314 | info_and_help = c.BUTTON_START + get_lang.get_string_buttons(lang, "info_and_help") + c.BUTTON_END 315 | keyboard_buttons = [[leaderboards, about_you],[region, settings], [info_and_help]] 316 | return ReplyKeyboardMarkup(keyboard_buttons, resize_keyboard=True) 317 | 318 | 319 | def help_kb(lang): 320 | source_code = InlineKeyboardButton( 321 | text=get_lang.get_string(lang, "source_code"), 322 | url="https://github.com/91DarioDev/topsupergroupsbot" 323 | ) 324 | feedback = InlineKeyboardButton( 325 | text=get_lang.get_string(lang, "feedback"), 326 | callback_data="help_feedback" 327 | ) 328 | commands = InlineKeyboardButton( 329 | text=get_lang.get_string(lang, "commands"), 330 | callback_data="help_commands" 331 | ) 332 | group_usage = InlineKeyboardButton( 333 | text=get_lang.get_string(lang, "how_to_use_in_groups"), 334 | callback_data="help_how_to_use_in_groups" 335 | ) 336 | buttons_list = [[commands, group_usage], [feedback, source_code]] 337 | # optional buttons 338 | optional_buttons = [] 339 | if config.OFFICIAL_CHANNEL is not None: 340 | official_channel = InlineKeyboardButton( 341 | text=get_lang.get_string(lang, "official_channel"), 342 | url=config.OFFICIAL_CHANNEL 343 | ) 344 | optional_buttons.append(official_channel) 345 | 346 | if config.DONATE_ADDRESSES is not None: 347 | donate_button = InlineKeyboardButton( 348 | text=get_lang.get_string(lang, "donate"), 349 | callback_data="donate_button" 350 | ) 351 | optional_buttons.append(donate_button) 352 | 353 | grouped_optional_buttons = [optional_buttons[i:i + 2] for i in range(0, len(optional_buttons), 2)] 354 | for i in grouped_optional_buttons: 355 | buttons_list.append(i) 356 | 357 | keyboard = InlineKeyboardMarkup(buttons_list) 358 | return keyboard 359 | 360 | 361 | def back_main_private_help_kb(lang): 362 | back = InlineKeyboardButton( 363 | text=get_lang.get_string(lang, "back"), 364 | callback_data="back_main_private_help" 365 | ) 366 | buttons_list = [[back]] 367 | keyboard = InlineKeyboardMarkup(buttons_list) 368 | return keyboard 369 | 370 | 371 | def group_categories_kb(lang, current_category): 372 | buttons_list = [] 373 | strings = get_lang.get_string(lang, "categories") 374 | for i in sorted(categories.CODES.items(), key=lambda x: x[1]): 375 | buttons_list.append(InlineKeyboardButton( 376 | text=emojis.CURRENT_CHOICE+strings[i[1]] if i[0] == current_category else strings[i[1]], 377 | callback_data="set_group_category:"+str(i[0])) 378 | ) 379 | footer = InlineKeyboardButton( 380 | text=get_lang.get_string(lang, "back"), 381 | callback_data="main_group_settings_creator") 382 | footer_buttons = [footer] 383 | buttons_list = build_menu( 384 | buttons_list, 385 | n_cols=2, 386 | footer_buttons=footer_buttons 387 | ) 388 | keyboard = InlineKeyboardMarkup(buttons_list) 389 | return keyboard 390 | 391 | 392 | def filter_by_category_leaderboard_kb(lang, base, back_callback): 393 | buttons_list = [] 394 | strings = get_lang.get_string(lang, "categories") 395 | for i in sorted(categories.CODES.items(), key=lambda x: x[1]): 396 | buttons_list.append(InlineKeyboardButton( 397 | text=strings[i[1]], 398 | callback_data=base+str(i[0])) 399 | ) 400 | footer = InlineKeyboardButton( 401 | text=get_lang.get_string(lang, "back"), 402 | callback_data=back_callback) 403 | footer_buttons = [footer] 404 | buttons_list = build_menu( 405 | buttons_list, 406 | n_cols=2, 407 | footer_buttons=footer_buttons 408 | ) 409 | keyboard = InlineKeyboardMarkup(buttons_list) 410 | return keyboard 411 | 412 | 413 | def advanced_commands_kb(lang): 414 | advanced_commands = InlineKeyboardButton( 415 | text=get_lang.get_string(lang, "advanced_commands"), 416 | callback_data="advanced_commands" 417 | ) 418 | back = InlineKeyboardButton( 419 | text=get_lang.get_string(lang, "back"), 420 | callback_data="back_main_private_help" 421 | ) 422 | buttons_list = [[advanced_commands], [back]] 423 | keyboard = InlineKeyboardMarkup(buttons_list) 424 | return keyboard 425 | 426 | 427 | def back_commands_kb(lang): 428 | back = InlineKeyboardButton( 429 | text=get_lang.get_string(lang, "back"), 430 | callback_data="back_commands" 431 | ) 432 | buttons_list = [[back]] 433 | keyboard = InlineKeyboardMarkup(buttons_list) 434 | return keyboard 435 | -------------------------------------------------------------------------------- /topsupergroupsbot/digest_supergroups.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | import html 18 | 19 | from topsupergroupsbot import database 20 | from topsupergroupsbot import get_lang 21 | from topsupergroupsbot import keyboards 22 | from topsupergroupsbot import utils 23 | from topsupergroupsbot import emojis 24 | from topsupergroupsbot import leaderboards 25 | from topsupergroupsbot import constants as c 26 | 27 | from telegram.error import (TelegramError, 28 | Unauthorized, 29 | BadRequest, 30 | TimedOut, 31 | ChatMigrated, 32 | NetworkError) 33 | from telegram.ext.dispatcher import run_async 34 | 35 | 36 | @run_async 37 | def weekly_groups_digest(bot, job): 38 | near_interval = '7 days' 39 | far_interval = '14 days' 40 | 41 | query = """ 42 | SELECT 43 | group_id, 44 | lang, 45 | nsfw, 46 | joined_the_bot 47 | FROM supergroups 48 | WHERE weekly_digest = TRUE AND bot_inside = TRUE 49 | ORDER BY last_date DESC 50 | """ 51 | lst = database.query_r(query) 52 | 53 | ############# 54 | # MESSAGES 55 | ############ 56 | 57 | query = """ 58 | SELECT 59 | group_id, 60 | COUNT(msg_id) AS msgs, 61 | RANK() OVER(PARTITION BY s.lang ORDER BY COUNT(msg_id) DESC) 62 | FROM messages 63 | LEFT OUTER JOIN supergroups as s 64 | USING (group_id) 65 | WHERE 66 | message_date > now() - interval %s 67 | AND (s.banned_until IS NULL OR s.banned_until < now()) 68 | AND s.bot_inside IS TRUE 69 | GROUP BY s.lang, group_id 70 | 71 | """ 72 | msgs_this_week = database.query_r(query, near_interval) 73 | 74 | query = """ 75 | SELECT 76 | group_id, 77 | COUNT(msg_id) AS msgs, 78 | RANK() OVER(PARTITION BY s.lang ORDER BY COUNT(msg_id) DESC) 79 | FROM messages 80 | LEFT OUTER JOIN supergroups as s 81 | USING (group_id) 82 | WHERE 83 | message_date BETWEEN now() - interval %s AND now() - interval %s 84 | AND (s.banned_until IS NULL OR s.banned_until < now()) 85 | AND s.bot_inside IS TRUE 86 | GROUP BY s.lang, group_id 87 | """ 88 | msgs_last_week = database.query_r(query, far_interval, near_interval) 89 | 90 | ############# 91 | # MEMBERS 92 | ############ 93 | 94 | query = """ 95 | SELECT 96 | last_members.group_id, 97 | last_members.amount, 98 | RANK() OVER(PARTITION BY s.lang ORDER BY last_members.amount DESC) 99 | FROM 100 | ( 101 | SELECT 102 | *, 103 | ROW_NUMBER() OVER ( 104 | PARTITION BY group_id 105 | ORDER BY updated_date DESC 106 | ) AS row 107 | FROM members 108 | ) AS last_members 109 | LEFT OUTER JOIN supergroups AS s 110 | USING (group_id) 111 | WHERE 112 | last_members.row=1 113 | AND (s.banned_until IS NULL OR s.banned_until < now()) 114 | AND s.bot_inside IS TRUE 115 | """ 116 | members_this_week = database.query_r(query) 117 | 118 | 119 | query = """ 120 | SELECT 121 | last_members.group_id, 122 | last_members.amount, 123 | RANK() OVER(PARTITION BY s.lang ORDER BY last_members.amount DESC) 124 | FROM 125 | ( 126 | SELECT 127 | *, 128 | ROW_NUMBER() OVER ( 129 | PARTITION BY group_id 130 | ORDER BY updated_date DESC 131 | ) AS row 132 | FROM members 133 | WHERE updated_date <= now() - interval %s 134 | ) AS last_members 135 | LEFT OUTER JOIN supergroups AS s 136 | USING (group_id) 137 | WHERE 138 | last_members.row=1 139 | AND (s.banned_until IS NULL OR s.banned_until < now()) 140 | AND s.bot_inside IS TRUE 141 | """ 142 | members_last_week = database.query_r(query, near_interval) 143 | 144 | #################### 145 | # SUM AND AVG VOTES 146 | #################### 147 | 148 | query = """ 149 | WITH myconst AS 150 | (SELECT 151 | s.lang, 152 | AVG(vote)::float AS overall_avg 153 | FROM votes AS v 154 | LEFT OUTER JOIN supergroups AS s 155 | ON s.group_id = v.group_id 156 | WHERE (s.banned_until IS NULL OR s.banned_until < now() ) 157 | AND s.bot_inside IS TRUE 158 | GROUP BY s.lang 159 | HAVING COUNT(vote) >= %s) 160 | 161 | SELECT 162 | *, 163 | RANK() OVER (PARTITION BY sub.lang ORDER BY bayesan DESC) 164 | FROM ( 165 | SELECT 166 | v.group_id, 167 | s_ref.title, 168 | s_ref.username, 169 | COUNT(vote) AS amount, 170 | ROUND(AVG(vote), 1)::float AS average, 171 | s.nsfw, 172 | extract(epoch from s.joined_the_bot at time zone 'utc') AS dt, 173 | s.lang, 174 | s.category, 175 | -- (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C 176 | -- * R = average for the movie (mean) = (Rating) 177 | -- * v = number of votes for the movie = (votes) 178 | -- * m = minimum votes required to be listed in the Top 250 (currently 1300) 179 | -- * C = the mean vote across the whole report (currently 6.8) 180 | ( (COUNT(vote)::float / (COUNT(vote)+%s)) * AVG(vote)::float + (%s::float / (COUNT(vote)+%s)) * (m.overall_avg) ) AS bayesan 181 | FROM votes AS v 182 | LEFT OUTER JOIN supergroups_ref AS s_ref 183 | ON s_ref.group_id = v.group_id 184 | LEFT OUTER JOIN supergroups AS s 185 | ON s.group_id = v.group_id 186 | LEFT OUTER JOIN myconst AS m 187 | ON (s.lang = m.lang) 188 | GROUP BY v.group_id, s_ref.title, s_ref.username, s.nsfw, s.banned_until, s.lang, s.category, s.bot_inside, s.joined_the_bot, m.overall_avg 189 | HAVING 190 | (s.banned_until IS NULL OR s.banned_until < now()) 191 | AND COUNT(vote) >= %s 192 | AND s.bot_inside IS TRUE 193 | ) AS sub; 194 | """ 195 | this_week_votes_avg = database.query_r( 196 | query, 197 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 198 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 199 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 200 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 201 | leaderboards.VotesLeaderboard.MIN_REVIEWS 202 | ) 203 | 204 | query = """ 205 | SELECT 206 | group_id, 207 | COUNT(vote) AS amount, 208 | ROUND(AVG(vote), 1) AS average, 209 | RANK() OVER(PARTITION BY s.lang ORDER BY ROUND(AVG(VOTE), 1)DESC, COUNT(VOTE)DESC) 210 | FROM votes 211 | LEFT OUTER JOIN supergroups AS s 212 | USING (group_id) 213 | WHERE vote_date <= now() - interval %s 214 | GROUP BY group_id, s.lang, s.banned_until, s.bot_inside 215 | HAVING 216 | (s.banned_until IS NULL OR s.banned_until < now()) 217 | AND COUNT(vote) >= %s 218 | AND s.bot_inside IS TRUE 219 | """ 220 | query = """ 221 | WITH myconst AS 222 | (SELECT 223 | s.lang, 224 | AVG(vote)::float AS overall_avg 225 | FROM votes AS v 226 | LEFT OUTER JOIN supergroups AS s 227 | ON s.group_id = v.group_id 228 | WHERE (s.banned_until IS NULL OR s.banned_until < now() ) AND vote_date <= now() - interval %s 229 | AND s.bot_inside IS TRUE 230 | GROUP BY s.lang 231 | HAVING COUNT(vote) >= %s) 232 | 233 | SELECT 234 | *, 235 | RANK() OVER (PARTITION BY sub.lang ORDER BY bayesan DESC) 236 | FROM ( 237 | SELECT 238 | v.group_id, 239 | s_ref.title, 240 | s_ref.username, 241 | COUNT(vote) AS amount, 242 | ROUND(AVG(vote), 1)::float AS average, 243 | s.nsfw, 244 | extract(epoch from s.joined_the_bot at time zone 'utc') AS dt, 245 | s.lang, 246 | s.category, 247 | -- (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C 248 | -- * R = average for the movie (mean) = (Rating) 249 | -- * v = number of votes for the movie = (votes) 250 | -- * m = minimum votes required to be listed in the Top 250 (currently 1300) 251 | -- * C = the mean vote across the whole report (currently 6.8) 252 | ( (COUNT(vote)::float / (COUNT(vote)+%s)) * AVG(vote)::float + (%s::float / (COUNT(vote)+%s)) * (m.overall_avg) ) AS bayesan 253 | FROM votes AS v 254 | LEFT OUTER JOIN supergroups_ref AS s_ref 255 | ON s_ref.group_id = v.group_id 256 | LEFT OUTER JOIN supergroups AS s 257 | ON s.group_id = v.group_id 258 | LEFT OUTER JOIN myconst AS m 259 | ON (s.lang = m.lang) 260 | WHERE vote_date <= now() - interval %s 261 | GROUP BY v.group_id, s_ref.title, s_ref.username, s.nsfw, s.banned_until, s.lang, s.category, s.bot_inside, s.joined_the_bot, m.overall_avg 262 | HAVING 263 | (s.banned_until IS NULL OR s.banned_until < now()) 264 | AND COUNT(vote) >= %s 265 | AND s.bot_inside IS TRUE 266 | ) AS sub; 267 | """ 268 | last_week_votes_avg = database.query_r( 269 | query, 270 | near_interval, 271 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 272 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 273 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 274 | leaderboards.VotesLeaderboard.MIN_REVIEWS, 275 | near_interval, 276 | leaderboards.VotesLeaderboard.MIN_REVIEWS 277 | ) 278 | 279 | ################## 280 | # ACTIVE USERS 281 | ################## 282 | 283 | query = """ 284 | SELECT 285 | group_id, 286 | COUNT(DISTINCT user_id), 287 | RANK() OVER(PARTITION BY s.lang ORDER BY COUNT(DISTINCT user_id) DESC) 288 | FROM messages 289 | LEFT OUTER JOIN supergroups AS s 290 | USING (group_id) 291 | WHERE 292 | message_date > (now() - interval %s) 293 | AND (s.banned_until IS NULL OR s.banned_until < now()) 294 | AND s.bot_inside IS TRUE 295 | GROUP BY group_id, s.lang 296 | """ 297 | this_week_active_users = database.query_r(query, near_interval) 298 | 299 | query = """ 300 | SELECT 301 | group_id, 302 | COUNT(DISTINCT user_id), 303 | RANK() OVER(PARTITION BY s.lang ORDER BY COUNT(DISTINCT user_id) DESC) 304 | FROM messages 305 | LEFT OUTER JOIN supergroups AS s 306 | USING (group_id) 307 | WHERE 308 | message_date BETWEEN (now() - interval %s) AND (now() - interval %s) 309 | AND (s.banned_until IS NULL OR s.banned_until < now()) 310 | AND s.bot_inside IS TRUE 311 | GROUP BY group_id, s.lang 312 | """ 313 | last_week_active_users = database.query_r(query, far_interval, near_interval) 314 | 315 | start_in = 0 316 | for group in lst: 317 | start_in += 0.1 318 | group_id = group[0] 319 | lang = group[1] 320 | 321 | msgs_new = 0 322 | msgs_old = 0 323 | msgs_pos_old = 0 324 | msgs_pos_new = 0 325 | 326 | members_new = 0 327 | members_old = 0 328 | members_pos_old = 0 329 | members_pos_new = 0 330 | 331 | sum_v_new = 0 332 | avg_v_new = 0 333 | sum_v_old = 0 334 | avg_v_old = 0 335 | avg_pos_old = 0 336 | avg_pos_new = 0 337 | 338 | act_users_new = 0 339 | act_users_old = 0 340 | act_users_pos_old = 0 341 | act_users_pos_new = 0 342 | 343 | for i in msgs_this_week: 344 | if i[0] == group_id: 345 | msgs_new = i[1] 346 | msgs_pos_new = i[2] 347 | break 348 | 349 | for i in msgs_last_week: 350 | if i[0] == group_id: 351 | msgs_old = i[1] 352 | msgs_pos_old = i[2] 353 | break 354 | 355 | for i in members_this_week: 356 | if i[0] == group_id: 357 | members_new = i[1] 358 | members_pos_new = i[2] 359 | break 360 | 361 | for i in members_last_week: 362 | if i[0] == group_id: 363 | members_old = i[1] 364 | members_pos_old = i[2] 365 | break 366 | 367 | for i in this_week_votes_avg: 368 | if i[0] == group_id: 369 | sum_v_new = i[3] 370 | avg_v_new = i[4] 371 | avg_pos_new = i[10] 372 | break 373 | 374 | for i in last_week_votes_avg: 375 | if i[0] == group_id: 376 | sum_v_old = i[3] 377 | avg_v_old = i[4] 378 | avg_pos_old = i[10] 379 | break 380 | 381 | for i in this_week_active_users: 382 | if i[0] == group_id: 383 | act_users_new = i[1] 384 | act_users_pos_new = i[2] 385 | break 386 | 387 | for i in last_week_active_users: 388 | if i[0] == group_id: 389 | act_users_old = i[1] 390 | act_users_pos_old = i[2] 391 | break 392 | 393 | diff_msg, percent_msg = diff_percent(msgs_new, msgs_old, lang) 394 | diff_members, percent_members = diff_percent(members_new, members_old, lang) 395 | diff_act, percent_act = diff_percent(act_users_new, act_users_old, lang) 396 | 397 | text = get_lang.get_string(lang, "weekly_groups_digest").format( 398 | # by messages 399 | utils.sep_l(msgs_old, lang), 400 | utils.sep_l(msgs_new, lang), 401 | diff_msg, percent_msg, 402 | utils.sep_l(msgs_pos_old, lang), 403 | utils.sep_l(msgs_pos_new, lang), 404 | # by members 405 | utils.sep_l(members_old, lang), 406 | utils.sep_l(members_new, lang), 407 | diff_members, percent_members, 408 | utils.sep_l(members_pos_old, lang), 409 | utils.sep_l(members_pos_new, lang), 410 | # by votes average 411 | utils.sep_l(avg_v_old, lang), emojis.STAR, utils.sep_l(sum_v_old, lang), 412 | utils.sep_l(avg_v_new, lang), emojis.STAR, utils.sep_l(sum_v_new, lang), 413 | utils.sep_l(avg_pos_old, lang), 414 | utils.sep_l(avg_pos_new, lang), 415 | # by active users 416 | utils.sep_l(act_users_old, lang), 417 | utils.sep_l(act_users_new, lang), 418 | diff_act, percent_act, 419 | utils.sep_l(act_users_pos_old, lang), 420 | utils.sep_l(act_users_pos_new, lang) 421 | ) 422 | 423 | ############## 424 | # TOP n USERS 425 | ############## 426 | 427 | query_top_users = """ 428 | SELECT 429 | user_id, 430 | COUNT(msg_id) AS num_msgs, 431 | name, 432 | RANK() OVER (ORDER BY COUNT(msg_id) DESC) 433 | FROM messages AS m 434 | LEFT OUTER JOIN users_ref AS u_ref 435 | USING (user_id) 436 | WHERE group_id = %s AND m.message_date > (now() - interval %s) 437 | GROUP BY user_id, name 438 | LIMIT %s 439 | """ 440 | top_users_of_the_group = database.query_r(query_top_users, group_id, near_interval, 10) 441 | for user in top_users_of_the_group: 442 | text += "{}) {}: {}\n".format( 443 | user[3], 444 | user[0], 445 | html.escape(utils.truncate(user[2], c.MAX_CHARS_LEADERBOARD_PAGE_GROUP)), 446 | utils.sep_l(user[1], lang) 447 | ) 448 | 449 | text += "\n#weekly_group_digest" 450 | reply_markup = keyboards.disable_group_weekly_digest_kb(lang) 451 | # schedule send 452 | job.job_queue.run_once( 453 | send_one_by_one_weekly_group_digest, 454 | start_in, 455 | context=[group_id, text, reply_markup] 456 | ) 457 | 458 | 459 | def diff_percent(new, old, lang): 460 | diff = new - old 461 | diff_s = utils.sep_l(diff, lang) if diff < 0 else "+"+utils.sep_l(diff, lang) 462 | try: 463 | percent = round(diff*100/old, 2) 464 | percent_s = (utils.sep_l(percent, lang) if percent < 0 else "+"+utils.sep_l(percent, lang))+"%" 465 | except ZeroDivisionError: 466 | percent_s = "—" 467 | return diff_s, percent_s 468 | 469 | 470 | @run_async 471 | def send_one_by_one_weekly_group_digest(bot, job): 472 | group_id = job.context[0] 473 | message = job.context[1] 474 | reply_markup = job.context[2] 475 | try: 476 | bot.send_message( 477 | chat_id=group_id, 478 | text=message, 479 | reply_markup=reply_markup, 480 | parse_mode='HTML', 481 | disable_notification=True) 482 | except Unauthorized: 483 | query = "UPDATE supergroups SET bot_inside = FALSE WHERE group_id = %s" 484 | database.query_w(query, group_id) 485 | except Exception as e: 486 | print("{} exception is send_one_by_one group digest".format(e)) 487 | -------------------------------------------------------------------------------- /topsupergroupsbot/commands.py: -------------------------------------------------------------------------------- 1 | # TopSupergroupsBot - A telegram bot for telegram public groups leaderboards 2 | # Copyright (C) 2017-2018 Dario (github.com/91DarioDev) 3 | # 4 | # TopSupergroupsBot is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published 6 | # by the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # TopSupergroupsBot is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with TopSupergroupsBot. If not, see . 16 | 17 | 18 | import time 19 | import html 20 | 21 | from topsupergroupsbot import utils 22 | from topsupergroupsbot import keyboards 23 | from topsupergroupsbot import database 24 | from topsupergroupsbot import constants 25 | from topsupergroupsbot import votelink 26 | from topsupergroupsbot import leaderboards 27 | from topsupergroupsbot import messages_supergroups 28 | from topsupergroupsbot import get_lang 29 | from topsupergroupsbot import supported_langs 30 | from topsupergroupsbot import emojis 31 | from topsupergroupsbot import cache_users_stats 32 | from topsupergroupsbot import cache_groups_rank 33 | 34 | 35 | def first_start(bot, update): 36 | user_id = update.message.from_user.id 37 | query = """ 38 | SELECT 1 39 | FROM USERS 40 | WHERE user_id = %s 41 | FETCH FIRST 1 ROW ONLY 42 | """ 43 | extract = database.query_r(query, user_id, one=True) 44 | if extract is None: # this is the first time the user starts the bot 45 | # send region choose 46 | guessed_lang = utils.guessed_user_lang(bot, update) 47 | text = get_lang.get_string(guessed_lang, "choose_region") 48 | reply_markup = keyboards.private_region_kb(guessed_lang, guessed_lang) 49 | update.message.reply_text(text=text, reply_markup=reply_markup) 50 | 51 | 52 | @utils.private_only 53 | def start(bot, update, args): 54 | start_help_buttons(bot, update) 55 | if len(args) == 0: 56 | if update.message.chat.type == "private": 57 | start_no_params(bot, update) 58 | return 59 | first_arg = args[0] 60 | if first_arg.lower().startswith("vote"): 61 | votelink.send_vote_by_link(bot, update, first_arg) 62 | elif first_arg == "aboutyou": 63 | aboutyou(bot, update) 64 | elif first_arg == "groups_working": 65 | send_groups_working(bot, update) 66 | return 67 | elif first_arg.startswith("groupleaderboarddirectlink"): 68 | group_id = first_arg.replace("groupleaderboarddirectlink", "") 69 | groupleaderboard_private_direct_link(bot, update, group_id) 70 | return 71 | 72 | 73 | def groupleaderboard_private_direct_link(bot, update, group_id): 74 | user_id = update.message.from_user.id 75 | lang = utils.get_db_lang(user_id) 76 | page = 1 77 | update.effective_chat.send_action('typing') 78 | query_db = "SELECT username FROM supergroups_ref WHERE group_id = %s LIMIT 1" 79 | extract = database.query_r(query_db, group_id, one=True) 80 | leaderboard = leaderboards.GroupLeaderboard(lang=lang, page=page, group_id=group_id) 81 | result = leaderboard.build_page(group_username=extract[0], only_admins=False) 82 | 83 | update.message.reply_text( 84 | text=result[0], 85 | reply_markup=result[1], 86 | parse_mode='MARKDOWN', 87 | disable_notification=True 88 | ) 89 | 90 | 91 | def start_no_params(bot, update): 92 | lang = utils.get_db_lang(update.message.from_user.id) 93 | text = get_lang.get_string(lang, "help_message") 94 | reply_markup = keyboards.help_kb(lang) 95 | update.message.reply_text(text=text, parse_mode="HTML", reply_markup=reply_markup) 96 | 97 | 98 | def settings(bot, update): 99 | if update.message.chat.type == "private": 100 | settings_private(bot, update) 101 | elif update.message.chat.type in ['group', 'supergroup']: 102 | settings_group(bot, update) 103 | 104 | 105 | def group_rank_private(bot, update, args): 106 | user_id = update.message.from_user.id 107 | lang = utils.get_db_lang(user_id) 108 | if len(args) != 1: 109 | text = get_lang.get_string(lang, "error_param_group_rank_private") 110 | update.message.reply_text(text, parse_mode="HTML") 111 | return 112 | username = args[0] 113 | if username.startswith("@"): 114 | username = username.replace("@", "") 115 | 116 | query = "SELECT group_id FROM supergroups_ref WHERE LOWER(username) = LOWER(%s)" 117 | extract = database.query_r(query, username) 118 | 119 | if len(extract) > 1: 120 | print("error too many") 121 | return 122 | 123 | if len(extract) == 0: 124 | # the group does not exist otherwise anything is returned and if None is NULL 125 | text = get_lang.get_string(lang, "cant_check_this").format(html.escape(username)) 126 | update.message.reply_text(text=text) 127 | return 128 | 129 | group_id = extract[0][0] 130 | update.message.reply_text(text=group_rank_text(group_id, lang), parse_mode="HTML") 131 | 132 | 133 | def groupleaderboard_private(bot, update, args): 134 | user_id = update.message.from_user.id 135 | lang = utils.get_db_lang(user_id) 136 | if len(args) > 2 or len(args) == 0: 137 | text = get_lang.get_string(lang, "error_param_group_leaderboard_private") 138 | update.message.reply_text(text, parse_mode="HTML") 139 | return 140 | 141 | if len(args) == 2 and (any([arg.startswith("p=") for arg in args])) is False: 142 | 143 | text = get_lang.get_string(lang, "error_param_group_leaderboard_private") 144 | update.message.reply_text(text, parse_mode="HTML") 145 | return 146 | 147 | page = 1 148 | for arg in args: 149 | if arg.startswith('p='): 150 | try: 151 | page = int(arg.replace('p=', '')) 152 | except ValueError: 153 | update.message.reply_text(text=get_lang.get_string(lang, "error_param_group_leaderboard_private"), parse_mode='HTML') 154 | return 155 | if page <= 0: 156 | update.message.reply_text(text=get_lang.get_string(lang, "error_param_group_leaderboard_private"), parse_mode='HTML') 157 | else: 158 | group_username = arg 159 | if group_username.startswith("@"): 160 | group_username = group_username.replace("@", "") 161 | 162 | update.effective_chat.send_action('typing') 163 | query = "SELECT group_id FROM supergroups_ref WHERE LOWER(username) = LOWER(%s)" 164 | extract = database.query_r(query, group_username) 165 | 166 | if len(extract) > 1: 167 | print("error too many") 168 | return 169 | 170 | if len(extract) == 0: 171 | # the group does not exist otherwise anything is returned and if None is NULL 172 | text = get_lang.get_string(lang, "cant_check_this").format(html.escape(group_username)) 173 | update.message.reply_text(text=text) 174 | return 175 | 176 | group_id = extract[0][0] 177 | leaderboard = leaderboards.GroupLeaderboard(lang=lang, page=page, group_id=group_id) 178 | result = leaderboard.build_page(group_username=group_username, only_admins=False) 179 | 180 | update.message.reply_text( 181 | text=result[0], 182 | reply_markup=result[1], 183 | parse_mode='MARKDOWN', 184 | disable_notification=True) 185 | 186 | 187 | 188 | @utils.private_only 189 | def vote(bot, update, args): 190 | user_id = update.message.from_user.id 191 | lang = utils.get_db_lang(user_id) 192 | if len(args) != 1: 193 | text = get_lang.get_string(lang, "insert_param_vote") 194 | update.message.reply_text(text, parse_mode="HTML") 195 | return 196 | username = args[0] 197 | if username.startswith("@"): 198 | username = username.replace("@", "") 199 | 200 | query = """ 201 | SELECT s.group_id, s_ref.username, s_ref.title, v.vote, v.vote_date 202 | FROM supergroups_ref AS s_ref 203 | RIGHT JOIN supergroups AS s 204 | ON s_ref.group_id = s.group_id 205 | LEFT OUTER JOIN votes AS v 206 | ON v.group_id = s.group_id 207 | AND v.user_id = %s 208 | WHERE LOWER(s_ref.username) = LOWER(%s) 209 | AND s.bot_inside = TRUE 210 | """ 211 | 212 | extract = database.query_r(query, user_id, username) 213 | 214 | if len(extract) == 0: 215 | # the group does not exist otherwise anything is returned and if None is NULL 216 | text = get_lang.get_string(lang, "cant_vote_this") 217 | update.message.reply_text(text=text) 218 | return 219 | 220 | if len(extract) > 1: 221 | print("error too many") 222 | return 223 | 224 | extract = extract[0] 225 | text = get_lang.get_string(lang, "vote_this_group").format( 226 | extract[0], extract[1], extract[2]) 227 | if extract[3] and extract[4] is not None: 228 | stars = emojis.STAR*extract[3] 229 | date = utils.formatted_date_l(extract[4].date(), lang) 230 | text += "\n\n"+get_lang.get_string(lang, "already_voted").format(stars, date) 231 | 232 | if extract[3] and extract[4] is not None: 233 | reply_markup = keyboards.change_vote_kb(extract[0], lang) 234 | else: 235 | text += "\n\n" 236 | text += get_lang.get_string(lang, "vote_from_one_to_five") 237 | reply_markup = keyboards.vote_group_kb(extract[0], lang) 238 | update.message.reply_text(text=text, reply_markup=reply_markup) 239 | 240 | 241 | def settings_private(bot, update): 242 | lang = utils.get_db_lang(update.message.from_user.id) 243 | reply_markup = keyboards.main_private_settings_kb(lang) 244 | text = get_lang.get_string(lang, "private_settings") 245 | update.message.reply_text(text=text, reply_markup=reply_markup) 246 | 247 | 248 | @utils.admin_command_only() 249 | def settings_group(bot, update): 250 | query_db = "SELECT lang FROM supergroups WHERE group_id = %s" 251 | lang = database.query_r(query_db, update.message.chat.id, one=True)[0] 252 | text = get_lang.get_string(lang, "choose_group_lang") 253 | reply_markup = keyboards.main_group_settings_kb(lang) 254 | update.message.reply_text(text=text, reply_markup=reply_markup, quote=False) 255 | 256 | 257 | @utils.admin_command_only(possible_in_private=True) 258 | def groupleaderboard(bot, update, args): 259 | if update.message.chat.type == "private": 260 | update.message.reply_text("Only in groups") 261 | return 262 | leaderboards.groupleaderboard(bot, update, args) 263 | 264 | 265 | def language(bot, update): 266 | if update.message.chat.type == "private": 267 | language_private(bot, update) 268 | else: 269 | language_group(bot, update) 270 | 271 | 272 | @utils.private_only 273 | def region(bot, update): 274 | query = "SELECT lang, region FROM users WHERE user_id = %s" 275 | extract = database.query_r(query, update.message.from_user.id, one=True) 276 | lang = extract[0] 277 | region = extract[1] 278 | text = get_lang.get_string(lang, "choose_region") 279 | reply_markup = keyboards.private_region_kb(lang, region) 280 | update.message.reply_text(text=text, reply_markup=reply_markup) 281 | 282 | 283 | @utils.creator_command_only 284 | def language_group(bot, update): 285 | messages_supergroups.choose_group_language(bot, update) 286 | 287 | 288 | # this does not need the only private decorator cause the command has the same 289 | # name for groups 290 | def language_private(bot, update): 291 | query = "SELECT lang FROM users WHERE user_id = %s" 292 | extract = database.query_r(query, update.message.from_user.id, one=True) 293 | lang = extract[0] 294 | text = get_lang.get_string(lang, "choose_your_lang") 295 | reply_markup = keyboards.private_language_kb(lang, back=False) 296 | update.message.reply_text(text=text, reply_markup=reply_markup) 297 | 298 | 299 | @utils.private_only 300 | def aboutyou(bot, update): 301 | user_id = update.message.from_user.id 302 | 303 | # query = """ 304 | # WITH tleft AS ( 305 | # SELECT main.user_id, u.lang, main.num_msgs, main.num_grps, main.rnk 306 | # FROM ( 307 | # SELECT 308 | # user_id, 309 | # num_grps, 310 | # num_msgs, 311 | # DENSE_RANK() OVER(ORDER BY num_msgs DESC, num_grps DESC, user_id DESC) rnk 312 | # FROM ( 313 | # SELECT 314 | # user_id, 315 | # COUNT(distinct group_id) AS num_grps, 316 | # COUNT(*) AS num_msgs 317 | # FROM messages 318 | # WHERE message_date > date_trunc('week', now()) 319 | # GROUP BY user_id 320 | # ) AS sub 321 | # ) AS main 322 | # LEFT OUTER JOIN users AS u 323 | # USING (user_id) 324 | # WHERE u.weekly_own_digest = TRUE AND user_id = %s 325 | # AND bot_blocked = FALSE 326 | # ) 327 | # , tright AS ( 328 | # SELECT main.user_id, main.group_id, s_ref.title, s_ref.username, main.m_per_group, main.pos 329 | # FROM ( 330 | # SELECT user_id, group_id, COUNT(user_id) AS m_per_group, 331 | # ROW_NUMBER() OVER ( 332 | # PARTITION BY group_id 333 | # ORDER BY COUNT(group_id) DESC 334 | # ) AS pos 335 | # FROM messages 336 | # WHERE message_date > date_trunc('week', now()) AND user_id = %s 337 | # GROUP BY group_id, user_id 338 | # ) AS main 339 | # LEFT OUTER JOIN supergroups_ref AS s_ref 340 | # USING (group_id) 341 | # ORDER BY m_per_group DESC 342 | # ) 343 | # SELECT l.user_id, l.lang, l.num_msgs, l.num_grps, l.rnk, r.title, r.username, r.m_per_group, r.pos 344 | # FROM tleft AS l 345 | # INNER JOIN tright AS r 346 | # USING (user_id) 347 | # """ 348 | 349 | ####################### 350 | # WARNING!!!! # 351 | ####################### 352 | # No more using the query to the db, but using the scheduled cache. 353 | # by the way the result is returned in the very same form 354 | # so it can be changed anytime 355 | 356 | # FOLLOWING LINES ARE COMMENTED TO NOT EXECUTE THE QUERY 357 | 358 | #extract = database.query_r(query, user_id, user_id) 359 | #extract = cache_users_stats.group_extract(extract)[0] 360 | 361 | 362 | user_cache, latest_update = cache_users_stats.get_cached_user(user_id) 363 | lang = utils.get_db_lang(user_id) 364 | if user_cache is None: 365 | text = get_lang.get_string(lang, "you_inactive_this_week") 366 | 367 | else: 368 | text = get_lang.get_string(lang, "this_week_you_sent_this")+"\n\n" 369 | groups = user_cache[1] 370 | for group in groups: 371 | title = group[0] 372 | username = group[1] 373 | m_per_group = group[2] 374 | pos_per_group = group[3] 375 | text += get_lang.get_string(lang, "messages_in_groups_position").format( 376 | utils.sep_l(m_per_group, lang), 377 | username, 378 | utils.sep_l(pos_per_group, lang) 379 | ) 380 | 381 | # global stats 382 | text += "\n"+get_lang.get_string(lang, "you_globally_this_week").format( 383 | utils.sep_l(user_cache[0][2], lang), 384 | utils.sep_l(user_cache[0][3], lang), 385 | utils.sep_l(user_cache[0][4], lang) 386 | ) 387 | text += "\n\n{}: {}.".format( 388 | utils.get_lang.get_string(lang, "latest_update"), 389 | utils.round_seconds(int(time.time()-latest_update), lang) 390 | ) 391 | utils.send_message_long(bot, chat_id=user_id, text=text) 392 | 393 | 394 | @utils.private_only 395 | def leaderboard(bot, update): 396 | query = "SELECT lang, region FROM users WHERE user_id = %s" 397 | extract = database.query_r(query, update.message.from_user.id, one=True) 398 | lang = extract[0] 399 | region = extract[1] 400 | text = get_lang.get_string(lang, "generic_leaderboard").format(supported_langs.COUNTRY_FLAG[region]) 401 | reply_markup = keyboards.generic_leaderboard_kb(lang, region) 402 | update.message.reply_text(text=text, reply_markup=reply_markup, parse_mode="HTML") 403 | 404 | 405 | @utils.private_only 406 | def help(bot, update): 407 | start_help_buttons(bot, update) 408 | lang = utils.get_db_lang(update.message.from_user.id) 409 | text = get_lang.get_string(lang, "help_message") 410 | reply_markup = keyboards.help_kb(lang) 411 | update.message.reply_text(text=text, parse_mode="HTML", reply_markup=reply_markup) 412 | 413 | 414 | @utils.private_only 415 | def send_groups_working(bot, update): 416 | lang = utils.get_db_lang(update.message.from_user.id) 417 | text = get_lang.get_string(lang, "groups_working") 418 | update.message.reply_text(text=text, parse_mode='HTML') 419 | 420 | 421 | @utils.private_only 422 | def feedback(bot, update): 423 | sender_id = update.message.from_user.id 424 | lang = utils.get_db_lang(sender_id) 425 | text = constants.FEEDBACK_INV_CHAR 426 | text += get_lang.get_string(lang, "feedback_message") 427 | update.message.reply_text(text=text) 428 | 429 | 430 | def start_help_buttons(bot, update): 431 | lang = utils.get_db_lang(update.message.from_user.id) 432 | text = get_lang.get_string(lang, "hello") 433 | reply_markup = keyboards.default_regular_buttons_kb(lang) 434 | update.message.reply_text(text=text, reply_markup=reply_markup) 435 | 436 | 437 | @utils.admin_command_only(possible_in_private=True) 438 | def group_rank(bot, update): 439 | query = "SELECT lang FROM supergroups WHERE group_id = %s" 440 | lang = database.query_r(query, update.message.chat.id, one=True)[0] 441 | update.message.reply_text(text=group_rank_text(update.message.chat.id, lang), parse_mode='HTML', quote=False) 442 | 443 | 444 | def group_rank_text(group_id, lang): 445 | strings = get_lang.get_string(lang, "group_rank") 446 | rank = cache_groups_rank.get_group_cached_rank(group_id) 447 | if rank is None: 448 | return strings['None'] 449 | 450 | text = strings['title'] 451 | # by messages 452 | try: 453 | text += "\n\n" 454 | text += strings['by_messages'].format(rank[cache_groups_rank.BY_MESSAGES][cache_groups_rank.REGION]) 455 | text += "\n" 456 | text += strings['position'].format( 457 | utils.sep_l(rank[cache_groups_rank.BY_MESSAGES][cache_groups_rank.RANK], lang) 458 | ) 459 | text += "\n" 460 | text += strings['messages'].format( 461 | utils.sep_l(rank[cache_groups_rank.BY_MESSAGES][cache_groups_rank.VALUE], lang) 462 | ) 463 | text += "\n" 464 | text += strings['updated'].format( 465 | utils.get_lang.get_string(lang, "latest_update"), 466 | utils.round_seconds(int(time.time() - rank[cache_groups_rank.BY_MESSAGES][cache_groups_rank.CACHED_AT]), lang) 467 | ) 468 | except KeyError: 469 | pass 470 | 471 | # by members 472 | try: 473 | text += "\n\n" 474 | text += strings['by_members'].format(rank[cache_groups_rank.BY_MEMBERS][cache_groups_rank.REGION]) 475 | text += "\n" 476 | text += strings['position'].format( 477 | utils.sep_l(rank[cache_groups_rank.BY_MEMBERS][cache_groups_rank.RANK], lang) 478 | ) 479 | text += "\n" 480 | text += strings['members'].format( 481 | utils.sep_l(rank[cache_groups_rank.BY_MEMBERS][cache_groups_rank.VALUE], lang) 482 | ) 483 | text += "\n" 484 | text += strings['updated'].format( 485 | utils.get_lang.get_string(lang, "latest_update"), 486 | utils.round_seconds(int(time.time() - rank[cache_groups_rank.BY_MEMBERS][cache_groups_rank.CACHED_AT]), lang) 487 | ) 488 | except KeyError: 489 | pass 490 | 491 | # by votes average 492 | try: 493 | text += "\n\n" 494 | text += strings['by_votes'].format(rank[cache_groups_rank.BY_VOTES][cache_groups_rank.REGION]) 495 | text += "\n" 496 | text += strings['position'].format( 497 | utils.sep_l(rank[cache_groups_rank.BY_VOTES][cache_groups_rank.RANK], lang) 498 | ) 499 | text += "\n" 500 | text += strings['votes'].format( 501 | rank[cache_groups_rank.BY_VOTES][cache_groups_rank.VALUE][0], 502 | utils.sep_l(rank[cache_groups_rank.BY_VOTES][cache_groups_rank.VALUE][1], lang) 503 | ) 504 | text += "\n" 505 | text += strings['updated'].format( 506 | utils.get_lang.get_string(lang, "latest_update"), 507 | utils.round_seconds(int(time.time() - rank[cache_groups_rank.BY_VOTES][cache_groups_rank.CACHED_AT]), lang) 508 | ) 509 | except KeyError: 510 | pass 511 | 512 | return text --------------------------------------------------------------------------------