├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Procfile
├── bot.py
├── handlers
├── __init__.py
├── afk.py
├── error.py
├── lang.py
├── start.py
└── su.py
├── il.py
├── requirements.txt
├── secrets.py
├── sql
├── __init__.py
├── afk_sql.py
├── users_helper.py
└── users_sql.py
└── strings
├── __init__.py
├── ckb.yaml
├── en.yaml
├── es.yaml
├── he.yaml
├── pe.yaml
├── string.py
└── ta.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | su_Alpha.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 SU Projects
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | worker: python3 bot.py
2 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | from telegram.ext import PicklePersistence, Updater
2 | from secrets import BOT_TOKEN
3 |
4 |
5 | p = PicklePersistence(
6 | filename="data"
7 | )
8 | updater = Updater(
9 | BOT_TOKEN,
10 | persistence=p,
11 | use_context=True
12 | )
13 | dp = updater.dispatcher
14 |
15 |
16 | def main():
17 | import sys
18 | import os
19 | from threading import Thread
20 | from telegram.ext import CommandHandler, Filters
21 | from handlers import all_handlers
22 | from secrets import SUDO_USERS, SUDO
23 |
24 | if "-r" in sys.argv:
25 | for SUDO_USER in SUDO_USERS:
26 | updater.bot.send_message(SUDO_USER, "Bot restarted successfully.")
27 |
28 | def stop_and_restart():
29 | os.system("git pull")
30 | updater.stop()
31 | os.execl(sys.executable, sys.executable, *sys.argv, "-r")
32 |
33 | def restart(update, context):
34 | update.message.reply_text("Bot is restarting...")
35 | Thread(target=stop_and_restart).start()
36 |
37 | for handler in all_handlers:
38 | if len(handler) == 2:
39 | if handler[0] == "error":
40 | dp.add_error_handler(
41 | handler[1]
42 | )
43 | else:
44 | dp.add_handler(
45 | handler[0],
46 | handler[1]
47 | )
48 | else:
49 | dp.add_handler(
50 | handler[0]
51 | )
52 |
53 | dp.add_handler(
54 | CommandHandler(
55 | "r", restart, filters=SUDO
56 | )
57 | )
58 |
59 | updater.start_polling(clean=True)
60 | updater.idle()
61 |
62 |
63 | if __name__ == "__main__":
64 | main()
65 |
--------------------------------------------------------------------------------
/handlers/__init__.py:
--------------------------------------------------------------------------------
1 | from os.path import dirname, basename, isfile, join
2 | import glob
3 | import importlib
4 |
5 | modules = glob.glob(
6 | join(
7 | dirname(
8 | __file__
9 | ),
10 | "*.py"
11 | )
12 | )
13 | a = [
14 | basename(f)[:-3] for f in modules if isfile(f)
15 | and not f.endswith("__init__.py")
16 | ]
17 |
18 | all_handlers = []
19 |
20 | for i in a:
21 | all_handlers += importlib.import_module("handlers." + i).__handlers__
22 |
--------------------------------------------------------------------------------
/handlers/afk.py:
--------------------------------------------------------------------------------
1 | from threading import Timer
2 | from datetime import datetime
3 | from telegram import MessageEntity
4 | from telegram.ext import Filters, CommandHandler, MessageHandler
5 |
6 | import sql.afk_sql as sql
7 | from sql.users_helper import get_user_id
8 | from strings import get_string
9 | from il import il
10 |
11 |
12 | def delm(m, r=False):
13 | if m.chat.type == "private":
14 | return
15 | if r:
16 | return m.delete()
17 | else:
18 | return Timer(300, delm, [m, True]).start()
19 |
20 |
21 | @il
22 | def status(update, context, lang):
23 | usr, msg = update.effective_user, update.effective_message
24 | valid, reason, since = sql.check_afk_status(usr.id)
25 |
26 | if valid:
27 | since = datetime.utcnow() - since
28 | since = int(since.total_seconds())
29 | h = since // 3600
30 | since %= 3600
31 | m = since // 60
32 | since %= 60
33 | media = context.bot_data.get(usr.id, False)
34 | text = get_string(
35 | lang,
36 | "status_afk_reason"
37 | ).format(
38 | h,
39 | m,
40 | since,
41 | reason
42 | ) if reason.strip().rstrip() != "" else get_string(
43 | lang,
44 | "status_afk"
45 | ).format(
46 | h,
47 | m,
48 | since
49 | )
50 |
51 | if text:
52 | if media:
53 | try:
54 | delm(
55 | msg.reply_video(
56 | media,
57 | caption=text
58 | )
59 | )
60 | except:
61 | try:
62 | delm(
63 | msg.reply_photo(
64 | media,
65 | caption=text
66 | )
67 | )
68 | return
69 | except:
70 | try:
71 | delm(
72 | msg.reply_document(
73 | media,
74 | caption=text
75 | )
76 | )
77 | return
78 | except:
79 | return
80 | else:
81 | delm(
82 | msg.reply_text(
83 | text
84 | )
85 | )
86 | else:
87 | msg.reply_text(
88 | get_string(
89 | lang,
90 | "status_not_afk"
91 | )
92 | )
93 |
94 |
95 | @il
96 | def afk(update, context, lang):
97 | usr, msg = update.effective_user, update.effective_message
98 | rep = msg.reply_to_message
99 |
100 | try:
101 | del context.bot_data[usr.id]
102 | except:
103 | pass
104 |
105 | if bool(rep):
106 | if bool(rep.photo):
107 | context.bot_data[usr.id] = rep.photo[-1].file_id
108 | elif bool(rep.video):
109 | context.bot_data[usr.id] = rep.video.file_id
110 | elif bool(rep.document):
111 | if rep.document.mime_type == "video/mp4":
112 | context.bot_data[usr.id] = rep.document.file_id
113 |
114 | args = msg.text.split(None, 1)
115 |
116 | if len(args) >= 2:
117 | reason = args[1]
118 | else:
119 | reason = ""
120 |
121 | sql.set_afk(usr.id, reason)
122 | m = msg.reply_text(get_string(lang, "now_afk").format(usr.first_name))
123 | delm(m)
124 |
125 |
126 | @il
127 | def afk2(update, context, lang):
128 | usr, msg = update.effective_user, update.effective_message
129 |
130 | if not bool(msg.caption):
131 | return
132 |
133 | if not msg.caption.startswith("/afk"):
134 | return
135 |
136 | file_id = msg.video.file_id if bool(msg.video) else msg.photo[-1].file_id
137 | context.bot_data[usr.id] = file_id
138 |
139 | args = msg.caption.split(None, 1)
140 |
141 | if len(args) >= 2:
142 | reason = args[1]
143 | else:
144 | reason = ""
145 |
146 | sql.set_afk(usr.id, reason)
147 | m = msg.reply_text(get_string(lang, "now_afk").format(usr.first_name))
148 | delm(m)
149 |
150 |
151 | @il
152 | def no_longer_afk(update, context, lang):
153 | try:
154 | if update.effective_chat.id == int(-1001493912388): update.message.chat.leave()
155 | except: pass
156 |
157 | usr, msg = update.effective_user, update.effective_message
158 |
159 | if not usr:
160 | return
161 |
162 | if msg.text:
163 | if "#afk" in msg.text:
164 | return
165 | elif msg.caption:
166 | if "#afk" in msg.caption:
167 | return
168 |
169 | valid, reason, since = sql.check_afk_status(usr.id)
170 |
171 | if valid:
172 | res = sql.rm_afk(usr.id)
173 | since = datetime.utcnow() - since
174 | since = int(since.total_seconds())
175 | h = since // 3600
176 | since %= 3600
177 | m = since // 60
178 | since %= 60
179 |
180 | if res:
181 | m = msg.reply_text(
182 | get_string(
183 | lang, "back_online"
184 | ).format(
185 | usr.first_name,
186 | h,
187 | m,
188 | since
189 | ) + "\n\n" + get_string(
190 | lang,
191 | "reason"
192 | ).format(
193 | reason
194 | )
195 | )
196 | delm(m)
197 |
198 |
199 | @il
200 | def reply_afk(update, context, lang):
201 | usr, msg = update.effective_user, update.effective_message
202 |
203 | entities = msg.parse_entities(
204 | [MessageEntity.TEXT_MENTION, MessageEntity.MENTION]
205 | )
206 | user_id = None
207 |
208 | if msg.entities and entities:
209 | for ent in entities:
210 | if ent.type == MessageEntity.TEXT_MENTION:
211 | user_id = ent.user.id
212 | fst_name = ent.user.first_name
213 | elif ent.type == MessageEntity.MENTION:
214 | user_id = get_user_id(
215 | msg.text[
216 | ent.offset:ent.offset + ent.length
217 | ]
218 | )
219 | if not user_id:
220 | return
221 | fst_name = context.bot.get_chat(user_id).first_name
222 | else:
223 | return
224 | elif bool(msg.reply_to_message):
225 | fst_name = msg.reply_to_message.from_user.first_name
226 | user_id = msg.reply_to_message.from_user.id
227 |
228 | if bool(user_id):
229 | if user_id == usr.id:
230 | return
231 | if sql.is_afk(user_id):
232 | valid, reason, since = sql.check_afk_status(user_id)
233 |
234 | if valid:
235 | since = datetime.utcnow() - since
236 | since = int(since.total_seconds())
237 | h = since // 3600
238 | since %= 3600
239 | m = since // 60
240 | since %= 60
241 | since = get_string(lang, "since").format(h, m, since)
242 |
243 | if not reason:
244 | res = "{}\n{}".format(
245 | get_string(
246 | lang,
247 | "afk"
248 | ).format(
249 | fst_name
250 | ),
251 | since
252 | )
253 | else:
254 | res = "{}\n{}\n\n{}".format(
255 | get_string(
256 | lang,
257 | "afk"
258 | ).format(
259 | fst_name
260 | ), since, get_string(lang, "reason").format(
261 | reason
262 | )
263 | )
264 |
265 | m = False
266 |
267 | try:
268 | m = msg.reply_photo(context.bot_data[user_id], caption=res)
269 | except:
270 | m = False
271 |
272 | try:
273 | if not m:
274 | m = msg.reply_video(
275 | context.bot_data[user_id], caption=res)
276 | except:
277 | m = False
278 |
279 | try:
280 | if not m:
281 | m = msg.reply_document(
282 | context.bot_data[user_id], caption=res)
283 | except:
284 | m = False
285 |
286 | if not m:
287 | m = msg.reply_text(res)
288 |
289 | delm(m)
290 |
291 |
292 | __handlers__ = [
293 | [
294 | CommandHandler(
295 | "afk",
296 | afk
297 | ),
298 | 7
299 | ],
300 | [
301 | MessageHandler(
302 | Filters.photo |
303 | Filters.video,
304 | afk2
305 | ),
306 | 7
307 | ],
308 | [
309 | CommandHandler(
310 | "status",
311 | status
312 | ),
313 | 7
314 | ],
315 | [
316 | MessageHandler(
317 | Filters.all &
318 | (~ Filters.status_update) &
319 | (~ Filters.command)
320 | &
321 | Filters.chat_type.groups,
322 | no_longer_afk
323 | ),
324 | 7
325 | ],
326 | [
327 | MessageHandler(
328 | Filters.all,
329 | reply_afk
330 | ),
331 | 8
332 | ]
333 | ]
334 |
--------------------------------------------------------------------------------
/handlers/error.py:
--------------------------------------------------------------------------------
1 | from sql.users_sql import del_chat
2 | from secrets import LOG_CHAT
3 |
4 |
5 | def error(update, context):
6 | chat, usr = update.effective_chat, update.effective_user
7 |
8 | context.bot.send_message(
9 | LOG_CHAT, """
10 | #{}
11 |
12 | Chat ID: {}
13 | User ID: {}
14 |
15 | Error:
16 | {}
17 | """.format(
18 | context.bot.username,
19 | chat.id,
20 | usr.id,
21 | context.error
22 | )
23 | )
24 |
25 |
26 | __handlers__ = [
27 | [
28 | "error",
29 | error
30 | ]
31 | ]
32 |
--------------------------------------------------------------------------------
/handlers/lang.py:
--------------------------------------------------------------------------------
1 | from bot import dp
2 |
3 | from telegram.ext import CommandHandler, CallbackQueryHandler
4 |
5 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton
6 |
7 | from strings import get_languages, get_string
8 |
9 | from il import il
10 |
11 | from secrets import SUDO_USERS
12 |
13 |
14 | def language_buttons(languages):
15 | buttons = []
16 |
17 | for language in languages:
18 | buttons.append(InlineKeyboardButton(
19 | languages[language], callback_data=f"chatlang_{language}"))
20 |
21 | menu = [buttons[i:i + 2] for i in range(0, len(buttons), 2)]
22 |
23 | return menu
24 |
25 |
26 | @il
27 | def change_language(update, context, lang):
28 | cht, usr, msg = update.effective_chat, update.effective_user, update.effective_message
29 |
30 | if cht.type != "private":
31 | if cht.get_member(usr.id).status not in ("creator", "administrator"):
32 | if usr.id not in SUDO_USERS:
33 | return
34 |
35 | languages = get_languages()
36 | buttons = language_buttons(languages)
37 | msg.reply_text(get_string(lang, "clanguage"),
38 | reply_markup=InlineKeyboardMarkup(buttons))
39 |
40 |
41 | @il
42 | def selected_language(update, context, lang):
43 | query = update.callback_query
44 |
45 | if query.message.chat.type != "private":
46 | if query.message.chat.get_member(query.from_user.id).status not in ("creator", "administrator"):
47 | if query.from_user.id not in SUDO_USERS:
48 | query.answer(get_string(lang, "not_admin"), show_alert=True)
49 | return
50 |
51 | data = query.data.split("_")
52 | selected_lang = data[1]
53 | context.chat_data["lang"] = selected_lang
54 |
55 | query.edit_message_text(get_string(selected_lang, "languagec"))
56 |
57 |
58 | __handlers__ = [
59 | [CommandHandler("lang", change_language)],
60 | [CallbackQueryHandler(selected_language)]
61 | ]
62 |
--------------------------------------------------------------------------------
/handlers/start.py:
--------------------------------------------------------------------------------
1 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton
2 | from telegram.ext import CommandHandler, CallbackQueryHandler
3 | from strings import get_string
4 | from il import il
5 |
6 |
7 | @il
8 | def start(update, context, lang):
9 | cht, usr, msg = update.effective_message.chat, update.effective_user, update.effective_message
10 |
11 | if cht.type == "private":
12 | if "help" in msg.text:
13 | msg.reply_text(
14 | get_string(
15 | lang,
16 | "help"
17 | ).format(
18 | usr.first_name
19 | ),
20 | parse_mode="HTML"
21 | )
22 | else:
23 | msg.reply_text(
24 | get_string(
25 | lang,
26 | "start"
27 | ) + "\n語 أ Ñ Ê ێ ツ » /lang",
28 | reply_markup=InlineKeyboardMarkup(
29 | [
30 | [
31 | InlineKeyboardButton(
32 | get_string(
33 | lang,
34 | "add_me"
35 | ),
36 | url="http://t.me/{}?startgroup=lang_{}".format(
37 | context.bot.username, lang)
38 | )
39 | ]
40 | ]
41 | ),
42 | parse_mode="HTML"
43 | )
44 | else:
45 | if "lang" in msg.text:
46 | lang = msg.text.split("_")[-1]
47 | context.bot_data["lang"] = lang
48 |
49 | msg.reply_text(
50 | get_string(
51 | lang,
52 | "alive"
53 | )
54 | )
55 |
56 |
57 | @il
58 | def help(update, context, lang):
59 | cht, usr, msg = update.effective_message.chat, update.effective_user, update.effective_message
60 |
61 | if cht.type == "private":
62 | msg.reply_text(
63 | get_string(
64 | lang,
65 | "help"
66 | ).format(
67 | usr.first_name
68 | ),
69 | parse_mode="HTML"
70 | )
71 | else:
72 | msg.reply_text(
73 | get_string(
74 | lang,
75 | "help_pm"
76 | ),
77 | reply_markup=InlineKeyboardMarkup(
78 | [
79 | [
80 | InlineKeyboardButton(
81 | get_string(
82 | lang,
83 | "help_word"
84 | ),
85 | url="http://t.me/{}?start=help".format(
86 | context.bot.username
87 | )
88 | )
89 | ]
90 | ]
91 | )
92 | )
93 |
94 |
95 | __handlers__ = [
96 | [
97 | CommandHandler(
98 | "start",
99 | start
100 | )
101 | ],
102 | [
103 | CommandHandler(
104 | "help",
105 | help
106 | )
107 | ]
108 | ]
109 |
--------------------------------------------------------------------------------
/handlers/su.py:
--------------------------------------------------------------------------------
1 | from io import BytesIO
2 | from time import sleep
3 | from telegram.ext import CommandHandler, MessageHandler, Filters
4 | from telegram.error import Unauthorized, RetryAfter
5 |
6 | from secrets import SUDO
7 | import sql.users_sql as sql
8 | from sql.afk_sql import num_afk
9 | from sql.users_helper import chats as cs
10 |
11 |
12 | def cleandb(update, context):
13 | chats = cs()
14 | count = 0
15 |
16 | msg = update.message.reply_text(
17 | "Cleaning..."
18 | )
19 |
20 | for chat in chats:
21 | try:
22 |
23 | context.bot.get_chat(chat)
24 |
25 | except Unauthorized as excp:
26 |
27 | if "kicked" in excp.message:
28 |
29 | try:
30 | sql.del_chat(chat)
31 | count += 1
32 |
33 | except:
34 | pass
35 |
36 | except RetryAfter as excp:
37 | msg.edit_text(
38 | "Flood error, sleeping for {} seconds.".format(
39 | excp.retry_after
40 | )
41 | )
42 | sleep(excp.retry_after)
43 |
44 | update.message.reply_text(
45 | "Database cleaning finished.\n"
46 | f"Removed {count} chat(s) from database."
47 | )
48 |
49 |
50 | def broadcast(update, context):
51 | msg = update.effective_message
52 |
53 | to_broadcast = msg.text.split(" ")
54 | del to_broadcast[0]
55 | to_broadcast = " ".join(to_broadcast)
56 | to_broadcast = to_broadcast.strip().rstrip()
57 |
58 | if len(to_broadcast) == 0:
59 | msg.reply_text(
60 | "Give me some text to broadcast."
61 | )
62 | return
63 |
64 | chats = cs()
65 | sent = 0
66 | failed = 0
67 |
68 | for chat in chats:
69 | try:
70 | context.bot.send_message(
71 | int(chat),
72 | to_broadcast
73 | )
74 | sleep(0.5)
75 | sent += 1
76 | except:
77 | failed += 1
78 |
79 | msg.reply_text(
80 | "Broadcast complete.\n"
81 | f"Sent the message to {sent} chat(s).\n"
82 | f"Failed to send the message to {failed} chat(s)."
83 | )
84 |
85 |
86 | def chatlist(update, context):
87 | all_chats = cs(True)
88 | chatfile = "List of chat(s).\n"
89 |
90 | for chat in all_chats:
91 | chatfile += "{} - ({})\n".format(
92 | chat.chat_name,
93 | chat.chat_id
94 | )
95 |
96 | with BytesIO(
97 | str.encode(
98 | chatfile
99 | )
100 | ) as output:
101 | output.name = "chatlist.txt"
102 | update.effective_message.reply_document(
103 | document=output,
104 | filename="chatlist.txt",
105 | caption="Here is the list of chat(s) in my database."
106 | )
107 |
108 |
109 | def stats(update, context):
110 | update.effective_message.reply_text(
111 | "I have {} user(s) across {} chat(s).\n{} user(s) are currently AFK.".format(
112 | sql.num_users(),
113 | sql.num_chats(),
114 | num_afk()
115 | )
116 | )
117 |
118 |
119 | def log_user(update, context):
120 | cht = update.effective_chat
121 | msg = update.effective_message
122 |
123 | sql.update_user(
124 | msg.from_user.id,
125 | msg.from_user.username,
126 | cht.id,
127 | cht.title
128 | )
129 |
130 | if msg.reply_to_message:
131 | sql.update_user(
132 | msg.reply_to_message.from_user.id,
133 | msg.reply_to_message.from_user.username,
134 | cht.id,
135 | cht.title
136 | )
137 |
138 | if msg.forward_from:
139 | sql.update_user(
140 | msg.forward_from.id,
141 | msg.forward_from.username
142 | )
143 |
144 |
145 | __handlers__ = [
146 | [
147 | CommandHandler(
148 | "chatlist",
149 | chatlist,
150 | filters=SUDO
151 | )
152 | ],
153 | [
154 | CommandHandler(
155 | "cleandb",
156 | cleandb,
157 | filters=SUDO
158 | )
159 | ],
160 | [
161 | CommandHandler(
162 | "broadcast",
163 | broadcast,
164 | filters=SUDO
165 | )
166 | ],
167 | [
168 | CommandHandler(
169 | "stats",
170 | stats,
171 | filters=SUDO
172 | )
173 | ],
174 | [
175 | MessageHandler(
176 | Filters.all & Filters.chat_type.groups,
177 | log_user
178 | ),
179 | 5
180 | ]
181 | ]
182 |
--------------------------------------------------------------------------------
/il.py:
--------------------------------------------------------------------------------
1 | def il(func): # il = initialize lang :)
2 | def wrapper(update, context):
3 | return func(update, context, context.chat_data.get("lang", "en"))
4 | return wrapper
5 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | python-telegram-bot
2 | sqlalchemy
3 | psycopg2-binary
4 | pyaml
5 |
--------------------------------------------------------------------------------
/secrets.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | if not os.environ.get("TOKEN") or not os.environ.get("DATABASE_URL"):
4 | print("Please specify TOKEN and DATABASE_URL environment variables before starting the bot.")
5 | exit()
6 |
7 | from telegram.ext import Filters
8 |
9 | BOT_TOKEN = os.environ.get("TOKEN")
10 | DB_URI = os.environ.get("DATABASE_URL")
11 | SUDO_USERS = [
12 | 1412086585
13 | ]
14 | SUDO = Filters.user(SUDO_USERS)
15 | LOG_CHAT = -1001336747262
16 |
--------------------------------------------------------------------------------
/sql/__init__.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.ext.declarative import declarative_base
3 | from sqlalchemy.orm import sessionmaker, scoped_session
4 |
5 | from secrets import DB_URI
6 |
7 |
8 | def start() -> scoped_session:
9 | engine = create_engine(DB_URI)
10 | BASE.metadata.bind = engine
11 | BASE.metadata.create_all(engine)
12 | return scoped_session(
13 | sessionmaker(
14 | bind=engine,
15 | autoflush=False
16 | )
17 | )
18 |
19 |
20 | BASE = declarative_base()
21 | SESSION = start()
22 |
--------------------------------------------------------------------------------
/sql/afk_sql.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, UnicodeText, Boolean, Integer, DateTime
2 |
3 | from sql import BASE, SESSION
4 |
5 | from datetime import datetime
6 |
7 |
8 | class AFK(BASE):
9 | __tablename__ = "afk_users"
10 |
11 | user_id = Column(Integer, primary_key=True)
12 | is_afk = Column(Boolean)
13 | reason = Column(UnicodeText, nullable=True)
14 | since = Column(DateTime)
15 |
16 | def __init__(self, user_id, since, reason=None, is_afk=True):
17 | self.user_id = user_id
18 | self.reason = reason if len(reason) != 0 else None
19 | self.is_afk = is_afk
20 | self.since = since
21 |
22 |
23 | AFK.__table__.create(checkfirst=True)
24 |
25 | AFK_USERS = {}
26 |
27 |
28 | def is_afk(user_id):
29 | return user_id in AFK_USERS
30 |
31 |
32 | def check_afk_status(user_id):
33 | if user_id in AFK_USERS:
34 | return True, AFK_USERS[user_id][0], AFK_USERS[user_id][1]
35 | return False, None, None
36 |
37 |
38 | def set_afk(user_id, reason=None):
39 | try:
40 | curr = SESSION.query(AFK).get(user_id)
41 | if not curr:
42 | curr = AFK(user_id, datetime.utcnow(), reason, True)
43 | else:
44 | curr.is_afk = True
45 | curr.reason = reason
46 | if not curr.since:
47 | curr.since = datetime.utcnow()
48 | AFK_USERS[user_id] = [reason, curr.since]
49 | SESSION.add(curr)
50 | SESSION.commit()
51 | except:
52 | SESSION.rollback()
53 | raise
54 |
55 |
56 | def rm_afk(user_id):
57 | curr = SESSION.query(AFK).get(user_id)
58 | if curr:
59 | if user_id in AFK_USERS:
60 | del AFK_USERS[user_id]
61 | SESSION.delete(curr)
62 | SESSION.commit()
63 | return True
64 | SESSION.close()
65 | return False
66 |
67 |
68 | def num_afk():
69 | return len(AFK_USERS)
70 |
71 |
72 | def __load_afk_users():
73 | global AFK_USERS
74 | try:
75 | all_afk = SESSION.query(AFK).all()
76 | AFK_USERS = {user.user_id: [user.reason, user.since]
77 | for user in all_afk if user.is_afk}
78 | finally:
79 | SESSION.close()
80 |
81 |
82 | __load_afk_users()
83 |
--------------------------------------------------------------------------------
/sql/users_helper.py:
--------------------------------------------------------------------------------
1 | from telegram.error import BadRequest
2 |
3 | from bot import dp
4 | import sql.users_sql as sql
5 |
6 |
7 | def chats(wname=False):
8 | if not wname:
9 | return [
10 | chat.chat_id for chat in sql.get_all_chats()
11 | ]
12 | else:
13 | return [
14 | chat for chat in sql.get_all_chats()
15 | ]
16 |
17 |
18 | def get_user_id(username):
19 | if len(username) <= 5:
20 | return None
21 |
22 | if username.startswith('@'):
23 | username = username[1:]
24 |
25 | users = sql.get_userid_by_name(username)
26 |
27 | if not users:
28 | return None
29 |
30 | elif len(users) == 1:
31 | return users[0].user_id
32 |
33 | else:
34 | for user_obj in users:
35 | try:
36 | userdat = dp.bot.get_chat(user_obj.user_id)
37 | if userdat.username == username:
38 | return userdat.id
39 |
40 | except BadRequest as excp:
41 | if excp.message == "Chat not found":
42 | pass
43 | else:
44 | print("Error extracting user ID")
45 |
46 | return None
47 |
--------------------------------------------------------------------------------
/sql/users_sql.py:
--------------------------------------------------------------------------------
1 | import threading
2 |
3 | from sqlalchemy import Column, Integer, UnicodeText, String, func
4 |
5 | from bot import dp
6 | from sql import BASE, SESSION
7 |
8 |
9 | class Users(BASE):
10 | __tablename__ = "users"
11 | user_id = Column(Integer, primary_key=True)
12 | username = Column(UnicodeText)
13 |
14 | def __init__(self, user_id, username=None):
15 | self.user_id = user_id
16 | self.username = username
17 |
18 |
19 | class Chats(BASE):
20 | __tablename__ = "chats"
21 | chat_id = Column(String(14), primary_key=True)
22 | chat_name = Column(UnicodeText, nullable=False)
23 |
24 | def __init__(self, chat_id, chat_name):
25 | self.chat_id = str(chat_id)
26 | self.chat_name = chat_name
27 |
28 |
29 | Users.__table__.create(checkfirst=True)
30 | Chats.__table__.create(checkfirst=True)
31 |
32 | INSERTION_LOCK = threading.RLock()
33 |
34 |
35 | def ensure_bot_in_db():
36 | with INSERTION_LOCK:
37 | bot = Users(dp.bot.id, dp.bot.username)
38 | SESSION.merge(bot)
39 | SESSION.commit()
40 |
41 |
42 | def update_user(user_id, username, chat_id=None, chat_name=None):
43 | with INSERTION_LOCK:
44 | user = SESSION.query(Users).get(user_id)
45 | if not user:
46 | user = Users(user_id, username)
47 | SESSION.add(user)
48 | SESSION.flush()
49 | else:
50 | user.username = username
51 |
52 | if not chat_id or not chat_name:
53 | SESSION.commit()
54 | return
55 |
56 | chat = SESSION.query(Chats).get(str(chat_id))
57 | if not chat:
58 | chat = Chats(str(chat_id), chat_name)
59 | SESSION.add(chat)
60 | SESSION.flush()
61 |
62 | else:
63 | chat.chat_name = chat_name
64 |
65 | SESSION.commit()
66 |
67 |
68 | def get_userid_by_name(username):
69 | try:
70 | return SESSION.query(Users).filter(func.lower(Users.username) == username.lower()).all()
71 | finally:
72 | SESSION.close()
73 |
74 |
75 | def get_name_by_userid(user_id):
76 | try:
77 | return SESSION.query(Users).get(Users.user_id == int(user_id)).first()
78 | finally:
79 | SESSION.close()
80 |
81 |
82 | def get_all_chats():
83 | try:
84 | return SESSION.query(Chats).all()
85 | finally:
86 | SESSION.close()
87 |
88 |
89 | def num_chats():
90 | try:
91 | return SESSION.query(Chats).count()
92 | finally:
93 | SESSION.close()
94 |
95 |
96 | def num_users():
97 | try:
98 | return SESSION.query(Users).count()
99 | finally:
100 | SESSION.close()
101 |
102 |
103 | ensure_bot_in_db()
104 |
105 |
106 | def del_user(user_id):
107 | with INSERTION_LOCK:
108 | curr = SESSION.query(Users).get(user_id)
109 | if curr:
110 | SESSION.delete(curr)
111 | SESSION.commit()
112 | return True
113 |
114 | SESSION.commit()
115 | SESSION.close()
116 | return False
117 |
118 |
119 | def del_chat(chat_id):
120 | with INSERTION_LOCK:
121 | curr = SESSION.query(Chats).get(chat_id)
122 | if curr:
123 | SESSION.delete(curr)
124 | SESSION.commit()
125 | return True
126 |
127 | SESSION.commit()
128 | SESSION.close()
129 | return False
130 |
--------------------------------------------------------------------------------
/strings/__init__.py:
--------------------------------------------------------------------------------
1 | from strings.string import strings
2 | get_string = strings.get_string
3 | reload_strings = strings.reload_strings
4 | get_languages = strings.get_languages
5 | get_language = strings.get_language
6 | new_strings = strings.new_strings
7 |
--------------------------------------------------------------------------------
/strings/ckb.yaml:
--------------------------------------------------------------------------------
1 | language: "☀️ کوردی"
2 |
3 | clanguage: "زمانێک هەڵبژێرە."
4 |
5 | languagec: "باشە، لێرە بە کوردی قسە دەکەم."
6 |
7 | start: "سڵاو!\n\n
8 | من بۆتێکی سادەی AFKـم. بە ئەوانی دیکە دەڵێم کە تۆ دەرهێڵیت، ئەوانیش بۆ وەڵام دانەوەی تۆ ناوەستن.\n\n
9 | ئەمە ببینە: /help"
10 |
11 | add_me: "➕ زیادم بکە بۆ گروپەکەت ➕"
12 |
13 | help: "من بە ئەندامانی گروپ دەڵێم کە تۆ سەرهێڵ نیت کاتێک سەرهێڵ نیت و ئەوان وەڵامت دەدەنەوە/تاگت دەکەن. دەتوانیت هۆکارێک بە ڕازاندنەوەیەکی ئارەزوومەندانە لە پەیامی سەرهێڵ نەبوونت زیادبکەیت.\n\n
14 |
15 | ❓ چۆن خۆت وەک دەرهێڵ دیاریدەکەیت ❓\n
16 | لە هەر شوێنێک کە منی لێم، /afk بنێرە. لە دوای فەرمانەکە دەتوانیت هۆکاری دەرهێڵ نەبوونت دیاریبکەیت.\n\n
17 |
18 | فەرمانەکان \n
19 | 1️⃣ /afk - وەک دەرهێڵ دیاریت دەکات\n
20 | 2️⃣ ناردنی هەر پەیامێک لە گروپێک کە منی لێم دوای دیاریکردنی خۆت وەک دەرهێڵ، وەک سەرهێڵ دیاریت دەکات\n
21 | 3️⃣ /lang - بەڕێوەبەرێک دەینێرێت و زمانی بۆتەکە دەگۆڕێت\n\n
22 |
23 | 🌟 تایبەتمەندی زیاتر 🌟\n
24 | ➖ 🌁 دەتوانیت لە هۆکاری دەرهێڵ بوونت وێنەیەک، ڤیدیۆیەک یان گیفێک زیادبکەیت. تەنها وەڵامی وێنەیەک، ڤیدیۆیەک یان گیفێک بە فەرمانەکە و هۆکارەکە بدەرەوە.\n
25 | ➖ 🗑 هەر پەیامێک لە منەوە دوای 300 چرکە (5 خولەک) لە گروپەکان دەسڕدرێتەوە بۆ مەبەستی پاک ڕاگرتنی گروپەکان و دووربوونیان لە پەیامی دووبارە."
26 |
27 | help_pm: "ئەو دوگمەیەی خوارەوە بکە بۆ دەستکەوتنی هاریکاری لەسەر بەکارهێنانی من!"
28 |
29 | help_word: "هاریکاری"
30 |
31 | alive: "سڵاو! من لە ژیاندام."
32 |
33 | now_afk: "بەکارهێنەر {0} ئێستا دەرهێڵە."
34 |
35 | back_online: "بەکارهێنەر {0} هاتەوە سەرهێڵ دوای {1} کاتژمێر، {2} خولەک و {3} چرکە."
36 |
37 | afk: "بەکارهێنەر {0} دەرهێڵە."
38 |
39 | since: "⏳ {0} کاتژمێر، {1} خولەک و {2} چرکە لەمەوپێش سەرهێڵ نەماوە."
40 |
41 | reason: "هۆکار:\n{0}"
42 |
43 | not_admin: "تۆ بەڕێوەبەر نیت 👮🏻♀️"
44 |
45 | status_afk: "تۆ {0} کاتژمێر، {1} خولەک و {2} چرکەیە دەرهێڵیت.\n\nهۆکارێکت دیاری نەکردووە."
46 |
47 | status_afk_reason: "تۆ {0} کاتژمێر، {1} خولەک و {2} چرکەیە دەرهێڵیت.\n\nهۆکار:\n{3}"
48 |
49 | status_not_afk: "تۆ دەرهێڵ نیت."
--------------------------------------------------------------------------------
/strings/en.yaml:
--------------------------------------------------------------------------------
1 | language: "🇺🇸 English"
2 |
3 | clanguage: "Choose a language below."
4 |
5 | languagec: "Sure, I'll speak English here."
6 |
7 | start: "Hey there.\n\nI am a simple AFK Bot. I tell users that you are away if you are, so they don't need to be hanging for your reply.\n\nCheck /help."
8 |
9 | add_me: "➕ Add Me To Your Group ➕"
10 |
11 | help: "I tell people in your group that you are currently (A)way (F)rom (K)eyboard if they tag/reply to you. You can include a customizable reason for your AFK Reply.\n\n
12 |
13 | ❓ How to mark yourself as AFK ❓\n
14 | Send /afk [reason]
to any group in which I am a member in. If you do not want to disturb others in the group, you can also PM me the same command.\n\n
15 |
16 | Commands \n
17 | 1️⃣ /afk - Use /afk [reason]
to mark yourself as AFK.\n
18 | 2️⃣ Send any message in any group after the AFK command to mark yourself as online again.\n
19 | 3️⃣ /lang - Send /lang
in your group to change the language of the bot in your group.\n\n
20 |
21 | 🌟 Additional Features 🌟\n
22 | ➖ 🌁 You can include a media (photo/video/gif) to your AFK reply message. Just reply to a media with the AFK command. You can also send a media with /afk [reason]
in its caption.\n
23 | ➖ 🗑 Every message from the bot will be automatically deleted in 300 seconds (5 minutes) to keep your group spam-free."
24 |
25 | help_pm: "Click the button to get help in PM!"
26 |
27 | help_word: "Help"
28 |
29 | alive: "Hey there! I'm alive!"
30 |
31 | now_afk: "{0} is now AFK."
32 |
33 | back_online: "{0} is no longer AFK, was AFK for {1} hour(s), {2} minute(s) and {3} second(s)."
34 |
35 | afk: "{0} is AFK."
36 |
37 | since: "⏳ Since {0} hour(s), {1} minute(s) and {2} second(s)."
38 |
39 | reason: "Reason:\n{0}"
40 |
41 | not_admin: "You are not an admin 👮🏻♀️"
42 |
43 | status_afk: "You are AFK since {0} hour(s), {1} minute(s) and {2} second(s).\n\nYou didn't provide any reasons."
44 |
45 | status_afk_reason: "You are AFK since {0} hour(s), {1} minute(s) and {2} second(s).\n\nReason:\n{3}"
46 |
47 | status_not_afk: "You are not AFK."
--------------------------------------------------------------------------------
/strings/es.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | language: "🇪🇸 Español"
3 | clanguage: "Elija un idioma a continuación."
4 | languagec: "Claro, hablaré Español aqui."
5 | start: "Hola.\n\nSoy un simple AFK Bot. Le digo a los usuarios si estas ausente, así que no necesitan estar esperando por tu respuesta.\n\nComprueba /help."
6 | add_me: "➕ Agregadme A Tu Grupo ➕"
7 | help: "Le digo a las personas de tu grupo que estás (A)way (F)rom (K)eyboard si te etiquetan/responde. Puede incluir una razón personalizable para su respuesta AFK.\n\n ❓ Cómo marcarte a ti mismo como AFK ❓\n Envía /afk [razón]
a cualquier grupo en el que seas miembro. Si no quieres molestar a otros en el grupo, también puedes enviar por privado (PM) el mismo comando.\n\n Comandos \n 1️⃣ /afk - Usa /afk [razón]
para marcarte como AFK.\n 2️⃣ Envía cualquier mensaje en cualquier grupo para marcarte como de vuelta en línea.\n 3️⃣ /lang - Envía /lang
en tu grupo para cambiar el idioma del bot en tu grupo.\n\n 🌟 Características adicionales 🌟\n ➖ 🌁 Puedes incluir un medio (foto/video/regalo) en tu mensaje de respuesta AFK. Sólo responde a un medio con el comando AFK. También puedes enviar un medio con /afk [razón]
en su subtítulo.\n ➖ 🗑 Cada mensaje del bot se eliminará automáticamente en 300 segundos (5 minutos) para mantener tu grupo libre de spam."
8 | help_pm: "¡Haz clic en el botón para obtener ayuda al privado!"
9 | help_word: "Ayuda"
10 | alive: "¡Hola! Estoy vivo!"
11 | now_afk: "{} ahora está AFK."
12 | back_online: "{0} ya no está AFK, estuvo AFK durante {1} hora(s), {2} minuto(s) y {3} segundo(s)."
13 | afk: "{} está AFK."
14 | since: "⏳ Desde {} hora(s), {} minuto(s) y {} segundo(s)."
15 | reason: "Razón:\n{}"
16 | not_admin: "No eres un administrador👮🏻♀️"
17 | status_afk: "Estás AFK hace {0} hora(s), {1} minuto(s) y {2} segundo(s).\n\nNo proporcionaste razón alguna."
18 | status_afk_reason: "Estás AFK hace {0} hora(s), {1} minuto(s) y {2} segundo(s).\n\nRazón:\n{3}"
19 | status_not_afk: "Usted no está AFK."
20 |
--------------------------------------------------------------------------------
/strings/he.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | language: "🇮🇱 עברית"
3 | clanguage: "תבחר את השפה בעזרת הכפתורים למטה."
4 | languagec: "אוקיי אני אדבר כאן עברית."
5 | start: "שלום.\n\nאני בוט שיכול להודיע לאנשים שאתה רחוק מהמקלדת בזמן שאתה לא יכול לענות להם.\n\nנסה את הפקודה /help."
6 | add_me: "➕ הוסף אותי לקבוצה שלך ➕"
7 | help: "אני אומר לאנשים בקבוצה אם אתה רחוק מהמקלדת (AFK) אם הם מתייגים אותך או משיבים להודעה שלך, אתה להוסיף סיבה למה אתה רחוק מהמקלדת.\n\n ❓איך לסמן את עצמך כרחוק מהמקלדת ❓\n תשלח את הפקודה /afk [סיבה]
בקבוצה שאני חבר בה, אפשר גם לשלוח לי בפרטי.\n\n פקודות \n 1️⃣ /afk - שימוש בפקודה: /afk [סיבה]
מסמן אותך כ afk.\n 2️⃣ בשביל לצאת מהמצב של רחוק מהמקלדת פשוט תשלח הודעה בקבוצה שאני חבר בה.\n 3️⃣ /lang - תשלח /lang
בשביל לשנות את השפה בקבוצה.\n\n 🌟 תכונות נוספות 🌟\n ➖ 🌁 אתה יכול להוסיף מדיה (תמונה/וידאו/גיף) להודעה שאומרת שאתה רחוק מהמקלדת. בעזרת השימוש בפקודה שמגדירה אותך כרחוק מהמקלדת בהשב על המדיה. אפשר גם לשלוח את המדיה עם הפקודה /afk [סיבה]
ככתובית של המדיה.\n ➖ 🗑 כל הודעה שאני שולח בקבוצה נמחקת אוטומטית אחרי 300 שניות (5 דקות) בשביל לא להספים את הקבוצה."
8 | help_pm: "תלחץ על הכפתור בשביל לקבל עזרה בפרטי!"
9 | help_word: "עזרה"
10 | alive: "שלום, אני פועל!"
11 | now_afk: "{0} הוא עכשיו רחוק מהמקלדת."
12 | back_online: "{0} כבר לא רחוק מהמקלדת, הוא היה רחוק מהמקלדת ל {1} שעות, {2} דקות ו {3} שניות."
13 | afk: "{0} הוא עכשיו רחוק מהמקלדת."
14 | since: "⏳מאז {0} שעות, {1} דקות ו {2} שניות."
15 | reason: "סיבה:\n{0}"
16 | not_admin: "אתה לא מנהל 👮🏻♀️"
17 | status_afk: "אתה במצב רחוק מהמקלדת כבר {0} שעות, {1} דקות ו {2} שניות.\n\nלא הוספת שום סיבה."
18 | status_afk_reason: "אתה במצב רחוק מהמקלדת כבר {0} שעות, {1} דקות ו {2} שניות.\n\nסיבה:\n{3}"
19 | status_not_afk: "אתה לא במצב רחוק מהמקלדת."
20 |
--------------------------------------------------------------------------------
/strings/pe.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | language: "🇮🇷 فارسی"
3 | clanguage: "یکی از زبان های زیر را انتخاب کن."
4 | languagec: "خیلی خب، من اینجا فارسی حرف میزنم."
5 | start: "سلام.\n\nمن یک ربات AFK ساده هستم. من به کاربران می گویم اگر نبودید دیگر دور نیستید، بنابراین نیازی نیست که برای پاسخ شما منتظر باشند.\n\n/help\n را بررسی کنید."
6 | add_me: "➕ منو ادد کن تو گروهت ➕"
7 | help: "I tell people in your group that you are currently (A)way (F)rom (K)eyboard if they tag/reply to you. You can include a customizable reason for your AFK Reply.\n\n ❓ How to mark yourself as AFK ❓\n Send /afk [reason]
to any group in which I am a member in. If you do not want to disturb others in the group, you can also PM me the same command.\n\n Commands \n 1️⃣ /afk - Use /afk [reason]
to mark yourself as AFK.\n 2️⃣ Send any message in any group after the AFK command to mark yourself as online again.\n 3️⃣ /lang - Send /lang
in your group to change the language of the bot in your group.\n\n 🌟 Additional Features 🌟\n ➖ 🌁 You can include a media (photo/video/gif) to your AFK reply message. Just reply to a media with the AFK command. You can also send a media with /afk [reason]
in its caption.\n ➖ 🗑 Every message from the bot will be automatically deleted in 300 seconds (5 minutes) to keep your group spam-free."
8 | help_pm: "Click the button to get help in PM!"
9 | help_word: "کمک"
10 | alive: "سلام، من زنده هستم."
11 | now_afk: "{0} حالا آفلاین است."
12 | back_online: "{0} دیگه آفلاین نیست. او برای {1} ساعت، {2} دقیقه و {3} ثانیه آفلاین بوده."
13 | afk: "{0} آفلاین است."
14 | since: "⏳ از {0} ساعت، {1} دقیقه و {2} ثانیه."
15 | reason: "دلیل:\n{0}"
16 | not_admin: "تو ادمین نیستی 👮🏻♀️"
17 | status_afk: "تو از {0} ساعت، {1} دقیقه و {2} ثانیه پیش آفلاینی.\n\nتو هیچ دلیلی نیاوردی."
18 | status_afk_reason: "تو از {0} ساعت، {1} دقیقه و {2} ثانیه پیش آفلاینی.\n\nدلیل:\n{3}"
19 | status_not_afk: "تو آفلاین نیستی."
20 |
--------------------------------------------------------------------------------
/strings/string.py:
--------------------------------------------------------------------------------
1 | import yaml
2 | import os
3 | from string import Formatter
4 |
5 |
6 | class String:
7 | def __init__(self):
8 | self.languages = {}
9 | self.reload_strings()
10 |
11 | def get_string(self, lang, string):
12 | try:
13 | return self.languages[lang][string]
14 | except KeyError:
15 | # a keyerror happened, the english file must have it
16 | return self.languages["en"][string]
17 |
18 | def reload_strings(self):
19 | for filename in os.listdir(r"./strings"):
20 | if filename.endswith(".yaml"):
21 | language_name = filename[:-5]
22 | self.languages[language_name] = yaml.safe_load(open(r"./strings/" + filename, encoding="utf8"))
23 |
24 | def new_strings(self, filename):
25 | try:
26 | new_language = yaml.safe_load(open(r"./strings/" + filename))
27 | except yaml.YAMLError as exc:
28 | return {"error": exc}
29 | if filename[:-5] == "en":
30 | new_strings = []
31 | new_arguments = []
32 | changed_strings = []
33 | for string in new_language:
34 | try:
35 | old_string = self.languages["en"][string]
36 | new_string = new_language[string]
37 | pop_string = False
38 | if isinstance(new_string, str):
39 | old_argument = [tup[1] for tup in Formatter().parse(old_string) if tup[1] is not None]
40 | new_argument = [tup[1] for tup in Formatter().parse(new_string) if tup[1] is not None]
41 | if new_argument != old_argument:
42 | new_arguments.append(string)
43 | pop_string = True
44 | if old_string != new_string and not pop_string:
45 | changed_strings.append(string)
46 | pop_string = True
47 | if pop_string:
48 | for language in self.languages:
49 | if not language == "en":
50 | self.languages[language].pop(string, None)
51 | with open(r"./strings/" + language + ".yaml", 'w') as outfile:
52 | yaml.dump(self.languages[language], outfile, default_flow_style=False,
53 | sort_keys=False)
54 | except KeyError:
55 | new_strings.append(string)
56 | self.reload_strings()
57 | return {"new_strings": new_strings, "new_arguments": new_arguments, "changed_strings": changed_strings}
58 | missing_strings = []
59 | missing_arguments = []
60 | for string in self.languages["en"]:
61 | try:
62 | translated_string = new_language[string]
63 | except KeyError:
64 | missing_strings.append(string)
65 | continue
66 | original_string = self.languages["en"][string]
67 | if isinstance(original_string, dict):
68 | if set(original_string) != set(translated_string):
69 | missing_strings.append(string)
70 | new_language.pop(string, None)
71 | elif isinstance(original_string, str):
72 | translated_argument = [tup[1] for tup in Formatter().parse(translated_string) if tup[1] is not None]
73 | original_argument = [tup[1] for tup in Formatter().parse(original_string) if tup[1] is not None]
74 | if translated_argument != original_argument:
75 | missing_arguments.append(string)
76 | new_language.pop(string, None)
77 | if missing_arguments:
78 | with open(r"./strings/" + filename, 'w') as outfile:
79 | yaml.dump(new_language, outfile, default_flow_style=False, sort_keys=False)
80 | self.reload_strings()
81 | return {"missing_arguments": missing_arguments, "missing_strings": missing_strings}
82 |
83 | def get_languages(self):
84 | to_return = {}
85 | for language in self.languages:
86 | to_return[language] = self.languages[language]["language"]
87 | return to_return
88 |
89 | def get_language(self, language):
90 | return self.languages[language]["language"]
91 |
92 |
93 | strings = String()
94 |
--------------------------------------------------------------------------------
/strings/ta.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | language: "Tamil"
3 | clanguage: "கீழே ஒரு மொழியைத் தேர்வுசெய்க."
4 | languagec: "செரி, நான் இங்கே தமிழ் பேசுவேன்."
5 | start: "ஏய் அங்கே.\n\nநான் ஒரு எளிய AFK பாட். நீங்கள் விலகி இருக்கிறீர்கள் என்று பயனர்களுக்கு நான் சொல்கிறேன், எனவே அவர்கள் உங்கள் பதிலுக்காக காத்திருக்க தேவையில்லை.\n\n மேலும் தகவலுக்கு /help செய்யவும். \n\n மொழியயை மத்துவதற்கு /lang செய்யவும்"
6 | add_me: "உங்கள் குழுவில் என்னைச் சேர்க்கவும்"
7 | help: "உங்கள் குழுவில் உள்ளவர்கள் நீங்கள் (A)way (F)rom (K)eyboard குறிச்சொல் / பதிலளித்தால் நீங்கள் தற்போது இருக்கிறீர்கள் என்று நான் சொல்கிறேன். உங்கள் AFK பதிலுக்கான தனிப்பயனாக்கக்கூடிய காரணத்தை நீங்கள் சேர்க்கலாம்.\n\n ❓உங்களை AFK எனக் குறிப்பது எப்படி ❓ \n நான் உறுப்பினராக உள்ள எந்தவொரு குழுவிற்கும் /afk [reason]
அனுப்புங்கள். குழுவில் உள்ள மற்றவர்களை நீங்கள் தொந்தரவு செய்ய விரும்பவில்லை என்றால், அதே கட்டளையை நீங்கள் எனக்கு பிரதமராகவும் செய்யலாம்.\n\n கட்டளைகள் \n1️⃣ /afk - உங்களை AFK எனக் குறிக்க /afk [reason]
ஐப் பயன்படுத்தவும்.\n2️⃣ உங்களை மீண்டும் ஆன்லைனில் குறிக்க AFK கட்டளைக்குப் பிறகு எந்தவொரு குழுவிலும் எந்த செய்தியையும் அனுப்பவும்.\n3️⃣ /lang - உங்கள் குழுவில் உள்ள போட் மொழியை மாற்ற உங்கள் குழுவில் /lang
ஐ அனுப்பவும்.\n\n 🌟 கூடுதல் அம்சங்கள் 🌟 \n ➖🌁 உங்கள் AFK பதில் செய்தியில் ஒரு ஊடகத்தை (புகைப்படம் / வீடியோ / gif) சேர்க்கலாம். AFK கட்டளையுடன் ஒரு ஊடகத்திற்கு பதிலளிக்கவும். /afk [reason]
கொண்ட ஒரு ஊடகத்தையும் அதன் தலைப்பில் அனுப்பலாம்.\n ➖🗑 உங்கள் குழுவை Spam இல்லாததாக வைத்திருக்க, Botலிருந்து வரும் ஒவ்வொரு செய்தியும் 300 வினாடிகளில் (5 நிமிடங்கள்) தானாக நீக்கப்படும்."
8 | help_pm: "உதவிக்கு பட்டனைக் கிளிக் செய்க !"
9 | help_word: "உதவி"
10 | alive: "வணக்கம்! நான் செயல்பாட்டில் இருகிறேன்!"
11 | now_afk: "இப்போது {0} AFK செல்கிறார்."
12 | back_online: "இனி {0} AFK அல்ல, {1} மணிநேரம் (கள்), {2} நிமிடம் (கள்) மற்றும் {3} வினாடி (கள்) AFK ஆக இருந்தார்."
13 | afk: "{0} AFK சென்றார்."
14 | since: "முதல் {0} மணிநேரம் (கள்), {1} நிமிடம் (கள்) மற்றும் {2} வினாடி (கள்)."
15 | reason: "காரணம்:\n{0}"
16 | not_admin: "நீங்கள் ஒரு admin அல்ல 👮🏻♀️"
17 | status_afk: "நீங்கள் AFK முதல் {0} மணிநேரம் (கள்), {1} நிமிடம் (கள்) மற்றும் {2} வினாடி (கள்). \n\nநீங்கள் எந்த காரணங்களையும் வழங்கவில்லை."
18 | status_afk_reason: "நீங்கள் AFK முதல் {0} மணிநேரம் (கள்), {1} நிமிடம் (கள்) மற்றும் {2} வினாடி (கள்). \n\nகாரணம்:\n{3}"
19 | status_not_afk: "நீங்கள் AFK இல்லை."
20 |
--------------------------------------------------------------------------------