From 20b7e69a79e39fb3e6225bacbc4e79e4517a259e Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sun, 24 Jan 2021 16:39:54 +0300 Subject: [PATCH 001/242] seden: Minor improvements * Added listgmute command * Added listgban command * hash module moved to misc module * birakmamseni command moved to misc module * Some changes Signed-off-by: NaytSeyd --- sedenbot/__init__.py | 2 +- sedenbot/modules/afk.py | 8 +-- sedenbot/modules/ban.py | 7 --- sedenbot/modules/birakmamseni.py | 43 --------------- sedenbot/modules/effects.py | 6 ++- sedenbot/modules/gban.py | 15 +++++- sedenbot/modules/gmute.py | 15 +++++- sedenbot/modules/hash.py | 76 -------------------------- sedenbot/modules/misc.py | 92 ++++++++++++++++++++++++++++++-- sedenbot/modules/spammer.py | 2 +- sedenecem/sql/gban_sql.py | 9 ++++ sedenecem/sql/gmute_sql.py | 9 ++++ sedenecem/translator/en.json | 10 ++-- sedenecem/translator/tr.json | 8 +-- 14 files changed, 153 insertions(+), 149 deletions(-) delete mode 100644 sedenbot/modules/birakmamseni.py delete mode 100644 sedenbot/modules/hash.py diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index b681c51..75a5c6c 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -121,7 +121,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.4.2 Beta' +BOT_VERSION = '1.4.3 Beta' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index cea588d..89bc15d 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -17,20 +17,20 @@ edit, reply, get_translation) # ========================= CONSTANTS ============================ - AFKSTR = [get_translation(f'afkstr{i+1}') for i in range(0, 22)] -# ================================================================= - TEMP_SETTINGS['AFK_USERS'] = {} TEMP_SETTINGS['IS_AFK'] = False TEMP_SETTINGS['COUNT_MSG'] = 0 +# ================================================================= @sedenify(incoming=True, outgoing=False, disable_edited=True, private=False, bot=False, disable_notify=True) def mention_afk(mention): me = mel[0] - if mention.mentioned or mention.reply_to_message and mention.reply_to_message.from_user and mention.reply_to_message.from_user.id == me.id: + if (mention.mentioned or mention.reply_to_message) and ( + mention.reply_to_message.from_user) and ( + mention.reply_to_message.from_user.id == me.id): if TEMP_SETTINGS['IS_AFK']: if mention.from_user.id not in TEMP_SETTINGS['AFK_USERS']: if 'AFK_REASON' in TEMP_SETTINGS: diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 50e87b4..e948342 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -32,7 +32,6 @@ def ban_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -82,7 +81,6 @@ def unban_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -120,7 +118,6 @@ def kick_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -170,7 +167,6 @@ def mute_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -221,7 +217,6 @@ def unmute_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -256,7 +251,6 @@ def promote_user(client, message): try: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id rank = args except Exception: return edit(message, f'`{get_translation("banFailUser")}`') @@ -316,7 +310,6 @@ def demote_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return diff --git a/sedenbot/modules/birakmamseni.py b/sedenbot/modules/birakmamseni.py deleted file mode 100644 index d3a1a75..0000000 --- a/sedenbot/modules/birakmamseni.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from requests import post -from sedenbot import HELP -from sedenecem.core import edit, sedenify, get_translation - - -@sedenify(pattern='^.b[ıi]rakmamseni$') -def birakmamseni(message): - '''Copyright (c) @Adem68 | 2020''' - url = 'https://birakmamseni.org/' - path = 'api/counter' - - headers = { - 'User-Agent': 'ajax/7.66.0', - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, br', - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Origin': '{}'.format(url), - 'X-Requested-With': 'XMLHttpRequest', - } - - try: - response = post(url=url + path, headers=headers) - count = response.json()['counter'].lstrip('0') - except BaseException: - edit(message, f'`{get_translation("covidError")}`') - return - - sonuc = get_translation('birakmamseniResult', ['**', '`', count]) - - edit(message, sonuc, preview=False) - - -HELP.update({'birakmamseni': get_translation('birakmamseniInfo')}) diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 9f220b8..adb5aaf 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -69,8 +69,9 @@ def earrape(message): return -@sedenify(pattern='^.nightcore') +@sedenify(pattern='^.nightcore$') def nightcore(message): + # Copyright (c) @kisekinopureya | 2021 reply = message.reply_to_message nightcore = 'nightcore' if path.isfile(nightcore): @@ -96,8 +97,9 @@ def nightcore(message): message.delete() -@sedenify(pattern='^.slowedtoperfection') +@sedenify(pattern='^.slowedtoperfection$') def slowedtoperfection(message): + # Copyright (c) @kisekinopureya | 2021 reply = message.reply_to_message slowedtoperfection = 'slowedtoperfection' if path.isfile(slowedtoperfection): diff --git a/sedenbot/modules/gban.py b/sedenbot/modules/gban.py index 3002a77..be00ec5 100644 --- a/sedenbot/modules/gban.py +++ b/sedenbot/modules/gban.py @@ -29,7 +29,6 @@ def gban_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -80,7 +79,6 @@ def ungban_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -107,6 +105,19 @@ def ungban_user(client, message): return +@sedenify(pattern='^.listgban$') +def gbanlist(message): + users = sql.gbanned_users() + if not users: + return edit(message, f'`{get_translation("listEmpty")}`') + gban_list = f'**{get_translation("gbannedUsers")}**\n' + count = 0 + for i in users: + count += 1 + gban_list += f'**{count} -** `{i.sender}`\n' + return edit(message, gban_list) + + @sedenify(incoming=True, outgoing=False, compat=False) def gban_check(client, message): gbanned = sql.is_gbanned(message.from_user.id) diff --git a/sedenbot/modules/gmute.py b/sedenbot/modules/gmute.py index ecc9c82..d149853 100644 --- a/sedenbot/modules/gmute.py +++ b/sedenbot/modules/gmute.py @@ -31,7 +31,6 @@ def gmute_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -78,7 +77,6 @@ def ungmute_user(client, message): elif reply: user_id = reply.from_user.id user = client.get_users(user_id) - user_id = user.id else: edit(message, f'`{get_translation("banFailUser")}`') return @@ -103,6 +101,19 @@ def ungmute_user(client, message): return +@sedenify(pattern='^.listgmute$') +def gmutelist(message): + users = sql.gmuted_users() + if not users: + return edit(message, f'`{get_translation("listEmpty")}`') + gmute_list = f'**{get_translation("gmutedUsers")}**\n' + count = 0 + for i in users: + count += 1 + gmute_list += f'**{count} -** `{i.sender}`\n' + return edit(message, gmute_list) + + @sedenify(incoming=True, outgoing=False, compat=False) def gmute_check(client, message): gmuted = sql.is_gmuted(message.from_user.id) diff --git a/sedenbot/modules/hash.py b/sedenbot/modules/hash.py deleted file mode 100644 index 3eea36e..0000000 --- a/sedenbot/modules/hash.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from subprocess import PIPE -from subprocess import run as runapp -from pybase64 import b64encode, b64decode - -from sedenbot import HELP -from sedenecem.core import (edit, reply_doc, extract_args, - sedenify, get_translation) - - -@sedenify(pattern='^.hash') -def hash(message): - hashtxt_ = extract_args(message) - if len(hashtxt_) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - hashtxt = open('hash.txt', 'w+') - hashtxt.write(hashtxt_) - hashtxt.close() - md5 = runapp(['md5sum', 'hash.txt'], stdout=PIPE) - md5 = md5.stdout.decode() - sha1 = runapp(['sha1sum', 'hash.txt'], stdout=PIPE) - sha1 = sha1.stdout.decode() - sha256 = runapp(['sha256sum', 'hash.txt'], stdout=PIPE) - sha256 = sha256.stdout.decode() - sha512 = runapp(['sha512sum', 'hash.txt'], stdout=PIPE) - runapp(['rm', 'hash.txt'], stdout=PIPE) - sha512 = sha512.stdout.decode() - - def rem_filename(st): - return st[:st.find(' ')] - - ans = (f'Text: `{hashtxt_}`' - f'\nMD5: `{rem_filename(md5)}`' - f'\nSHA1: `{rem_filename(sha1)}`' - f'\nSHA256: `{rem_filename(sha256)}`' - f'\nSHA512: `{rem_filename(sha512)}`') - if len(ans) > 4096: - hashfile = open('hash.txt', 'w+') - hashfile.write(ans) - hashfile.close() - reply_doc(message, - 'hash.txt', - caption=f'`{get_translation("outputTooLarge")}`') - runapp(['rm', 'hash.txt'], stdout=PIPE) - message.delete() - else: - edit(message, ans) - - -@sedenify(pattern='^.base64') -def base64(message): - argv = extract_args(message) - args = argv.split(' ', 1) - if len(args) < 2 or args[0] not in ['en', 'de']: - edit(message, f'`{get_translation("wrongCommand")}`') - return - args[1] = args[1].replace('`', '') - if args[0] == 'en': - lething = str(b64encode(bytes(args[1], 'utf-8')))[2:] - edit(message, f'Input: `{args[1]}`\nEncoded: `{lething[:-1]}`') - else: - lething = str(b64decode(bytes(args[1], 'utf-8')))[2:] - edit(message, f'Input: `{args[1]}`\nDecoded: `{lething[:-1]}`') - - -HELP.update({'base64': get_translation('base64Info')}) -HELP.update({'hash': get_translation('hashInfo')}) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index d2ef1ba..621434b 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -8,10 +8,14 @@ # from random import choice +from requests import post +from subprocess import PIPE +from subprocess import run as runapp +from pybase64 import b64encode, b64decode from sedenbot import HELP, SUPPORT_GROUP -from sedenecem.core import (edit, reply, extract_args, - sedenify, get_translation) +from sedenecem.core import (edit, reply, reply_doc, sedenify, + extract_args, get_translation) @sedenify(pattern='^.random') @@ -94,7 +98,7 @@ def repo(message): @sedenify(pattern='^.repeat') def repeat(message): - '''Copyright (c) Gegham Zakaryan | 2019''' + # Copyright (c) Gegham Zakaryan | 2019 args = extract_args(message).split(' ', 1) if len(args) < 2: edit(message, f'`{get_translation("wrongCommand")}`') @@ -143,4 +147,86 @@ def report_admin(client, message): message.delete() +@sedenify(pattern='^.hash') +def hash(message): + hashtxt_ = extract_args(message) + if len(hashtxt_) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + hashtxt = open('hash.txt', 'w+') + hashtxt.write(hashtxt_) + hashtxt.close() + md5 = runapp(['md5sum', 'hash.txt'], stdout=PIPE) + md5 = md5.stdout.decode() + sha1 = runapp(['sha1sum', 'hash.txt'], stdout=PIPE) + sha1 = sha1.stdout.decode() + sha256 = runapp(['sha256sum', 'hash.txt'], stdout=PIPE) + sha256 = sha256.stdout.decode() + sha512 = runapp(['sha512sum', 'hash.txt'], stdout=PIPE) + runapp(['rm', 'hash.txt'], stdout=PIPE) + sha512 = sha512.stdout.decode() + + def rem_filename(st): + return st[:st.find(' ')] + + ans = (f'Text: `{hashtxt_}`' + f'\nMD5: `{rem_filename(md5)}`' + f'\nSHA1: `{rem_filename(sha1)}`' + f'\nSHA256: `{rem_filename(sha256)}`' + f'\nSHA512: `{rem_filename(sha512)}`') + if len(ans) > 4096: + hashfile = open('hash.txt', 'w+') + hashfile.write(ans) + hashfile.close() + reply_doc(message, + 'hash.txt', + caption=f'`{get_translation("outputTooLarge")}`') + runapp(['rm', 'hash.txt'], stdout=PIPE) + message.delete() + else: + edit(message, ans) + + +@sedenify(pattern='^.base64') +def base64(message): + argv = extract_args(message) + args = argv.split(' ', 1) + if len(args) < 2 or args[0] not in ['en', 'de']: + edit(message, f'`{get_translation("wrongCommand")}`') + return + args[1] = args[1].replace('`', '') + if args[0] == 'en': + lething = str(b64encode(bytes(args[1], 'utf-8')))[2:] + edit(message, f'Input: `{args[1]}`\nEncoded: `{lething[:-1]}`') + else: + lething = str(b64decode(bytes(args[1], 'utf-8')))[2:] + edit(message, f'Input: `{args[1]}`\nDecoded: `{lething[:-1]}`') + + +@sedenify(pattern='^.b[ıi]rakmamseni$') +def birakmamseni(message): + '''Copyright (c) @Adem68 | 2020''' + url = 'https://birakmamseni.org/' + path = 'api/counter' + + headers = { + 'User-Agent': 'ajax/7.66.0', + 'Accept': '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Origin': f'{url}', + 'X-Requested-With': 'XMLHttpRequest'} + + try: + response = post(url=url + path, headers=headers) + count = response.json()['counter'].lstrip('0') + except Exception as e: + return edit(message, get_translation('banError', ['`', '**', e])) + + sonuc = get_translation('birakmamseniResult', ['**', '`', count]) + + edit(message, sonuc, preview=False) + + HELP.update({'misc': get_translation('miscInfo')}) diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index dd0d7f4..52f184d 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -86,7 +86,7 @@ def picspam(message): @sedenify(pattern='^.delayspam') def delayspam(message): - """Copyright (c) @ReversedPosix | 2020""" + # Copyright (c) @ReversedPosix | 2020 delayspam = extract_args(message) arr = delayspam.split() if len(arr) < 3 or not arr[0].isdigit() or not arr[1].isdigit(): diff --git a/sedenecem/sql/gban_sql.py b/sedenecem/sql/gban_sql.py index 3194d13..7a497fd 100644 --- a/sedenecem/sql/gban_sql.py +++ b/sedenecem/sql/gban_sql.py @@ -27,6 +27,15 @@ def is_gbanned(sender): SESSION.close() +def gbanned_users(): + try: + return SESSION.query(GBan).all() + except BaseException: + return None + finally: + SESSION.close() + + def gban(sender): adder = GBan(str(sender)) SESSION.add(adder) diff --git a/sedenecem/sql/gmute_sql.py b/sedenecem/sql/gmute_sql.py index 4f766f6..21eb3e5 100644 --- a/sedenecem/sql/gmute_sql.py +++ b/sedenecem/sql/gmute_sql.py @@ -27,6 +27,15 @@ def is_gmuted(sender): SESSION.close() +def gmuted_users(): + try: + return SESSION.query(GMute).all() + except BaseException: + return None + finally: + SESSION.close() + + def gmute(sender): adder = GMute(str(sender)) SESSION.add(adder) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index a6ca8b5..8856631 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -60,9 +60,7 @@ "banResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4banned!%4", "barcodeInfo": ".barcode \nUsage: Make a barcode from provided content.\nEg: .barcode https://devotag.com\n\nNote: Use .decode command to get decoded content.", "barcodeUsage": "%1Usage:%1 %2.barcode %2", - "base64Info": ".base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello", "bioSuccess": "Successfully changed Bio.", - "birakmamseniInfo": ".birakmemseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign", "birakmamseniResult": "%1\u26ab\u26aa Birakmam Seni Data \u26ab\u26aa%1\n\nAs of now, as part of %1BIRAKMAM SENI%1 campaign %2%3%2 \ud83d\udda4\ud83e\udd0d pieces of support were provided.\nCome to, support our %1BE\u015eIKTA\u015e%1 \ud83e\udd85 !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSupports coming through SMS, Money Order / Eft and Postal Check channels are added to meter periodically.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1has been blacklisted for this chat.%1", "blacklistChats": "Blacklists in the Current Chat:", @@ -185,6 +183,7 @@ "founderResult": "%1=======================================\n\nThis bot;\nDeveloped by%1 %2[NaytSeyd](https://t.me/NightShade)%2 %1and%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2\n%1In addition\nLovingly edited by%1 %2[Sedenogen](https://t.me/CiyanogenOneTeams)%2\n\n%1=======================================%1", "gbanLog": "%1#GBAN\nUSER: [%2](tg://user?id=%3)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4globally banned!%4", + "gbannedUsers": "GBanned Users:", "geniusToken": "Please set the Genius token. Thank you!", "gitAccount": "Account Type", "gitBio": "Bio", @@ -211,12 +210,12 @@ "gitWebsite": "Website", "gmuteLog": "%1#GMUTE\nUSER: [%2](tg://user?id=%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4", + "gmutedUsers": "GMuted Users:", "googleDesc": "No description found.", "googleInfo": ".google \nUsage: Does a search on Google.", "googleLog": "Google Search query %1 was executed successfully", "googleResult": "%1Search Query:%1\n%2%3%2\n\n%1Results:%1\n%4", "groupUsage": "This command can only be used on groups.", - "hashInfo": ".hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.", "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", @@ -245,6 +244,7 @@ "lastfmProcess": "[%1](%2) %3is now listening to:%3\n\n\u2022 [%4](%5)\n%6%7%6", "lastfmProcess2": "[%1](%2) %3was last listening to:%3\n\n", "lfyResult": "Here you are, help yourself.", + "listEmpty": "The list is empty!", "loadedModules": "Modules to be loaded: %1", "loadedModules2": "Loaded module: %1", "loadedModulesError": "An error occurred while loading %1", @@ -290,7 +290,7 @@ "mediaInvalid": "Invalid media.", "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f \nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy \nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat \nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random ... \nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat \nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random ... \nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.birakmamseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "%1#MUTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", "muteProcess": "Gets a tape!", @@ -608,4 +608,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} \ No newline at end of file +} diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index c119c99..76da0ca 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -62,7 +62,6 @@ "barcodeUsage": "%1Kullan\u0131m:%1 %2.barcode %2", "base64Info": ".base64\nKullan\u0131m: Verilen dizenin base64 kodlamas\u0131n\u0131 bulun. \u00d6rnek .base64 en merhaba", "bioSuccess": "Biyografi ba\u015far\u0131yla de\u011fi\u015ftirildi.", - "birakmamseniInfo": ".birakmemseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.", "birakmamseniResult": "%1\u26ab\u26aa B\u0131rakmam Seni Kampanyas\u0131 Verileri \u26ab\u26aa%1\n\n\u015eu an itibar\u0131yla %1BIRAKMAM SEN\u0130%1 kampanyas\u0131 kapsam\u0131nda %2%3%2 \ud83d\udda4\ud83e\udd0d adet destekte bulunuldu.\nHaydi sen de hemen %1B\u00dcY\u00dcK BE\u015e\u0130KTA\u015e\u2019IMIZA%1 \ud83e\udd85 destek ol !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSMS, Havale/Eft ve Posta \u00c7eki kanallar\u0131 ile gelen destekler periyodik olarak sayaca eklenmektedir.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1bu sohbet i\u00e7in kara listeye al\u0131nd\u0131.%1", "blacklistChats": "Bu grup i\u00e7in ayarlanan karaliste:", @@ -185,6 +184,7 @@ "founderResult": "%1=======================================\n\nBu bot;%1\n%2[NaytSeyd](https://t.me/NightShade)%2 %1ve%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2 %1taraf\u0131ndan geli\u015ftirilmektedir.\nEk olarak%1\n%2[Sedenogen](https://t.me/CiyanogenOneTeams)%2 %1taraf\u0131ndan sevgi ile d\u00fczenlenmi\u015ftir.\n\n=======================================%1", "gbanLog": "%1#GBAN\nKullan\u0131c\u0131: [%2](tg://user?id=%3)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", + "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", "gitAccount": "Kullan\u0131c\u0131 tipi", "gitBio": "Biyografi", @@ -211,6 +211,7 @@ "gitWebsite": "Website", "gmuteLog": "%1#GMUTE\nKullan\u0131c\u0131: [%2](tg://user?id=%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4", + "gmutedUsers": "K\u00fcresel susturulan kullan\u0131c\u0131lar:", "googleDesc": "A\u00e7\u0131klama bulunamad\u0131.", "googleInfo": ".google \nKullan\u0131m: H\u0131zl\u0131 bir Google aramas\u0131 yapar.", "googleLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla Google'da arat\u0131ld\u0131!", @@ -245,6 +246,7 @@ "lastfmProcess": "[%1](%2) %3\u015fuan \u015funu dinliyor:%3\n\n\u2022 [%4](%5)\n%6%7%6", "lastfmProcess2": "[%1](%2) %3en son \u015funu dinledi:%3\n\n", "lfyResult": "\u0130\u015fte, keyfine bak.", + "listEmpty": "Liste bo\u015f!", "loadedModules": "Y\u00fcklenecek mod\u00fcller: %1", "loadedModules2": "Y\u00fcklenen mod\u00fcl: %1", "loadedModulesError": "%1 mod\u00fcl\u00fc y\u00fcklenirken bir hata olu\u015ftu.", @@ -290,7 +292,7 @@ "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f \nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy \nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", - "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat \nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random ... \nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.", + "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat \nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random ... \nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.birakmamseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.", "mockUsage": "bANa bIr mETin vEr!", "muteLog": "%1#MUTE\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", "muteProcess": "Sessize al\u0131n\u0131yor...", @@ -606,4 +608,4 @@ "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} \ No newline at end of file +} From 728f7c15864c18177d76fda42af1185494a60691 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Mon, 25 Jan 2021 19:00:09 +0300 Subject: [PATCH 002/242] seden: afk: Fix Attribute error Signed-off-by: NaytSeyd --- sedenbot/modules/afk.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 89bc15d..ee713d9 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -26,43 +26,43 @@ @sedenify(incoming=True, outgoing=False, disable_edited=True, private=False, bot=False, disable_notify=True) -def mention_afk(mention): +def mention_afk(msg): me = mel[0] - if (mention.mentioned or mention.reply_to_message) and ( - mention.reply_to_message.from_user) and ( - mention.reply_to_message.from_user.id == me.id): + mentioned = msg.mentioned + rep_m = msg.reply_to_message + if mentioned or rep_m and rep_m.from_user and rep_m.from_user.id == me.id: if TEMP_SETTINGS['IS_AFK']: - if mention.from_user.id not in TEMP_SETTINGS['AFK_USERS']: + if msg.from_user.id not in TEMP_SETTINGS['AFK_USERS']: if 'AFK_REASON' in TEMP_SETTINGS: reply( - mention, get_translation( + msg, get_translation( "afkMessage2", [ '**', me.first_name, me.id, '`', TEMP_SETTINGS['AFK_REASON']])) else: - reply(mention, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS['AFK_USERS'].update({mention.from_user.id: 1}) + reply(msg, f"```{choice(AFKSTR)}```") + TEMP_SETTINGS['AFK_USERS'].update({msg.from_user.id: 1}) TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: if TEMP_SETTINGS['AFK_USERS'][ - mention.from_user.id] % randint( + msg.from_user.id] % randint( 2, 4) == 0: if 'AFK_REASON' in TEMP_SETTINGS: reply( - mention, get_translation( + msg, get_translation( "afkMessage2", [ '**', me.first_name, me.id, '`', TEMP_SETTINGS['AFK_REASON']])) else: - reply(mention, f"```{choice(AFKSTR)}```") + reply(msg, f"```{choice(AFKSTR)}```") TEMP_SETTINGS['AFK_USERS'][ - mention.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - mention.from_user.id] + 1 + msg.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ + msg.from_user.id] + 1 TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: TEMP_SETTINGS['AFK_USERS'][ - mention.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - mention.from_user.id] + 1 + msg.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ + msg.from_user.id] + 1 TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 raise ContinuePropagation From 39fafb104da4b8ef92e07094574a24de6b882aa2 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Thu, 28 Jan 2021 17:56:37 +0300 Subject: [PATCH 003/242] seden: Fix mistakes on speedtest and getsticker command Signed-off-by: NaytSeyd --- sedenbot/modules/speedtest.py | 2 +- sedenbot/modules/stickers.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index 33aaf30..d0a5f8c 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -54,7 +54,7 @@ def speed_test(message): reply_doc( message, speedtest_image, caption=get_translation( 'speedtestResultDoc', [ - '**', ms]), delete_after_send=True, delete_orig=True) + '**', ms]), delete_orig=True) except Exception as exc: edit(message, get_translation('speedtestResultText', diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index d6e4f91..a6bc267 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -107,8 +107,9 @@ def add_exist(conv, pack, pname, pnick): if limit in status.text: pack += 1 - pname = f'a{myacc.id}_by_{myacc.username}_{pack}' - pnick = f"{kanger}'s UserBot pack {pack}" + pname = PACKNAME.replace( + ' ', '') if PACKNAME else f'a{myacc.id}_by_{myacc.username}_{pack}' + pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" edit(message, get_translation('packFull', ['`', '**', str(pack)])) return add_exist(conv, pack, pname, pnick) @@ -153,7 +154,7 @@ def getsticker(message): photo = download_media_wc(reply) reply_doc( - reply, + message, photo, caption=f'**Sticker ID:** `{reply.sticker.file_id}' f'`\n**Emoji**: `{reply.sticker.emoji}`', From 299f7c58203e3a225161a950570deaeb11c397ef Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sun, 14 Feb 2021 21:27:16 +0300 Subject: [PATCH 004/242] seden: scrapers: Fix DuckDuckGo Parser Signed-off-by: NaytSeyd --- sedenbot/modules/scrapers.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 3947432..f3efb55 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -253,8 +253,11 @@ def ddgo(message): res1 = soup.findAll('table', {'border': 0}) res1 = res1[-1].findAll('tr') - edit(message, get_translation('sedenQuery', [ - '**', '`', query, do_ddsearch(query, res1)]), preview=False) + match = do_ddsearch(query, res1) + edit( + message, get_translation( + 'googleResult', [ + '**', '`', query, match]), preview=False) send_log(get_translation('ddgoLog', [query])) @@ -287,8 +290,8 @@ def splitter(res): ltxt = link.text.replace('|', '-').replace('...', '').strip() desc = (item[1].text.strip() if len(item) > 1 - else f'{get_translation("ddgoDesc")}') - out += (f'{i+1}-[{ltxt}]({link["href"]})\n{desc}\n\n') + else get_translation('ddgoDesc')) + out += (f'{i+1} - [{ltxt}]({link["href"]})\n{desc}\n\n') return out @@ -499,7 +502,7 @@ def currency(message): HELP.update({'img': get_translation('imgInfo')}) HELP.update({'currency': get_translation('currencyInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) -HELP.update({'google': get_translation('googleInfo')}) +HELP.update({'goolag': get_translation('googleInfo')}) HELP.update({'duckduckgo': get_translation('ddgoInfo')}) HELP.update({'wiki': get_translation('wikiInfo')}) HELP.update({'ud': get_translation('udInfo')}) From 2c9fd50d5c9c7fad2d2d1fe613e6462f9110a52c Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Wed, 17 Feb 2021 22:08:37 +0300 Subject: [PATCH 005/242] seden: Shorted app.json Signed-off-by: NaytSeyd --- app.json | 49 ++++--------------------------------------------- 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/app.json b/app.json index 693d5ab..c34dd8a 100644 --- a/app.json +++ b/app.json @@ -28,7 +28,7 @@ "required": true }, "BOT_PREFIX": { - "description": "It changes your bot pattern (default pattern is '.'(dot).", + "description": "It changes your bot pattern (default pattern is '.' (dot).", "required": false }, "CHROME_DRIVER": { @@ -36,39 +36,14 @@ "required": false, "value": "/usr/bin/chromedriver" }, - "DOWNLOAD_DIRECTORY": { - "description": "Download location for many modules (.download etc..)", - "required": false, - "value": "./downloads/" - }, - "GENIUS_TOKEN": { - "description": "Get this value from https://genius.com/developers.", - "required": false - }, "HEROKU_APPNAME": { "description": "Add the Heroku app name here. Required for updates.", "required": true }, "HEROKU_KEY": { - "description": "Your Heroku API key, get it from 'https://dashboard.heroku.com/account. Required for updates.", + "description": "Heroku API key, get it from 'https://dashboard.heroku.com/account. Required for updates.", "required": true }, - "LASTFM_API": { - "description": "Get this value from https://www.last.fm/api/account/create.", - "required": false - }, - "LASTFM_PASSWORD": { - "description": "Last.FM Password.", - "required": false - }, - "LASTFM_SECRET": { - "description": "Secret Key. Get this value from https://www.last.fm/api/account/create.", - "required": false - }, - "LASTFM_USERNAME": { - "description": "Last.FM Username.", - "required": false - }, "LOG_ID": { "description": "Chat ID of the Log group. If value is 0, log feature not work. If value is left blank, logs send in Saved Messages", "required": false @@ -78,14 +53,6 @@ "required": false, "value": "False" }, - "LYDIA_APIKEY": { - "description": "Get this value from https://coffeehouse.intellivoid.info/dashboard.", - "required": false - }, - "OCR_APIKEY": { - "description": "OCR API Key for .ocr command. Get from https://ocr.space/ocrapi", - "required": false - }, "PM_AUTO_BAN": { "description": "PM Auto Ban Feature. Also known as 'bleep blop, this is a bot...'.", "required": false, @@ -100,10 +67,6 @@ "description": "Custom message for PM Auto Ban feature. Also known as Pmpermit Module.", "required": false }, - "RBG_APIKEY": { - "description": "RBG API Key for .rbg command. Get from https://www.remove.bg/api", - "required": false - }, "REPO_URL": { "description": "It helps your bot updates, if you maintain a fork repo you can add your repo URL here.", "required": true, @@ -115,12 +78,8 @@ "value": "en" }, "SESSION": { - "description": "Get this value by running python3 session.py in terminal.", + "description": "Get this value by running python3 session.py in terminal or bash script.", "required": true - }, - "WEATHER": { - "description": "Set the default city for Seden UserBot's Weather Module.", - "required": false } }, "formation": { @@ -144,4 +103,4 @@ "stack": "container", "success_url": "https://telegram.dog/SedenUserBot", "website": "https://teamderuntergang.github.io/" -} \ No newline at end of file +} From 0e0f17744a2f357c7f947e49dc1953beb07384a1 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 20 Feb 2021 16:05:34 +0300 Subject: [PATCH 006/242] seden: session: Fix bot token Signed-off-by: NaytSeyd --- session.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/session.py b/session.py index b005d48..8de29ac 100644 --- a/session.py +++ b/session.py @@ -46,8 +46,12 @@ \n**API_HASH:** `{API_HASH}` \n**SESSION:** `{session}` \n**NOTE: Don't give your account information to others!**''' - app.send_message('me', out) - print('''Session successfully generated! + out2 = 'Session successfully generated!' + if self.is_bot: + print(f'{session}\n{out2}') + else: + app.send_message('me', out) + print('''Session successfully generated! Please check your Telegram Saved Messages''') @@ -86,8 +90,12 @@ \n**API_HASH:** `{API_HASH}` \n**SESSION:** `{session}` \n**NOT: Hesap bilgileriniz başkalarına vermeyin!**''' - app.send_message('me', out) - print('''Session başarıyla oluşturuldu! + out2 = 'Session başarıyla oluşturuldu!' + if self.is_bot: + print(f'{session}\n{out2}') + else: + app.send_message('me', out) + print('''Session başarıyla oluşturuldu! Lütfen Telegram Kayıtlı Mesajlarınızı kontrol edin.''') From c15c3f4c8e41a97c567e59877546aed5424fbf52 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Tue, 23 Feb 2021 17:10:12 +0300 Subject: [PATCH 007/242] seden: weather: Fix weather error Signed-off-by: NaytSeyd --- sedenbot/modules/weather.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index c5a1e7b..cdf3131 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -46,9 +46,8 @@ def havadurumu(message): raise Exception data = data.replace('`', '‛') edit(message, f'`{data}`') - except Exception as e: + except Exception: edit(message, f'`{get_translation("weatherErrorServer")}`') - raise e HELP.update({'weather': get_translation('infoWeather')}) From 4880395c9569eb3d6042989aa7f8b959da33f8a0 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Thu, 25 Feb 2021 15:18:47 +0300 Subject: [PATCH 008/242] seden: Added online command Signed-off-by: NaytSeyd --- sedenbot/__init__.py | 3 +-- sedenbot/modules/profile.py | 32 +++++++++++++++++++++++++++++++- sedenecem/translator/en.json | 8 ++++++-- sedenecem/translator/tr.json | 6 +++++- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 75a5c6c..51c7abc 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -206,8 +206,7 @@ def set_logger(): 'HEROKU_APPNAME', 'SESSION', 'API_ID', - 'API_HASH', - 'DATABASE_URL'] + 'API_HASH'] def load_brain(): diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 62ec521..3a63e7a 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -8,11 +8,12 @@ # from os import remove +from time import sleep from pyrogram.errors import UsernameOccupied from pyrogram.raw.functions import channels, account -from sedenbot import HELP +from sedenbot import HELP, TEMP_SETTINGS from sedenecem.core import (edit, extract_args, sedenify, send_log, get_translation, download_media_wc) # ====================== CONSTANT =============================== @@ -25,6 +26,7 @@ USERNAME_SUCCESS = get_translation('usernameSuccess') USERNAME_TAKEN = get_translation('usernameTaken') +ALWAYS_ONLINE = 'offline' # =============================================================== @@ -155,4 +157,32 @@ def unblockpm(client, message): edit(message, f'`{get_translation("pmUnblockedUsage")}`') +@sedenify(pattern='^.online', compat=False) +def online(client, message): + args = extract_args(message) + offline = ALWAYS_ONLINE in TEMP_SETTINGS + if args == 'disable': + if offline: + del TEMP_SETTINGS[ALWAYS_ONLINE] + edit(message, f'`{get_translation("alwaysOnlineOff")}`') + return + else: + edit(message, f'`{get_translation("alwaysOnlineOff2")}`') + return + elif offline: + edit(message, f'`{get_translation("alwaysOnline2")}`') + return + + TEMP_SETTINGS[ALWAYS_ONLINE] = True + + edit(message, f'`{get_translation("alwaysOnline")}`') + + while ALWAYS_ONLINE in TEMP_SETTINGS: + try: + client.send(account.UpdateStatus(offline=False)) + sleep(5) + except BaseException: + return + + HELP.update({'profile': get_translation('profileInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 8856631..bdde6fb 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -37,6 +37,10 @@ "alreadyMuted": "Error!\nUser already muted!", "alreadyUnbanned": "Hata!\nUser already unbanned", "alreadyUnmuted": "Hata!\nUser already unmuted!", + "alwaysOnline": "Always online mode enabled!", + "alwaysOnline2": "Always online mode is already enabled!", + "alwaysOnlineOff": "Always online mode disabled!", + "alwaysOnlineOff2": "Always online mode is already disabled!", "androidInfo": ".magisk\nGet latest Magisk releases\n\n.device \nUsage: Get info about android device codename or model.\n\n.codename \nUsage: Search for android device codename.\n\n.specs \nUsage: Get device specifications info.\n\n.twrp \nUsage: Get latest twrp download for android device.\n\n.orangefox \nUsage: Get latest OrangeFox download for android device.\n\n.phh \nGet latest Phh releases", "androidPhhHeader": "%1Latest Phh %2AOSP Releases%1", "answerFromBot": "I didn't get an answer from bot!", @@ -364,7 +368,7 @@ "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", - "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.", + "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online \n\nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", "promoteLog": "%1#PROMOTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", "promoteProcess": "Promoting...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4promoted!%4", @@ -608,4 +612,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 76da0ca..5a809b8 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -37,6 +37,10 @@ "alreadyMuted": "Hata!\nKullan\u0131c\u0131 zaten susturuldu!", "alreadyUnbanned": "Hata!\nKullan\u0131c\u0131 daha \u00f6nceden yasaklanmam\u0131\u015f!", "alreadyUnmuted": "Hata!\nKullan\u0131c\u0131 daha \u00f6nceden susturulmam\u0131\u015f!", + "alwaysOnline": "S\u00fcrekli \u00e7evrimi\u00e7i modu etkinle\u015ftirildi!", + "alwaysOnline2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten etkinle\u015ftirildi!", + "alwaysOnlineOff": "S\u00fcrekli \u00e7evrimi\u00e7i modu devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", + "alwaysOnlineOff2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", "androidInfo": ".magisk\nG\u00fcncel Magisk s\u00fcr\u00fcmleri\n\n.device \nKullan\u0131m: Android cihaz\u0131 hakk\u0131nda bilgi\n\n.codename \nKullan\u0131m: Android cihaz kod adlar\u0131n\u0131 aray\u0131n.\n\nspecs \nKullan\u0131m: Cihaz \u00f6zellikleri hakk\u0131nda bilgi al\u0131n.\n\n.twrp \nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel TWRP s\u00fcr\u00fcmlerini al\u0131n.\n\n.orangefox \nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel OrangeFox Recovery s\u00fcr\u00fcmlerini al\u0131n.\n\n.phh \nG\u00fcncel Phh AOSP s\u00fcr\u00fcmlerini al\u0131n.", "androidPhhHeader": "%1G\u00fcncel PHH %2AOSP S\u00fcr\u00fcmleri%1", "answerFromBot": "Botdan cevap alamad\u0131m!", @@ -365,7 +369,7 @@ "privacySettings": "Gizlilik ayarlar\u0131 bunu yapmama engel oldu.", "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", "processing": "\u0130\u015fleniyor...", - "profileInfo": ".username \nKullan\u0131m: Telegram'daki kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 de\u011fi\u015fir.\n\n.name veya .name \nKullan\u0131m: Telegram'daki isminizi de\u011fi\u015fir. (Ad ve soyad ilk bo\u015flu\u011fa dayanarak birle\u015ftirilir.)\n\n.setbio \nKullan\u0131m: Telegram'daki biyografinizi bu komutu kullanarak de\u011fi\u015ftirir.\n\n.reserved\nKullan\u0131m: Rezerve etti\u011finiz kullan\u0131c\u0131 adlar\u0131n\u0131 g\u00f6sterir.\n\n.block\nKullan\u0131m: Bir kullan\u0131c\u0131y\u0131 engeller.\n\n.unblock\nKullan\u0131m: Engellenmi\u015f kullan\u0131c\u0131n\u0131n engelini kald\u0131r\u0131r.", + "profileInfo": ".username \nKullan\u0131m: Telegram'daki kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 de\u011fi\u015fir.\n\n.name veya .name \nKullan\u0131m: Telegram'daki isminizi de\u011fi\u015fir. (Ad ve soyad ilk bo\u015flu\u011fa dayanarak birle\u015ftirilir.)\n\n.setbio \nKullan\u0131m: Telegram'daki biyografinizi bu komutu kullanarak de\u011fi\u015ftirir.\n\n.reserved\nKullan\u0131m: Rezerve etti\u011finiz kullan\u0131c\u0131 adlar\u0131n\u0131 g\u00f6sterir.\n\n.block\nKullan\u0131m: Bir kullan\u0131c\u0131y\u0131 engeller.\n\n.unblock\nKullan\u0131m: Engellenmi\u015f kullan\u0131c\u0131n\u0131n engelini kald\u0131r\u0131r.\n\n.online \nKullan\u0131m: Hesab\u0131n\u0131z\u0131, siz Telegram'da \u00e7evrimi\u00e7i olmasan\u0131z bile \u00e7evrimi\u00e7i g\u00f6sterir.\nA\u00e7mak i\u00e7in .online, kapatmak i\u00e7in .online disable yaz\u0131n.\n\nNOT: Etkili olmas\u0131 i\u00e7in son g\u00f6r\u00fclmenizi herkese a\u00e7\u0131k yap\u0131n.", "promoteLog": "%1#PROMOTE\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", "promoteProcess": "Yetkilendiriliyor...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici!%4", From 6d896ef5e10b4aad296d463c3a04679b1a987a0a Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Thu, 25 Feb 2021 18:43:42 +0300 Subject: [PATCH 009/242] seden: Typo fix Signed-off-by: NaytSeyd --- sedenecem/translator/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index bdde6fb..9884c52 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -368,7 +368,7 @@ "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", - "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online \n\nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", + "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online \nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", "promoteLog": "%1#PROMOTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", "promoteProcess": "Promoting...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4promoted!%4", @@ -612,4 +612,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} \ No newline at end of file +} From 96e28237025faa0b448cd31fc54aa0367becec72 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 27 Feb 2021 11:45:25 +0300 Subject: [PATCH 010/242] seden: Update Translations Signed-off-by: NaytSeyd --- sedenecem/translator/en.json | 6 +++--- sedenecem/translator/tr.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 9884c52..cd5985e 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -161,7 +161,7 @@ "effectsInfo": ".earrape mp3 or mp4\nUsage: Applies earrape effect to your video or audio.\n\n.nightcore\nUsage: Applies nightcore effect to your audio\n\n.slowedtoperfection\nUsage: Applies slowed to perfection to your audio", "envCopySuccess": "%2Variable%2 %1%3%1 %2successfully copied to%2 %1%4%1", "envGetValue": "%2Variable:%2 %1%3%1\n%2Value:%2 %1%4%1", - "envInfo": ".env (set, get, rem, copy, move) \nUsage: It allows you to easily change variables of bot.", + "envInfo": ".env get \nUsage: Shows the value given to specified variable.\n\n.env set \nUsage: Sets the value given to specified variable.\n\n.env rem \nUsage: Deletes the specified variable.\n\n.env copy \nUsage: Copies the variable value to be copied into the given variable.\n\n.env move \nUsage: Moves the variable value to be moved to target variable.\n\n.env list\nUsage: Gives the list of available variables.", "envListKeys": "%1Available Variables:%1\n%3", "envMoveSuccess": "%2Variable%2 %1%3%1 %2successfully moved to%2 %1%4%1", "envNotFound": "%2Variable%2 %1%3%1 %2not found%2", @@ -368,7 +368,7 @@ "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", - "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online \nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", + "profileInfo": ".username \nUsage: Changes your Telegram username.\n\n.name or .name \nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio \nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online \n\nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", "promoteLog": "%1#PROMOTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", "promoteProcess": "Promoting...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4promoted!%4", @@ -612,4 +612,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 5a809b8..bc58d2a 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -162,7 +162,7 @@ "effectsInfo": ".earrape mp3 veya mp4\nKullan\u0131m: Videonuza veya ses dosyan\u0131za earrape efekti uygular.\n\n.nightcore\nKullan\u0131m: Yollad\u0131\u011f\u0131n\u0131z ses dosyas\u0131n\u0131 nightcore'a \u00c7evirir\n\n.slowedtoperfection \nKullan\u0131m: Yollad\u0131\u011f\u0131n\u0131z ses dosyas\u0131n\u0131 slowed to perfection'a \u00c7evirir.", "envCopySuccess": "%1%3%1 %2de\u011fi\u015fkeni ba\u015far\u0131yla%2 %1%4%1 %2de\u011fi\u015fkenine kopyaland\u0131%2", "envGetValue": "%2De\u011fi\u015fken:%2 %1%3%1\n%2De\u011fer:%2 %1%4%1", - "envInfo": ".env (set, get, rem, copy, move) \nKullan\u0131m: Botun de\u011fi\u015fkenlerini kolayca de\u011fi\u015fmenize yarar.", + "envInfo": ".env get \nKullan\u0131m: Belirtilen de\u011fi\u015fkene verilen de\u011feri g\u00f6sterir.\n\n.env set \nKullan\u0131m: Belirtilen de\u011fi\u015fkene verilen de\u011feri ayarlar.\n\n.env rem \nKullan\u0131m: Belirtilen de\u011fi\u015fkeni siler.\n\n.env copy \nKullan\u0131m: Kopyalanacak olan de\u011fi\u015fken de\u011ferini verilen de\u011fi\u015fkene kopyalar.\n\n.env move \nKullan\u0131m: Ta\u015f\u0131nacak de\u011fi\u015fken de\u011ferini hedef de\u011fi\u015fkene ta\u015f\u0131r.\n\n.env list\nKullan\u0131m: Mevcut de\u011fi\u015fkenlerin listesini verir.", "envListKeys": "%1Mevcut De\u011fi\u015fkenler:%1\n%3", "envMoveSuccess": "%1%3%1 %2de\u011fi\u015fkeni ba\u015far\u0131yla%2 %1%4%1 %2de\u011fi\u015fkenine ta\u015f\u0131nd\u0131%2", "envNotFound": "%1%3%1 %2de\u011fi\u015fkeni bulunamad\u0131%2", @@ -612,4 +612,4 @@ "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} +} \ No newline at end of file From 568b6184076c7563d4aa667743e7428aea1a62be Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 27 Feb 2021 11:49:03 +0300 Subject: [PATCH 011/242] seden: Typo Fix Signed-off-by: NaytSeyd --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2788f5..f6b9d3e 100755 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It git clone https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git cd Telegram-SedenUserBot -# Install pip dependincies +# Install pip dependencies pip3 install -r requirements.txt # Generate session from session.py (skip if there is already) From 9b0bb8dad398d7c5f91c749568984e17547f9025 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sun, 28 Feb 2021 22:30:18 +0300 Subject: [PATCH 012/242] seden: Cleanup code * Also includes PyLint fixes Signed-off-by: NaytSeyd --- sedenbot/__init__.py | 8 +++----- sedenbot/modules/admin/__init__.py | 2 +- sedenbot/modules/android.py | 2 +- sedenbot/modules/blacklist.py | 4 ++-- sedenbot/modules/effects.py | 8 ++++---- sedenbot/modules/gban.py | 3 +-- sedenbot/modules/horeke.py | 20 ++++++++++---------- sedenbot/modules/lydia.py | 2 -- sedenbot/modules/misc.py | 3 +-- sedenbot/modules/ocr.py | 2 +- sedenbot/modules/qrcode.py | 6 +++--- sedenbot/modules/reverse.py | 9 +++------ sedenbot/modules/scrapers.py | 7 +++---- sedenbot/modules/speedtest.py | 3 ++- sedenbot/modules/updater.py | 7 ++++--- sedenbot/modules/youtubedl.py | 1 - 16 files changed, 39 insertions(+), 48 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 51c7abc..cb8c844 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -15,16 +15,14 @@ from distutils.util import strtobool as sb from importlib import import_module from logging import basicConfig, getLogger, INFO, DEBUG, CRITICAL +from traceback import format_exc from requests import get -from pyrogram import Client +from dotenv import load_dotenv, set_key, unset_key +from pyrogram import Client, filters from pyrogram.handlers import MessageHandler -from pyrogram import filters - -from dotenv import load_dotenv, set_key, unset_key import sedenecem.translator as _tr -from traceback import format_exc def reload_env(): diff --git a/sedenbot/modules/admin/__init__.py b/sedenbot/modules/admin/__init__.py index 1a21422..d3d44d8 100644 --- a/sedenbot/modules/admin/__init__.py +++ b/sedenbot/modules/admin/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2020-2021 TeamDerUntergang # # This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. +# and licensed under GNU Affero General Public License v3. # See the GNU Affero General Public License for more details. # # All rights reserved. See COPYING, AUTHORS. diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 5bdd7e2..1040809 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -10,9 +10,9 @@ from re import sub from json import loads from urllib.parse import urlencode +from datetime import datetime from bs4 import BeautifulSoup from requests import get -from datetime import datetime from sedenbot import HELP, VALID_PROXY_URL from sedenecem.core import edit, extract_args, sedenify, get_translation diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/blacklist.py index 17023f5..4f1677b 100644 --- a/sedenbot/modules/blacklist.py +++ b/sedenbot/modules/blacklist.py @@ -66,8 +66,8 @@ def blacklist(message): message.continue_propagation() -@sedenify(pattern='^.addblacklist', compat=False) -def addblacklist(client, message): +@sedenify(pattern='^.addblacklist') +def addblacklist(message): if not sql: edit(message, f'`{get_translation("nonSqlMode")}`') return diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index adb5aaf..de426a9 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -37,7 +37,7 @@ def earrape(message): '-af', 'acrusher=.1:1:64:0:log', f'{media}.mp4']) - final, _ = process.communicate() + process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_doc( message, @@ -59,7 +59,7 @@ def earrape(message): '-af', 'acrusher=.1:1:64:0:log', f'{media}.mp3']) - final, _ = process.communicate() + process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3', delete_orig=True) remove(media) @@ -89,7 +89,7 @@ def nightcore(message): '-af', 'asetrate=44100*1.16,aresample=44100,atempo=1', f'{media}.mp3']) - final, _ = process.communicate() + process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') remove(media) @@ -117,7 +117,7 @@ def slowedtoperfection(message): '-af', 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', f'{media}.mp3']) - final, _ = process.communicate() + process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') remove(media) diff --git a/sedenbot/modules/gban.py b/sedenbot/modules/gban.py index be00ec5..9f8a0cd 100644 --- a/sedenbot/modules/gban.py +++ b/sedenbot/modules/gban.py @@ -11,7 +11,7 @@ from sedenbot import BRAIN from sedenecem.sql import gban_sql as sql -from sedenecem.core import (edit, sedenify, send_log, reply, +from sedenecem.core import (edit, sedenify, send_log, extract_args, get_translation) @@ -49,7 +49,6 @@ def gban_user(client, message): try: if sql.is_gbanned(user.id): return edit(message, f'`{get_translation("alreadyBanned")}`') - chat_id = message.chat.id sql.gban(user.id) edit( message, get_translation( diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index 3067efb..cb8048c 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -9,8 +9,8 @@ from os import execl, getpid from sys import executable, argv -from requests import get from math import floor +from requests import get from heroku3 import from_key from sedenbot import HELP, HEROKU_KEY, HEROKU_APPNAME @@ -111,17 +111,17 @@ def get_app_quota(): app_quota_percent])) -@sedenify(pattern='^.(restart|yb)$', compat=False) -def _restart(client, message): - return restart(client, message) +@sedenify(pattern='^.(restart|yb)$') +def _restart(message): + return restart(message) -@sedenify(pattern='^.d(restart|yb)$', compat=False) -def _drestart(client, message): - return restart(client, message, dyno=True) +@sedenify(pattern='^.d(restart|yb)$') +def _drestart(message): + return restart(message, dyno=True) -def restart(client, message, dyno=False): +def restart(message, dyno=False): send_log(get_translation('restartLog')) def std_off(): @@ -167,8 +167,8 @@ def std_ret(): dynos[0].restart() -@sedenify(pattern='^.(shutdown|kapat)$', compat=False) -def shutdown(client, message): +@sedenify(pattern='^.(shutdown|kapat)$') +def shutdown(message): edit(message, f'`{get_translation("shutdown")}`') send_log(get_translation('shutdownLog')) diff --git a/sedenbot/modules/lydia.py b/sedenbot/modules/lydia.py index 16f725e..35a1003 100644 --- a/sedenbot/modules/lydia.py +++ b/sedenbot/modules/lydia.py @@ -66,7 +66,6 @@ def addcf(message): reply_msg = message.reply_to_message if reply_msg: session = lydia.create_session() - session_id = session.id if not reply_msg.from_user.id: return edit(message, f'`{get_translation("lydiaError")}`') ACC_LYDIA.update({(message.chat.id & reply_msg.from_user.id): session}) @@ -107,7 +106,6 @@ def remcf(message): disable_edited=True, disable_notify=True) def user(message): - user_text = message.text try: session = ACC_LYDIA[message.chat.id & message.from_user.id] msg = message.text diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index 621434b..f612219 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -8,9 +8,8 @@ # from random import choice +from subprocess import PIPE, run as runapp from requests import post -from subprocess import PIPE -from subprocess import run as runapp from pybase64 import b64encode, b64decode from sedenbot import HELP, SUPPORT_GROUP diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/ocr.py index f150413..941ce89 100644 --- a/sedenbot/modules/ocr.py +++ b/sedenbot/modules/ocr.py @@ -7,7 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove, path, makedirs +from os import remove from requests import post from sedenbot import HELP, OCR_APIKEY diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index b3eec08..056a826 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -8,16 +8,16 @@ # from os import remove -from qrcode import QRCode, constants -from barcode import get -from barcode.writer import ImageWriter from urllib3 import PoolManager from bs4 import BeautifulSoup +from barcode import get +from barcode.writer import ImageWriter from sedenbot import HELP from sedenecem.core import (extract_args, sedenify, edit, reply_doc, download_media_wc, get_translation) +from qrcode import QRCode, constants @sedenify(pattern=r'^.decode$') def parseqr(message): diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index b4a989a..4428057 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -21,15 +21,12 @@ download_media_wc, get_translation) opener = request.build_opener() -useragent = '''Mozilla/5.0 (Linux; Android 9; -SM-G960F Build/PPR1.180610.011; wv) -AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 -Chrome/78.0.3904.70 Mobile Safari/537.36''' +useragent = 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.70 Mobile Safari/537.36' opener.addheaders = [('User-agent', useragent)] -@sedenify(pattern=r'^.reverse$', compat=False) -def reverse(client, message): +@sedenify(pattern=r'^.reverse$') +def reverse(message): photo = 'reverse.png' if path.isfile(photo): remove(photo) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index f3efb55..711c8f3 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -13,6 +13,7 @@ from urllib.parse import quote_plus from urllib.error import HTTPError from mimetypes import guess_type +from traceback import format_exc from urbandict import define from wikipedia import set_lang, summary from wikipedia.exceptions import DisambiguationError, PageError @@ -25,8 +26,6 @@ from pyrogram.types import InputMediaPhoto -from traceback import format_exc - from sedenbot import HELP, SEDEN_LANG from sedenecem.core import (sedenify, edit, send_log, reply_doc, reply_voice, extract_args, get_webdriver, get_translation) @@ -253,7 +252,7 @@ def ddgo(message): res1 = soup.findAll('table', {'border': 0}) res1 = res1[-1].findAll('tr') - match = do_ddsearch(query, res1) + match = do_ddsearch(res1) edit( message, get_translation( 'googleResult', [ @@ -261,7 +260,7 @@ def ddgo(message): send_log(get_translation('ddgoLog', [query])) -def do_ddsearch(query, res1): +def do_ddsearch(res1): def splitter(res): subs = [] tlist = None diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index d0a5f8c..9f339dd 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -8,12 +8,13 @@ # from datetime import datetime -from speedtest import Speedtest from sedenbot import HELP from sedenecem.core import (extract_args, sedenify, edit, reply_doc, get_translation) +from speedtest import Speedtest + @sedenify(pattern='^.speedtest') def speed_test(message): diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index ce159ef..f6cec70 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -10,14 +10,15 @@ from os import execl, path from sys import executable, argv from heroku3 import from_key -from git import Repo -from git.exc import (GitCommandError, InvalidGitRepositoryError, - NoSuchPathError) from sedenbot import HELP, HEROKU_KEY, HEROKU_APPNAME, REPO_URL from sedenecem.core import (extract_args, sedenify, edit, me, reply, reply_doc, get_translation) +from git import Repo +from git.exc import (GitCommandError, InvalidGitRepositoryError, + NoSuchPathError) + requirements_path = path.join( path.dirname(path.dirname(path.dirname(__file__))), 'requirements.txt') diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 71be6de..ceb2fe0 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -42,7 +42,6 @@ def youtubedl(message): title = video_info.get('title') uploader = video_info.get('uploader') - duration = video_info.get('duration') if util == 'mp4': edit(message, get_translation('downloadYTVideo', ['**', title, '`'])) From 261fa96a79ac76eecb0eeaf1549d6e29752f3424 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sun, 28 Feb 2021 22:48:47 +0300 Subject: [PATCH 013/242] seden: system: Fix neofetch markdown Signed-off-by: NaytSeyd --- sedenbot/modules/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 2420924..ccc2560 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -35,7 +35,7 @@ def neofetch(message): ['neofetch', f'HOSTNAME={HOSTNAME}', f'USER={USER}', '--stdout'], stdout=PIPE, stderr=PIPE) sonuc, _ = islem.communicate() - edit(message, sonuc.decode(), parse=None) + edit(message, f'`{sonuc.decode()}`') except BaseException: edit(message, f'`{get_translation("neofetchNotFound")}`') From 477505cfa0e9fe1f03fd1ae2f5b2760364cb8322 Mon Sep 17 00:00:00 2001 From: frknkrc44 Date: Wed, 31 Mar 2021 15:50:18 +0300 Subject: [PATCH 014/242] seden: Improve updown module Signed-off-by: frknkrc44 Signed-off-by: NaytSeyd --- sedenbot/modules/updown.py | 42 +++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index 2e82b47..6b96e1e 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -7,10 +7,17 @@ # All rights reserved. See COPYING, AUTHORS. # +from time import time from os.path import isfile -from sedenbot import HELP -from sedenecem.core import (download_media_wc, sedenify, edit, - extract_args, reply_doc, get_translation) +from sedenbot import HELP, TEMP_SETTINGS +from sedenecem.core import ( + download_media_wc, + sedenify, + edit, + extract_args, + reply_doc, + get_translation, +) @sedenify(pattern='^.download$') @@ -20,9 +27,17 @@ def download(message): edit(message, f'`{get_translation("downloadReply")}`') return + posix = time() + TEMP_SETTINGS[f'upload_{posix}'] = posix + def progress(current, total): - edit(message, get_translation('updownDownload', [ - '`', '(½{:.2f})'.format(current * 100 / total)])) + if (curr_posix := time()) - TEMP_SETTINGS[f'upload_{posix}'] > 5: + TEMP_SETTINGS[f'upload_{posix}'] = curr_posix + edit( + message, get_translation( + 'updownDownload', [ + '`', '(½{:.2f})'.format( + current * 100 / total)]), ) edit(message, f'`{get_translation("downloadMedia")}`') media = download_media_wc(reply, progress=progress) @@ -32,6 +47,7 @@ def progress(current, total): return edit(message, get_translation('updownDownloadSuccess', ['`', media])) + del TEMP_SETTINGS[f'upload_{posix}'] @sedenify(pattern='^.upload') @@ -42,9 +58,19 @@ def upload(message): edit(message, f'`{get_translation("uploadReply")}`') return + posix = time() + TEMP_SETTINGS[f'upload_{posix}'] = posix + def progress(current, total): - edit(message, get_translation('updownUpload', [ - '`', '(½{:.2f})'.format(current * 100 / total), args])) + if (curr_posix := time()) - TEMP_SETTINGS[f'upload_{posix}'] > 5: + TEMP_SETTINGS[f'upload_{posix}'] = curr_posix + edit( + message, + get_translation( + 'updownUpload', + ['`', '(½{:.2f})'.format(current * 100 / total), args], + ), + ) if isfile(args): try: @@ -55,9 +81,11 @@ def progress(current, total): edit(message, f'`{get_translation("uploadError")}`') raise e + del TEMP_SETTINGS[f'upload_{posix}'] return edit(message, f'`{get_translation("uploadFileError")}`') + del TEMP_SETTINGS[f'upload_{posix}'] HELP.update({'download': get_translation('uploadInfo')}) From d325723cda9449fe68a1b784c6483cdeac1689b8 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Fri, 2 Apr 2021 11:55:03 +0300 Subject: [PATCH 015/242] seden: Downgrade SQLAlchemy version * This will most likely fix your db error Signed-off-by: NaytSeyd --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58c69e3..3f5ce31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ pillow psycopg2-binary pybase64 pylast -pyrogram==1.1.13 +pyrogram==1.2.6 python-barcode python-dotenv qrcode @@ -21,7 +21,7 @@ removebg requests selenium speedtest-cli -sqlalchemy +sqlalchemy==1.3.23 tgcrypto urbandict wikipedia From 86e32b1c01415ac4aa39d29fbb5e2838d885c87e Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Fri, 2 Apr 2021 15:52:18 +0300 Subject: [PATCH 016/242] seden: Cleanup code Signed-off-by: NaytSeyd --- sedenbot/modules/lovers.py | 272 ------------------------------------- sedenbot/modules/system.py | 4 - 2 files changed, 276 deletions(-) delete mode 100644 sedenbot/modules/lovers.py diff --git a/sedenbot/modules/lovers.py b/sedenbot/modules/lovers.py deleted file mode 100644 index e722274..0000000 --- a/sedenbot/modules/lovers.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# -# For NaytSeyd ️️❤️ Saniye -# - - -from time import sleep -from random import choice -from sedenecem.core import edit, sedenify -# ================= CONSTANT ================= -SANIYE_STRINGS = [ - "blyrm🥺", "hallo", "nbr ex svglm", "evt🥺", "tamam askm", "hosgeldin askm", - "handicap", "jeg elsker dig", "ja", "ben uyuyorum, ig bebeler", "tmm cool", - "cool bruh", "bn varim🥺", "kstm", "@NightShade slk amd", "sefsz amd", - "amd skm", "ezk amd", "eeee😒", "bu ne lan Vsjjsbsjs", "uuuuu", - "gelin kuzucuklrm", "amedi skm", "ex svglilrm", - "svglm olmak istyenlr dm gelsn", "ikinizden ayriliyrm", "ayb krcm🥺", - "ve ii gclr bkr ❤️", "sn kmsn", "ekle bnie", "pika pikaaaaaaa", - "gule gule kullan", "cnku tg skm", "banane smk", "bkr skm", - "slk bkre dedim", "slm gzlm", "iimsn", "kayiplarda", - "snie gørdm dha ii oldm", "hello askm", "slk baris", "karima sg deme", - "sen haketmistin..", "kiyamam ki", "bana sg demen... 🥺", "hyr yalan", - "sen uzuyon", "beni cok uzuyorsun", "ecm skm", - "tmm küs, gece dc glmcm ozmn", "iyi gecelr 🌸", "yengenim", "eed askm", - "yemek yiyip gelcm bebislerm", "sapikmisin", "bana tapacaksin burda", - "nzbdbshajahahhq yazik", "komik olan ne", "belki gørdum nrdn biliyn", - "pjmani skm", "gle beraber uyuyalm", "ama uykm var🥺", "tm cok coolsn", - "bi susarmisiniz", "oglum sikrirtme kendini", "ingilizceni skm", - "buna gulecekmiydim", "søv ona", "@NightShade bana yavsiyo bu🥺", - "saniyeman ol", "eed puskuvut", "kutlay benim askim, ona yuruyemezsin😠", - "@NightShade amd😠", "bnie hep uzuyo🥺", "shhshshsha", - "bn derim senin yerine", "kanka deme lazim olr", - "seni gece ariyacam, kacta musaitsin", "ppn cko hos. svglm olrmsn", - "amd aglsn", "hiiiiio, happy bday ona ozaman", - "bildigim kadariyla u were tek kid", "jesus christ", - "o neydi lan zbnsshjaka", "tamam ozaman afettim🥺", - "alisdim artik beni uzmene...", "resimlerini siliyom...", - "seni cko øzledim❤️", "@NightShade 💞", - "bana dc girin diyorsunuz, beni takmiyorsunuz srfszlr", "kalbime gir", - "hadi see u", "bu gece de ben konusamam 🥺", - "bn cikiom tgden birazdan, instageamdan yazarsn", - "sn iyi degilken bende iyi olamam 🥺", "nazar degdi :(", "snie øzldm", - "bende sana atmistim, sende stickers yaptin :(", "yeterince cool degilsn", - "wowoowowowow", "uwuwuwuwuwu", "elni at"] -# ================= CONSTANT ================= - - -@sedenify(pattern='^.saniye') -def saniyeify(message): - saniye(message) - - -def saniye(message): - edit(message, choice(SANIYE_STRINGS)) - - -@sedenify(pattern='^.saniş$') -def sanis(message): - animation_interval = 0.1 - animation_ttl = range(0, 100) - edit( - message, - '[Saniş](tg://user?id=623847224) 💘 [NaytSeyd](tg://user?id=551728027)') - animation_chars = [ - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n◼️◼️◼️◼️❤️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n❤️◼️◼️◼️◼️\n◼️◼️◼️◼️◼️`", - "`◼️◼️◼️◼️◼️\n❤️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️❤️\n◼️◼️◼️◼️◼️`", - "`◼️❤️◼️◼️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️◼️◼️❤️◼️`", - "`◼️◼️◼️❤️◼️\n◼️◼️◼️◼️◼️\n◼️🧛🏻‍♂️❤️🧛🏻‍♀️◼️\n◼️◼️◼️◼️◼️\n◼️❤️◼️◼️◼️`", - ] - for i in animation_ttl: - sleep(animation_interval) - edit(message, animation_chars[i % len(animation_chars)]) - - -@sedenify(pattern='^.snş$') -def sns(message): - animation_interval = 0.5 - animation_ttl = range(0, 7) - edit(message, "Saniş ❤️") - animation_chars = [ - "S_", - "SA_", - "SAN_", - "SANİ_", - "SANİŞ_", - "SANİŞ❤_", - "[Saniş](tg://user?id=623847224) 💘 [NaytSeyd](tg://user?id=551728027)", - ] - for i in animation_ttl: - sleep(animation_interval) - edit(message, animation_chars[i % len(animation_chars)]) - - -@sedenify(pattern='^.naytsaniş$') -def naytsanis(message): - animation_interval = 1 - animation_ttl = range(0, 6) - edit( - message, - '[Saniş](tg://user?id=623847224) 💘 [NaytSeyd](tg://user?id=551728027)') - animation_chars = [ - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀⠀⠀⠀ ⢳⡀⠀⡏⠀⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀⠀⠀ ⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ NaytSeyd ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀⠀❤️⠀⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀⠀⠀⠀ ⠀⢳⡀⠀⡏⠀⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀⠀⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ Saniş ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀❤️⠀⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀ ⠀⢳⡀⠀⡏⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀⠀⠀⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ NaytSeyd ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀❤️⠀⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀ ⠀⢳⡀⠀⡏⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀ ⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ Saniş ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀⠀❤️ ⠀⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀⠀⠀⠀ ⢳⡀⠀⡏⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀⠀ ⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ NaytSeyd ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀❤️ ⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", - "`⠀⠀⠀⣠⣶⡾⠏⠉⠙⠳⢦⡀⠀⠀⠀⢠⠞⠉⠙⠲⡀⠀\n ⠀⣴⠿⠏⠀⠀⠀⠀⠀ ⠀⢳⡀⠀⡏⠀⠀ ⠀⢷\n⢠⣟⣋⡀⢀⣀⣀⡀⠀⣀⡀⣧⠀⢸⠀ ⠀ ⡇\n⢸⣯⡭⠁⠸⣛⣟⠆⡴⣻⡲⣿ ⣸ Saniş ⡇\n ⣟⣿⡭⠀⠀⠀⠀⠀⢱⠀ ⣿ ⢹⠀ ⡇\n ⠙⢿⣯⠄⠀⠀❤️⠀⠀⡿ ⠀⡇⠀⠀⠀⠀ ⡼\n⠀⠀⠀⠹⣶⠆⠀⠀⠀⠀⠀⡴⠃ ⠘⠤⣄⣠⠞⠀\n⠀⠀⠀⠀⢸⣷⡦⢤⡤⢤⣞⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⢀⣤⣴⣿⣏⠁⠀⠀⠸⣏⢯⣷⣖⣦⡀⠀⠀⠀⠀⠀⠀\n⢀⣾⣽⣿⣿⣿⣿⠛⢲⣶⣾⢉⡷⣿⣿⠵⣿⠀⠀⠀⠀⠀⠀\n⣼⣿⠍⠉⣿⡭⠉⠙⢺⣇⣼⡏⠀⠀ ⠀⣄⢸⠀⠀⠀⠀⠀⠀`", ] - for i in animation_ttl: - sleep(animation_interval) - edit(message, animation_chars[i % len(animation_chars)]) - - -@sedenify(pattern='^.❤️$') -def sanisveamed(message): - edit( - message, "⠀⠀`.-----. .-----. `\n" - r"` / '..' \ `\n" - "`| | `\n" - "`| Ahmet | `\n" - r"` \ Saniş / `\n" - r"` \ / `\n" - r"` '\ /' `\n" - r"` '\ /' `\n" - r"` '\ /' `\n" - r"` '\/' `\n" - " [Saniş](tg://user?id=623847224) 💘 [NaytSeyd](tg://user?id=551728027)") diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index ccc2560..ac81476 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -16,7 +16,6 @@ from pyrogram.raw.functions.help import GetNearestDc -from sedenbot.modules.lovers import saniye from sedenbot.modules.ecem import ecem from sedenbot import (HELP, ALIVE_MSG, CHANNEL, BOT_VERSION, HOSTNAME, USER) @@ -103,9 +102,6 @@ def alive(message): if KULLANICIMESAJI.lower() == 'ecem': ecem(message) return - elif KULLANICIMESAJI.lower() == 'saniye': - saniye(message) - return edit(message, f'{KULLANICIMESAJI}') From 81f200cb2d705d102ae994d026359ce617723534 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Fri, 2 Apr 2021 16:13:37 +0300 Subject: [PATCH 017/242] seden: Fix missing translations Signed-off-by: NaytSeyd --- sedenbot/modules/profile.py | 2 +- sedenecem/translator/en.json | 5 +++-- sedenecem/translator/tr.json | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 3a63e7a..f322e3e 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -87,7 +87,7 @@ def remove_profilepic(client, message): for photo in client.iter_profile_photos('me', limit=lim): client.delete_profile_photos(photo.file_id) count += 1 - edit(message, f'`{count} adet profil fotoğrafı silindi.`') + edit(message, f'`{get_translation("ppDeleted", [count])}`') @sedenify(pattern='^.setbio', compat=False) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index cd5985e..d7c52f2 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -363,8 +363,9 @@ "pmpermitLog": "[%1](tg://user?id=%2) was just another retarded nibba", "pmpermitMessage": "%1Bleep blop! This is a bot. Don't fret.\n\nMy F\u00fchrer hasn't approved you to PM.\nPlease wait for my F\u00fchrer to look in, he mostly approves PMs.\n\nAs far as I know, he doesn't usually approve retards though.%1", "pmpermitSqlLog": "Unable to run pmpermit module, no SQL connection found", + "ppDeleted": "%1 profile photos were deleted", "ppError": "Failure occured while processing image.", - "ppchanged": "Profile picture changed successfully..", + "ppChanged": "Profile picture changed successfully..", "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", @@ -612,4 +613,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} \ No newline at end of file +} diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index bc58d2a..4a13b74 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -364,8 +364,9 @@ "pmpermitLog": "[%1](tg://user?id=%2) ki\u015fisi sadece bir hayal k\u0131r\u0131kl\u0131\u011f\u0131yd\u0131.\nPM'ni me\u015fgul etti\u011fi i\u00e7in engellendi.", "pmpermitMessage": "%1Hey! Bu bir bot. Endi\u015felenme.\n\nSahibim sana PM atma izni vermedi.\nL\u00fctfen sahibimin aktif olmas\u0131n\u0131 bekleyin, o genellikle PM'leri onaylar.\n\nBildi\u011fim kadar\u0131yla o kafay\u0131 yemi\u015f insanlara PM izni vermiyor.%1", "pmpermitSqlLog": "Pmpermit mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", + "ppDeleted": "%1 adet profil foto\u011fraf\u0131 silindi.", "ppError": "Resim i\u015flenirken bir hata olu\u015ftu.", - "ppchanged": "Profil resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", + "ppChanged": "Profil resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", "privacySettings": "Gizlilik ayarlar\u0131 bunu yapmama engel oldu.", "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", "processing": "\u0130\u015fleniyor...", @@ -612,4 +613,4 @@ "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} \ No newline at end of file +} From 9cf95586d0ea94a551d3004b00bb0a988cbb88f9 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Fri, 2 Apr 2021 20:44:25 +0300 Subject: [PATCH 018/242] seden: Add ascii command Signed-off-by: NaytSeyd --- requirements.txt | 1 + sedenbot/modules/misc.py | 22 ++++++++++++++++++++++ sedenecem/translator/en.json | 6 +++--- sedenecem/translator/tr.json | 6 +++--- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3f5ce31..dc2d6ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ googletrans==3.1.0a0 gtts heroku3 humanize +image_to_ascii lyricsgenius pillow psycopg2-binary diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index f612219..8be11bd 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -8,9 +8,12 @@ # from random import choice +from os import remove + from subprocess import PIPE, run as runapp from requests import post from pybase64 import b64encode, b64decode +from image_to_ascii import ImageToAscii from sedenbot import HELP, SUPPORT_GROUP from sedenecem.core import (edit, reply, reply_doc, sedenify, @@ -228,4 +231,23 @@ def birakmamseni(message): edit(message, sonuc, preview=False) +@sedenify(pattern='^.ascii$') +def img_to_ascii(message): + reply = message.reply_to_message + edit(message, f'`{get_translation("processing")}`') + if not reply: + edit(message, f'`{get_translation("wrongCommand")}`') + return + + if not(reply.photo or reply.sticker or ( + reply.document and 'image' in reply.document.mime_type)): + edit(message, f'`{get_translation("wrongMedia")}`') + else: + media = download_media_wc(reply, file_name='ascii.png') + ImageToAscii(imagePath=media, outputFile="output.txt") + + reply_doc(message, 'output.txt', delete_orig=True, delete_after_send=True) + remove('ascii.png') + + HELP.update({'misc': get_translation('miscInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index d7c52f2..03028ac 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -294,7 +294,7 @@ "mediaInvalid": "Invalid media.", "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f \nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy \nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat \nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random ... \nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.birakmamseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat \nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random ... \nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.birakmamseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "%1#MUTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", "muteProcess": "Gets a tape!", @@ -363,9 +363,9 @@ "pmpermitLog": "[%1](tg://user?id=%2) was just another retarded nibba", "pmpermitMessage": "%1Bleep blop! This is a bot. Don't fret.\n\nMy F\u00fchrer hasn't approved you to PM.\nPlease wait for my F\u00fchrer to look in, he mostly approves PMs.\n\nAs far as I know, he doesn't usually approve retards though.%1", "pmpermitSqlLog": "Unable to run pmpermit module, no SQL connection found", + "ppChanged": "Profile picture changed successfully..", "ppDeleted": "%1 profile photos were deleted", "ppError": "Failure occured while processing image.", - "ppChanged": "Profile picture changed successfully..", "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", @@ -613,4 +613,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 4a13b74..e839465 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -296,7 +296,7 @@ "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f \nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy \nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", - "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat \nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random ... \nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.birakmamseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.", + "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat \nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random ... \nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.birakmamseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", "mockUsage": "bANa bIr mETin vEr!", "muteLog": "%1#MUTE\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", "muteProcess": "Sessize al\u0131n\u0131yor...", @@ -364,9 +364,9 @@ "pmpermitLog": "[%1](tg://user?id=%2) ki\u015fisi sadece bir hayal k\u0131r\u0131kl\u0131\u011f\u0131yd\u0131.\nPM'ni me\u015fgul etti\u011fi i\u00e7in engellendi.", "pmpermitMessage": "%1Hey! Bu bir bot. Endi\u015felenme.\n\nSahibim sana PM atma izni vermedi.\nL\u00fctfen sahibimin aktif olmas\u0131n\u0131 bekleyin, o genellikle PM'leri onaylar.\n\nBildi\u011fim kadar\u0131yla o kafay\u0131 yemi\u015f insanlara PM izni vermiyor.%1", "pmpermitSqlLog": "Pmpermit mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", + "ppChanged": "Profil resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", "ppDeleted": "%1 adet profil foto\u011fraf\u0131 silindi.", "ppError": "Resim i\u015flenirken bir hata olu\u015ftu.", - "ppChanged": "Profil resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", "privacySettings": "Gizlilik ayarlar\u0131 bunu yapmama engel oldu.", "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", "processing": "\u0130\u015fleniyor...", @@ -613,4 +613,4 @@ "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} +} \ No newline at end of file From 30c06222335d7d11e17148e95b25b5c060c19191 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Fri, 2 Apr 2021 22:03:32 +0300 Subject: [PATCH 019/242] seden: Fix import mistake Signed-off-by: NaytSeyd --- sedenbot/modules/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index 8be11bd..cf3b7eb 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -16,8 +16,8 @@ from image_to_ascii import ImageToAscii from sedenbot import HELP, SUPPORT_GROUP -from sedenecem.core import (edit, reply, reply_doc, sedenify, - extract_args, get_translation) +from sedenecem.core import (edit, reply, reply_doc, sedenify, extract_args, + download_media_wc, get_translation) @sedenify(pattern='^.random') From bff065cc128888a2717de2d5002ef978d453dbce Mon Sep 17 00:00:00 2001 From: frknkrc44 Date: Sat, 3 Apr 2021 00:43:29 +0300 Subject: [PATCH 020/242] Fix ascii and setpfp --- .gitignore | 24 ++++++------------------ sedenbot/modules/misc.py | 8 ++++---- sedenbot/modules/profile.py | 12 +++++++++++- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 2bb90af..00d9e75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,19 @@ config.env -__pycache__/* -.DS_Store/* brains.check learning-data-root.check blacklist.check sedenbot.db sedenbot.session* -sedenbot/__pycache__/* -sedenbot/.DS_Store/* -sedenbot/modules/.DS_Store/* -sedenbot/modules/__pycache__/* -sedenbot/modules/admin/.DS_Store/* -sedenbot/modules/admin/__pycache__/* -sedenecem/core/__pycache__/* -sedenecem/core/.DS_Store/* -sedenecem/sql/.DS_Store/* -sedenecem/sql/__pycache__/* -sedenecem/.DS_Store/* -sedenecem/__pycache__/* -sedenecem/translator/__pycache__/* +.DS_Store/ +__pycache__/ +downloads/ +.vscode/ *.check *.session *.session-journal .progress -.vscode/* -.idea/* +.idea/ *secret* *.log -bin/* +bin/ dump.rdb diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index cf3b7eb..3bb0904 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -239,15 +239,15 @@ def img_to_ascii(message): edit(message, f'`{get_translation("wrongCommand")}`') return - if not(reply.photo or reply.sticker or ( + if not(reply.photo or (reply.sticker and not reply.sticker.is_animated) or ( reply.document and 'image' in reply.document.mime_type)): edit(message, f'`{get_translation("wrongMedia")}`') else: media = download_media_wc(reply, file_name='ascii.png') ImageToAscii(imagePath=media, outputFile="output.txt") - - reply_doc(message, 'output.txt', delete_orig=True, delete_after_send=True) - remove('ascii.png') + reply_doc(reply, 'output.txt', delete_orig=False, delete_after_send=True) + message.delete() + remove('downloads/ascii.png') HELP.update({'misc': get_translation('miscInfo')}) diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index f322e3e..f3f5538 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -10,6 +10,8 @@ from os import remove from time import sleep +from PIL import Image + from pyrogram.errors import UsernameOccupied from pyrogram.raw.functions import channels, account @@ -66,8 +68,16 @@ def set_profilepic(client, message): edit(message, f'`{INVALID_MEDIA}`') if photo: - client.set_profile_photo(photo=photo) + image = Image.open(photo) + width, height = image.size + maxSize = (640, 640) + ratio = min(maxSize[0]/width, maxSize[1]/height) + image = image.resize((int(width*ratio), int(height*ratio))) + new_photo = 'downloads/profile_photo_new.png' + image.save(new_photo) + client.set_profile_photo(photo=new_photo) remove(photo) + remove(new_photo) edit(message, f'`{PP_CHANGED}`') else: edit(message, f'`{PP_ERROR}`') From 89ae5c82169d86c331b02484661e439ad0b30f71 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 3 Apr 2021 19:06:22 +0300 Subject: [PATCH 021/242] seden: A little changes Signed-off-by: NaytSeyd --- sedenbot/modules/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index 3bb0904..c01ce2c 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -245,9 +245,9 @@ def img_to_ascii(message): else: media = download_media_wc(reply, file_name='ascii.png') ImageToAscii(imagePath=media, outputFile="output.txt") - reply_doc(reply, 'output.txt', delete_orig=False, delete_after_send=True) + reply_doc(reply, 'output.txt', delete_after_send=True) message.delete() - remove('downloads/ascii.png') + remove(media) HELP.update({'misc': get_translation('miscInfo')}) From 8c3c1042d478d9735828cab7e39eefa3aeadd695 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Tue, 6 Apr 2021 22:15:59 +0300 Subject: [PATCH 022/242] seden: Change log strings Signed-off-by: NaytSeyd --- sedenecem/core/sedenify.py | 3 +-- sedenecem/translator/en.json | 3 +-- sedenecem/translator/tr.json | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index f93edb9..6dd92b7 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -98,8 +98,7 @@ def wrap(client, message): edit( message, f'`{get_translation("errorLogSend")}`') - link = get_translation('supportGroup', [SUPPORT_GROUP]) - text = get_translation('sedenErrorText', ['**', link]) + text = get_translation('sedenErrorText', ['**', '`', exc_info()[1]]) ftext = get_translation( 'sedenErrorText2', diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 03028ac..6cad0a8 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -471,7 +471,7 @@ "sedResult": "Did you mean ? \n\n%1", "sedenAlive": "Hello Seden! I am alive \u2764\ufe0f", "sedenErrorResult": "Query could not be returned / wrong", - "sedenErrorText": "%1SEDENBOT ERROR REPORT%1\nIf you want to, you can report it\njust forward this message to %2.\nNothing is logged except the fact of error and date\n", + "sedenErrorText": "%1ERROR:%1\n\n%2%3%2\n\n%1See the Log File for details.%1", "sedenErrorText2": "========== DISCLAIMER ==========\nThis file uploaded only here,\nwe logged only fact of error and date,\nour respect your privacy,'\nyou may not report this error if you've\nany confidential data here, no one will see your data.\n================================\n\n--------BEGIN SEDENBOT TRACEBACK LOG--------\n\nDate: %1\nChat ID: %2\nSender ID: %3\nSeden version: %4\n\nError Trigger:\n%5\n\nTraceback info:\n%6\n\nError text:\n%7\n\n--------END SEDENBOT TRACEBACK LOG--------\n\n\nLast 10 commits:\n", "sedenGitNotFound": "btw, Seden loves u \u2764\ufe0f", "sedenNearestDC": "%1Country:%1 %2%3%2\n%1Nearest Datacenter:%1 %2%4%2\n%1Current Datacenter:%1 %2%5%2", @@ -526,7 +526,6 @@ "stickerPackFull": "Pack %1 is full.", "stickerUsage": "Give me something..", "strUsage": "GiiiiiiiB sooooooomeeeeeee teeeeeeext!", - "supportGroup": "[Seden Support Group](https://telegram.dog/%1)", "supportResult": "You can reach our support group [here](http://t.me/%1).", "syntaxError": "Syntax error.", "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.pip \nUsage: Searches in pip modules.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound... but you don/'t.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index e839465..a827e43 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -471,7 +471,7 @@ "sedResult": "Bunu mu demek istedin ? \n\n%1", "sedenAlive": "Merhaba Seden! Seni Seviyorum \u2764\ufe0f", "sedenErrorResult": "Sorgu eksik veya hatal\u0131", - "sedenErrorText": "%1SEDENBOT HATA RAPORU%1\n\u0130sterseniz, bunu rapor edebilirsiniz\nsadece bu mesaj\u0131 buraya iletin %2.\nHata ve Tarih d\u0131\u015f\u0131nda hi\u00e7bir \u015fey kaydedilmez\n", + "sedenErrorText": "%1HATA:%1\n\n%2%3%2\n\n%1Detaylar i\u00e7in dosyaya bak\u0131n\u0131z.%1", "sedenErrorText2": "========== UYARI ==========\nBu dosya sadece burada y\u00fcklendi,\nsadece hata ve tarih k\u0131sm\u0131n\u0131 kaydettik,\ngizlili\u011finize sayg\u0131 duyuyoruz,\nburada herhangi bir gizli veri varsa\nbu hata raporu olmayabilir, kimse verilerinize ula\u015famaz.\n================================\n\n--------SEDENBOT HATA GUNLUGU--------\n\nTarih: %1\nGrup ID: %2\nG\u00f6nderen ki\u015finin ID: %3\nSeden s\u00fcr\u00fcm\u00fc: %4\n\nOlay Tetikleyici:\n%5\n\nGeri izleme bilgisi::\n%6\n\nHata metni:\n%7\n\n--------SEDENBOT HATA GUNLUGU BITIS--------\n\n\nSon 10 commit:\n", "sedenGitNotFound": "Bu arada Seden seni \u00e7ok seviyor \u2764\ufe0f", "sedenNearestDC": "%1\u00dclke:%1 %2%3%2\n%1En Yak\u0131n Veri Merkezi:%1 %2%4%2\n%1\u015eu Anki Veri Merkezi:%1 %2%5%2", @@ -526,7 +526,6 @@ "stickerPackFull": "%1 numaral\u0131 paket dolu.", "stickerUsage": "Bana d\u0131zlayabilece\u011fim bir \u015fey ver.", "strUsage": "Baaaaanaaaaa biiiiir meeeeetiiiiin veeeeer!", - "supportGroup": "[Seden Destek Grubu](https://telegram.dog/%1)", "supportResult": "[Buradan](http://t.me/%1) destek grubumuza ula\u015fabilirsiniz.", "syntaxError": "S\u00f6zdizimi hatas\u0131.", "systemInfo": ".alive\nKullan\u0131m: Seden botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.pip \nKullan\u0131m: Pip mod\u00fcllerinde arama yapar.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin...", From bcb01294592f6dcb9fd5023566dd01e10ef3a786 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Thu, 8 Apr 2021 13:21:39 +0300 Subject: [PATCH 023/242] seden: Add sudo & blacklist control on whois module * Also pyrogram version upgraded Signed-off-by: NaytSeyd --- sedenbot/modules/whois.py | 17 +++++++++++++++-- sedenecem/core/replier.py | 5 +++-- sedenecem/translator/en.json | 6 ++++-- sedenecem/translator/tr.json | 6 ++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/sedenbot/modules/whois.py b/sedenbot/modules/whois.py index 3c89420..e5be989 100644 --- a/sedenbot/modules/whois.py +++ b/sedenbot/modules/whois.py @@ -7,7 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -from sedenbot import HELP +from sedenbot import HELP, BLACKLIST, BRAIN from sedenecem.core import (extract_args, sedenify, edit, reply_img, get_translation, download_media_wc) @@ -57,11 +57,14 @@ def who_is(client, message): bio = reply_chat.bio or get_translation('notSet') status = reply_user.status last_seen = LastSeen(bot, status) + sudo = SudoCheck(user_id) + blacklist = BlacklistCheck(user_id) caption = get_translation( 'whoisResult', ['**', '`', first_name, last_name, username, user_id, photos, - dc_id, bot, scam, verified, chats, bio, last_seen]) + dc_id, bot, scam, verified, chats, bio, last_seen, sudo + if sudo else '', blacklist if blacklist else '']) if photo and media_perm: reply_img( @@ -89,4 +92,14 @@ def LastSeen(bot, status): return get_translation('statusLong') +def SudoCheck(user_id): + if user_id in BRAIN: + return get_translation('sudoCheck') + + +def BlacklistCheck(user_id): + if user_id in BLACKLIST: + return get_translation('blacklistCheck') + + HELP.update({'whois': get_translation('whoisInfo')}) diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 61047d9..7b04676 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -17,11 +17,12 @@ def reply_img( caption='', fix_markdown=False, delete_orig=False, - delete_file=False): + delete_file=False, + parse='md'): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - message.reply_photo(photo, caption=caption.strip()) + message.reply_photo(photo, caption=caption.strip(), parse_mode=parse) if delete_orig: message.delete() diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 6cad0a8..1f08d0b 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -68,6 +68,7 @@ "birakmamseniResult": "%1\u26ab\u26aa Birakmam Seni Data \u26ab\u26aa%1\n\nAs of now, as part of %1BIRAKMAM SENI%1 campaign %2%3%2 \ud83d\udda4\ud83e\udd0d pieces of support were provided.\nCome to, support our %1BE\u015eIKTA\u015e%1 \ud83e\udd85 !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSupports coming through SMS, Money Order / Eft and Postal Check channels are added to meter periodically.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1has been blacklisted for this chat.%1", "blacklistChats": "Blacklists in the Current Chat:", + "blacklistCheck": "This person is blacklisted in Seden UserBot!", "blacklistInfo": ".showblacklist\nUsage: Lists all active userbot blacklist in a chat.\n\n.addblacklist \nUsage: Saves the message to the 'blacklist keyword'.\nThe bot will delete to the message whenever 'blacklist keyword' is mentioned.\n\n.rmblacklist \nUsage: Stops the specified blacklist.", "blacklistLog": "#BLACKLIST\nChat ID: %1%2%1\nWord: %1%3%1", "blacklistPermission": "I don't have permission to delete messages in this group !", @@ -526,6 +527,7 @@ "stickerPackFull": "Pack %1 is full.", "stickerUsage": "Give me something..", "strUsage": "GiiiiiiiB sooooooomeeeeeee teeeeeeext!", + "sudoCheck": "This person is Seden admin!", "supportResult": "You can reach our support group [here](http://t.me/%1).", "syntaxError": "Syntax error.", "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.pip \nUsage: Searches in pip modules.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound... but you don/'t.", @@ -601,7 +603,7 @@ "whoisError": "Failed to fetching user..", "whoisInfo": ".whois\nUsage: Retrieves user's information.", "whoisProcess": "Fetching user information..", - "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Is Bot:%1 %2%9%2\n%1Is Restricted:%1 %2%10%2\n%1Is Verified by Telegram:%1 %2%11%2\n%1Common chats:%1 %2%12%2\n\n%1Bio:%1 %2%13%2\n%1Last Seen:%1 %2%14%2\n%1Profile link: [%3](tg://user?id=%6)%1", + "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Is Bot:%1 %2%9%2\n%1Is Restricted:%1 %2%10%2\n%1Is Verified by Telegram:%1 %2%11%2\n%1Common chats:%1 %2%12%2\n\n%1Bio:%1 %2%13%2\n%1Last Seen:%1 %2%14%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%15\n%16%1", "wikiError": "Disambiguated page found.\n\n%1", "wikiError2": "Page not found.\n\n%1", "wikiInfo": ".wiki \nUsage: Does a search on Wikipedia.", @@ -612,4 +614,4 @@ "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} \ No newline at end of file +} diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index a827e43..121cb16 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -69,6 +69,7 @@ "birakmamseniResult": "%1\u26ab\u26aa B\u0131rakmam Seni Kampanyas\u0131 Verileri \u26ab\u26aa%1\n\n\u015eu an itibar\u0131yla %1BIRAKMAM SEN\u0130%1 kampanyas\u0131 kapsam\u0131nda %2%3%2 \ud83d\udda4\ud83e\udd0d adet destekte bulunuldu.\nHaydi sen de hemen %1B\u00dcY\u00dcK BE\u015e\u0130KTA\u015e\u2019IMIZA%1 \ud83e\udd85 destek ol !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSMS, Havale/Eft ve Posta \u00c7eki kanallar\u0131 ile gelen destekler periyodik olarak sayaca eklenmektedir.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1bu sohbet i\u00e7in kara listeye al\u0131nd\u0131.%1", "blacklistChats": "Bu grup i\u00e7in ayarlanan karaliste:", + "blacklistCheck": "Bu ki\u015finin Seden UserBot kullan\u0131m\u0131 yasaklanm\u0131\u015ft\u0131r!", "blacklistInfo": ".showblacklist\nKullan\u0131m: Bir sohbetteki etkin kara listeyi listeler.\n\n.addblacklist \nKullan\u0131m: \u0130letiyi 'kara liste anahtar kelimesine' kaydeder. 'Kara liste anahtar kelimesinden' bahsedildi\u011finde bot iletiyi siler.\n\n.rmblacklist \nKullan\u0131m: Belirtilen kara listeyi durdurur.", "blacklistLog": "#BLACKLIST\nSohbet ID: %1%2%1\nKelime: %1%3%1", "blacklistPermission": "Bu grupta mesaj silme iznim yok !", @@ -526,6 +527,7 @@ "stickerPackFull": "%1 numaral\u0131 paket dolu.", "stickerUsage": "Bana d\u0131zlayabilece\u011fim bir \u015fey ver.", "strUsage": "Baaaaanaaaaa biiiiir meeeeetiiiiin veeeeer!", + "sudoCheck": "Bu ki\u015fi Seden yetkilisi!", "supportResult": "[Buradan](http://t.me/%1) destek grubumuza ula\u015fabilirsiniz.", "syntaxError": "S\u00f6zdizimi hatas\u0131.", "systemInfo": ".alive\nKullan\u0131m: Seden botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.pip \nKullan\u0131m: Pip mod\u00fcllerinde arama yapar.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin...", @@ -601,7 +603,7 @@ "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", "whoisInfo": ".whois\nKullan\u0131m: Kullan\u0131c\u0131n\u0131n bilgilerini al\u0131r.", "whoisProcess": "Kullan\u0131c\u0131 bilgisi getiriliyor..", - "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Bot Mu:%1 %2%9%2\n%1K\u0131s\u0131tl\u0131 M\u0131:%1 %2%10%2\n%1Telegram Taraf\u0131ndan Onayl\u0131 M\u0131:%1 %2%11%2\n%1Ortak Sohbetler:%1 %2%12%2\n\n%1Biyografi:%1 %2%13%2\n%1Son G\u00f6r\u00fclme:%1 %2%14%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)%1", + "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Bot Mu:%1 %2%9%2\n%1K\u0131s\u0131tl\u0131 M\u0131:%1 %2%10%2\n%1Telegram Taraf\u0131ndan Onayl\u0131 M\u0131:%1 %2%11%2\n%1Ortak Sohbetler:%1 %2%12%2\n\n%1Biyografi:%1 %2%13%2\n%1Son G\u00f6r\u00fclme:%1 %2%14%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%15\n%16%1", "wikiError": "Belirsiz bir sayfa bulundu.\n\n%1", "wikiError2": "Arad\u0131\u011f\u0131n\u0131z sayfa bulunamad\u0131.\n\n%1", "wikiInfo": ".wiki \nKullan\u0131m: Bir Vikipedi aramas\u0131 ger\u00e7ekle\u015ftirir.", @@ -612,4 +614,4 @@ "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} \ No newline at end of file +} From 5f1f6e519150f1baddcc02de3964c72d7fae5d79 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 10 Apr 2021 17:46:16 +0300 Subject: [PATCH 024/242] seden: Android: Improve Magisk * Urls changed * Canary added Signed-off-by: NaytSeyd --- sedenbot/modules/android.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 1040809..55c352f 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -25,17 +25,18 @@ def magisk(message): magisk_dict = { 'Stable': 'https://raw.githubusercontent.com/topjohnwu/' - 'magisk_files/master/stable.json', + 'magisk-files/master/stable.json', 'Beta': 'https://raw.githubusercontent.com/topjohnwu/' - 'magisk_files/master/beta.json'} + 'magisk-files/master/beta.json', + 'Canary': + 'https://raw.githubusercontent.com/topjohnwu/' + 'magisk-files/master/canary.json'} releases = f'**{get_translation("magiskReleases")}**\n' for name, release_url in magisk_dict.items(): try: data = get(release_url).json() - releases += f'`{name}:` [ZIP v{data["magisk"]["version"]}]({data["magisk"]["link"]}) | ' \ - f'[APK v{data["app"]["version"]}]({data["app"]["link"]}) | ' \ - f'[Uninstaller]({data["uninstaller"]["link"]})\n' + releases += f'`{name}:` [APK v{data["magisk"]["version"]}]({data["magisk"]["link"]})\n' except BaseException: pass edit(message, releases, preview=False) From 61cb77a435c0282b5107d27a521d05e7ab994b0d Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 10 Apr 2021 18:30:02 +0300 Subject: [PATCH 025/242] seden: Add zombies command Signed-off-by: NaytSeyd --- sedenbot/modules/ban.py | 48 +++++++++++++++++++++++++++++++++++- sedenecem/translator/en.json | 14 ++++++++--- sedenecem/translator/tr.json | 14 ++++++++--- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index e948342..3f758b7 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -10,7 +10,7 @@ from time import sleep from pyrogram.types import ChatPermissions -from pyrogram.errors import MessageTooLong +from pyrogram.errors import MessageTooLong, UserAdminInvalid from sedenbot import HELP, BRAIN from sedenecem.core import (edit, sedenify, send_log, reply_doc, @@ -430,6 +430,52 @@ def get_users(client, message): delete_after_send=True, delete_orig=True) +@sedenify(pattern='^.zombies', private=False, admin=True, compat=False) +def zombie_accounts(client, message): + args = extract_args(message).lower() + chat_id = message.chat.id + count = 0 + msg = f'`{get_translation("zombiesNoAccount")}`' + + if args != 'clean': + edit(message, f'`{get_translation("zombiesFind")}`') + for i in client.iter_chat_members(chat_id): + if i.user.is_deleted: + count += 1 + sleep(1) + if count > 0: + msg = get_translation('zombiesFound', ['**', '`', count]) + return edit(message, msg) + + edit(message, f'`{get_translation("zombiesRemove")}`') + count = 0 + users = 0 + + for i in client.iter_chat_members(chat_id): + if i.user.is_deleted: + try: + client.kick_chat_member(chat_id, i.user.id) + except UserAdminInvalid: + count -= 1 + users += 1 + except BaseException: + return edit(message, f'`{get_translation("zombiesError")}`') + client.unban_chat_member(chat_id, i.user.id) + count += 1 + + if count > 0: + msg = get_translation('zombiesResult', ['**', '`', count]) + + if users > 0: + msg = get_translation('zombiesResult2', ['**', '`', count, users]) + + edit(message, msg) + sleep(2) + message.delete() + + send_log(get_translation('zombiesLog', ['**', '`', count, message.chat.title, message.chat.id])) + + @sedenify(incoming=True, outgoing=False, compat=False) def mute_check(client, message): muted = sql.is_muted(message.chat.id, message.from_user.id) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 1f08d0b..7cde209 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -1,6 +1,6 @@ { "added": "added", - "adminInfo": ".promote \nUsage: Provides admin rights to the person in the chat.\n\n.demote \nUsage: Revokes the person's admin permissions in the chat.\n\n.ban \nUsage: Bans the person off your chat.\n\n.unban \nUsage: Removes the ban from the person in the chat.\n\n.gban \nUsage: Bans the person in all groups you have in common with them.\n\n.ungban \nUsage: Removes the person from the gbanned list\n\n.mute \nUsage: Mutes the person in the chat.\n\n.unmute \nRemoves the person from the muted list.\n\n.gmute \nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute \nUsage: Removes the person from the gmuted list\n\n.kick \nUsage: Kicks user.\n\n.pin \nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.", + "adminInfo": ".promote \nUsage: Provides admin rights to the person in the chat.\n\n.demote \nUsage: Revokes the person's admin permissions in the chat.\n\n.ban \nUsage: Bans the person off your chat.\n\n.unban \nUsage: Removes the ban from the person in the chat.\n\n.gban \nUsage: Bans the person in all groups you have in common with them.\n\n.ungban \nUsage: Removes the person from the gbanned list\n\n.mute \nUsage: Mutes the person in the chat.\n\n.unmute \nRemoves the person from the muted list.\n\n.gmute \nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute \nUsage: Removes the person from the gmuted list\n\n.kick \nUsage: Kicks user.\n\n.pin \nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.", "adminUsage": "I'm not admin!", "adminlist": "%1Admins in%1 %2%3%2%1:%1", "afkEnd": "I'm no longer AFK.", @@ -613,5 +613,13 @@ "wrongMedia": "Wrong media type!", "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl \nUsage: Downloads video or audio from the link you provided.", - "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354" -} + "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354", + "zombiesError": "I don't have enough rights.", + "zombiesFind": "Finding deleted accounts...", + "zombiesFound": "%1Found%1 %2%3%2 %1deleted account.\nClean them by using%1 %2.zombies clean%2", + "zombiesLog": "#CLEANUP\n%1Removed%1 %2%3%2 %1deleted account.\nGROUP: %4%1 | %2%5%2", + "zombiesNoAccount": "No deleted account(s) found!", + "zombiesRemove": "Removing deleted account(s)...", + "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", + "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 121cb16..f46e20a 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -1,6 +1,6 @@ { "added": "eklendi", - "adminInfo": ".promote <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote \nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban \nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban \nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.gban \nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban \nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.mute \nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute \nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.gmute \nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute \nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.kick \nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin \nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.", + "adminInfo": ".promote <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote \nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban \nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban \nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.gban \nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban \nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.mute \nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute \nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.gmute \nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute \nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.kick \nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin \nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.", "adminUsage": "Y\u00f6netici de\u011filim!", "adminlist": "%2%3%2 %1sohbetinde bulunan y\u00f6neticiler:%1", "afkEnd": "Art\u0131k AFK de\u011filim.", @@ -613,5 +613,13 @@ "wrongMedia": "Ge\u00e7ersiz medya t\u00fcr\u00fc!", "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl \nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", - "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334" -} + "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334", + "zombiesError": "Yeterli yetkim bulunmamakta.", + "zombiesFind": "Silinen hesaplar bulunuyor...", + "zombiesFound": "%1Bu grupta%1 %2%3%2 %1adet silinen hesap bulundu.\nTemizlemek i\u00e7in%1 %2.zombies clean%2 %1komutunu kullan.%1", + "zombiesLog": "#TEMIZLIK\n%2%3%2 %1tane silinen hesap at\u0131ld\u0131.\nGRUP: %4%1 | %2%5%2", + "zombiesNoAccount": "Silinen hesap(lar) bulunamad\u0131!", + "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor...", + "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", + "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" +} \ No newline at end of file From 35fbd3550b6cb75e04b3c161f5f271bba0f0ef79 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Sat, 10 Apr 2021 18:38:11 +0300 Subject: [PATCH 026/242] seden: Misc changes Signed-off-by: NaytSeyd --- sedenbot/modules/ban.py | 5 ++++- sedenbot/modules/effects.py | 18 ++++++++---------- sedenbot/modules/profile.py | 4 ++-- sedenbot/modules/qrcode.py | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 3f758b7..cc94121 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -473,7 +473,10 @@ def zombie_accounts(client, message): sleep(2) message.delete() - send_log(get_translation('zombiesLog', ['**', '`', count, message.chat.title, message.chat.id])) + send_log( + get_translation( + 'zombiesLog', [ + '**', '`', count, message.chat.title, chat_id])) @sedenify(incoming=True, outgoing=False, compat=False) diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index de426a9..cef1818 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -85,10 +85,10 @@ def nightcore(message): media = download_media_wc(reply, file_name=nightcore) process = Popen(['ffmpeg', '-i', - f'{media}', - '-af', - 'asetrate=44100*1.16,aresample=44100,atempo=1', - f'{media}.mp3']) + f'{media}', + '-af', + 'asetrate=44100*1.16,aresample=44100,atempo=1', + f'{media}.mp3']) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') @@ -111,12 +111,10 @@ def slowedtoperfection(message): else: edit(message, f'`{get_translation("applySlowedtoperfection")}`') media = download_media_wc(reply, file_name=slowedtoperfection) - process = Popen(['ffmpeg', - '-i', - f'{media}', - '-af', - 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', - f'{media}.mp3']) + process = Popen( + ['ffmpeg', '-i', f'{media}', '-af', + 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', + f'{media}.mp3']) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index f3f5538..a30d465 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -71,8 +71,8 @@ def set_profilepic(client, message): image = Image.open(photo) width, height = image.size maxSize = (640, 640) - ratio = min(maxSize[0]/width, maxSize[1]/height) - image = image.resize((int(width*ratio), int(height*ratio))) + ratio = min(maxSize[0] / width, maxSize[1] / height) + image = image.resize((int(width * ratio), int(height * ratio))) new_photo = 'downloads/profile_photo_new.png' image.save(new_photo) client.set_profile_photo(photo=new_photo) diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index 056a826..f1493a1 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -19,6 +19,7 @@ from qrcode import QRCode, constants + @sedenify(pattern=r'^.decode$') def parseqr(message): reply = message.reply_to_message From a721d80fd16d987ecbe5487b399c0eacce915047 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Mon, 12 Apr 2021 14:45:35 +0300 Subject: [PATCH 027/242] seden: Add doviz command Signed-off-by: NaytSeyd --- sedenbot/modules/scrapers.py | 12 ++++++++++++ sedenecem/translator/tr.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 711c8f3..910f6aa 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -472,6 +472,18 @@ def lang(message): send_log(get_translation('scraperLog', ['`', scraper, LANG.title()])) +@sedenify(pattern='^.d[oö]viz') +def doviz(message): + page = BeautifulSoup(get('https://www.doviz.com/').content, 'html.parser') + res = page.find_all('div', {'class', 'item'}) + out = '**Güncel döviz kurları:**\n\n' + for i in res: + name = i.find('span', {'class': 'name'}).text + value = i.find('span', {'class': 'value'}).text + out += f'`•` **{name}:** `{value}`\n' + edit(message, out) + + @sedenify(pattern='^.currency') def currency(message): input_str = extract_args(message) diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index f46e20a..21797b6 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -111,7 +111,7 @@ "covidError": "Bir hata olu\u015ftu.", "cpUsage": "\ud83d\ude02Bana\ud83d\udcafBIR\u270c\ufe0fmE\ud83c\udd71\ufe0fIn\ud83d\udc50Ver\ud83d\udc4f", "currencyError": "Yazd\u0131\u011f\u0131n \u015fey uzayl\u0131lar\u0131n kulland\u0131\u011f\u0131 bir para birimine benziyor, bu y\u00fczden d\u00f6n\u00fc\u015ft\u00fcremiyorum.", - "currencyInfo": ".currency \nKullan\u0131m: Belirtilen para miktarlar\u0131n\u0131 d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.", + "currencyInfo": ".currency \nKullan\u0131m: Belirtilen para miktarlar\u0131n\u0131 d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n\n.d\u00f6viz\nKullan\u0131m: G\u00fcncel d\u00f6viz kurlar\u0131n\u0131 getirir.", "ddgoDesc": "A\u00e7\u0131klama sa\u011flanmam\u0131\u015f", "ddgoInfo": ".ddgo \nKullan\u0131m: H\u0131zl\u0131 bir DuckDuckGo aramas\u0131 yapar.", "ddgoLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla DuckDuckGo'da arat\u0131ld\u0131!", From 424aab1e2fc3d497adf599ac47b0b9560ad9ad62 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Wed, 14 Apr 2021 20:55:17 +0300 Subject: [PATCH 028/242] seden: Add stats command Signed-off-by: NaytSeyd --- sedenbot/modules/profile.py | 38 ++++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 40 insertions(+) diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index a30d465..7bfa241 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -195,4 +195,42 @@ def online(client, message): return +@sedenify(pattern='^.stats$', compat=False) +def user_stats(client, message): + edit(message, f'`{get_translation("processing")}`') + chats = 0 + channels = 0 + groups = 0 + sgroups = 0 + pms = 0 + bots = 0 + unread = 0 + user = [] + for i in client.iter_dialogs(): + chats += 1 + if i.chat.type == 'channel': + channels += 1 + elif i.chat.type == 'group': + groups += 1 + elif i.chat.type == 'supergroup': + sgroups += 1 + else: + pms += 1 + user.append(i.chat.id) + + if i.unread_messages_count > 0: + unread += 1 + + users = client.get_users(user) + for i in users: + if i.is_bot: + bots += 1 + + edit( + message, + get_translation( + 'statsResult', + ['**', '`', chats, channels, groups, sgroups, bots, pms, unread])) + + HELP.update({'profile': get_translation('profileInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 7cde209..6b3e886 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -516,6 +516,7 @@ "ssResult": "Generating screenshot of the page...\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", "ssUpload": "Uploading screenshot ...", "ssUsage": "You must provide a valid link before I can take a screenshot.", + "statsResult": "%1Total Chats:%1 %2%3%2\n%1Channels:%1 %2%4%2\n%1Groups:%1 %2%5%2\n%1Super Groups:%1 %2%6%2\n%1Bots:%1 %2%7%2\n%1PM's:%1 %2%8%2\n%1Unread Messages:%1 %2%9%2", "statusLong": "A long time ago", "statusMonth": "Within the last month", "statusOnline": "Online", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 21797b6..5e5b818 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -516,6 +516,7 @@ "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor...\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", "ssUpload": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc kar\u015f\u0131ya y\u00fckleniyor ...", "ssUsage": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc alabilmem i\u00e7in ge\u00e7erli bir ba\u011flant\u0131 vermelisin.", + "statsResult": "%1Toplam Sohbet:%1 %2%3%2\n%1Kanallar:%1 %2%4%2\n%1Gruplar:%1 %2%5%2\n%1S\u00fcper Gruplar:%1 %2%6%2\n%1Botlar:%1 %2%7%2\n%1\u00d6zel Mesajlar:%1 %2%8%2\n%1Okunmam\u0131\u015f Mesajlar:%1 %2%9%2", "statusLong": "Uzun zaman \u00f6nce", "statusMonth": "Bir ay i\u00e7inde", "statusOnline": "\u00c7evrimi\u00e7i", From 304d8b26ade9179108b74d792ebe9d39a0b9c7e9 Mon Sep 17 00:00:00 2001 From: NaytSeyd Date: Wed, 14 Apr 2021 20:58:14 +0300 Subject: [PATCH 029/242] seden: Fix currency * Also python version updated to 3.9.4 Signed-off-by: NaytSeyd --- requirements.txt | 1 + sedenbot/modules/scrapers.py | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/requirements.txt b/requirements.txt index dc2d6ab..65362f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ bs4 coffeehouse cowpy +currencyconverter deethon emoji gitpython diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 910f6aa..bb83798 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -24,6 +24,7 @@ from requests import get from bs4 import BeautifulSoup +from currency_converter import CurrencyConverter from pyrogram.types import InputMediaPhoto from sedenbot import HELP, SEDEN_LANG @@ -488,26 +489,23 @@ def doviz(message): def currency(message): input_str = extract_args(message) input_sgra = input_str.split(' ') + currency = CurrencyConverter() + if len(input_sgra) == 3: try: number = float(input_sgra[0]) currency_from = input_sgra[1].upper() currency_to = input_sgra[2].upper() - request_url = f'https://api.exchangeratesapi.io/latest?base={currency_from}' - current_response = get(request_url).json() - if currency_to in current_response['rates']: - current_rate = float(current_response['rates'][currency_to]) - rebmun = round(number * current_rate, 2) - edit( - message, - f'**{number} {currency_from} = {rebmun} {currency_to}**') - else: - edit(message, get_translation('currencyError')) + convert = currency.convert(number, currency_from, currency_to) + out = round(number * convert, 2) + edit( + message, + f'**{number} {currency_from} = {out} {currency_to}**') except Exception as e: edit(message, str(e)) else: - edit(message, get_translation('syntaxError')) - return + edit(message, f'`{get_translation("syntaxError")}`') + return HELP.update({'img': get_translation('imgInfo')}) From ab7c74752b9e07fe4e73a1d550daee96c06b6e7c Mon Sep 17 00:00:00 2001 From: frknkrc44 Date: Fri, 16 Apr 2021 02:35:45 +0300 Subject: [PATCH 030/242] Some optimizations --- seden.py | 3 +- sedenbot/__init__.py | 234 ++++++++++--------- sedenbot/modules/afk.py | 216 +++++++++++------- sedenbot/modules/android.py | 419 +++++++++++++++++++---------------- sedenbot/modules/ban.py | 266 ++++++++++++---------- sedenbot/modules/pmpermit.py | 83 ++++--- sedenbot/modules/stickers.py | 150 +++++++------ sedenbot/modules/updater.py | 96 ++++---- sedenecem/core/misc.py | 89 ++++---- sedenecem/core/sedenify.py | 108 +++++---- 10 files changed, 933 insertions(+), 731 deletions(-) diff --git a/seden.py b/seden.py index efa99a2..f2debf4 100644 --- a/seden.py +++ b/seden.py @@ -7,6 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -if __name__ == '__main__': +if __name__ == "__main__": from sedenbot import app + app.run() diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index cb8c844..9a4c389 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -7,6 +7,12 @@ # All rights reserved. See COPYING, AUTHORS. # +from os import name + +if name == "nt": + print("Uninstall Windows to use this bot") + quit(1) + from sqlite3 import connect from sys import version_info from os.path import isfile @@ -15,7 +21,9 @@ from distutils.util import strtobool as sb from importlib import import_module from logging import basicConfig, getLogger, INFO, DEBUG, CRITICAL +from pathlib import PurePath from traceback import format_exc +from typing import Any, Dict from requests import get from dotenv import load_dotenv, set_key, unset_key @@ -26,7 +34,7 @@ def reload_env(): - return load_dotenv('config.env', override=True) + return load_dotenv("config.env", override=True) reload_env() @@ -39,31 +47,30 @@ def get_translation(transKey, params: list = None): if params and len(params) > 0: for i in reversed(range(len(params))): - ret = ret.replace(f'%{i+1}', str(params[i])) + ret = ret.replace(f"%{i+1}", str(params[i])) - ret = ret.replace('½', '%') + ret = ret.replace("½", "%") return ret if version_info[0] < 3 or version_info[1] < 8: - LOGS.warn(get_translation('pythonVersionError')) + LOGS.warn(get_translation("pythonVersionError")) quit(1) -HELP = {} +HELP: Dict[str, str] = {} BRAIN = [] BLACKLIST = [] -VALID_PROXY_URL = [] -CONVERSATION = {} -PM_COUNT = {} -PM_LAST_MSG = {} -TEMP_SETTINGS = {} +CONVERSATION: Dict[Any, Any] = {} +PM_COUNT: Dict[Any, int] = {} +PM_LAST_MSG: Dict[Any, Any] = {} +TEMP_SETTINGS: Dict[Any, Any] = {} # Console verbose logging -LOG_VERBOSE = sb(environ.get('LOG_VERBOSE', 'False')) +LOG_VERBOSE = sb(environ.get("LOG_VERBOSE", "False")) basicConfig( - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=DEBUG if LOG_VERBOSE else INFO, ) @@ -71,29 +78,29 @@ def get_translation(transKey, params: list = None): # Bot lang # # If missted, the default lang is English. -SEDEN_LANG = environ.get('SEDEN_LANG', 'en') +SEDEN_LANG = environ.get("SEDEN_LANG", "en") def set_local_env(key: str, value: str): - return set_key('config.env', key, value) + return set_key(PurePath("config.env"), key, value) def unset_local_env(key: str): if key in environ: del environ[key] - return unset_key('config.env', key) + return unset_key(PurePath("config.env"), key) def set_logger(): # Turns off out printing Session value - pyrogram_syncer = getLogger('pyrogram.syncer') + pyrogram_syncer = getLogger("pyrogram.syncer") pyrogram_syncer.setLevel(CRITICAL) # Closes some junk outputs - pyrogram_session = getLogger('pyrogram.session.session') + pyrogram_session = getLogger("pyrogram.session.session") pyrogram_session.setLevel(CRITICAL) - pyrogram_auth = getLogger('pyrogram.session.auth') + pyrogram_auth = getLogger("pyrogram.session.auth") pyrogram_auth.setLevel(CRITICAL) @@ -101,83 +108,80 @@ def set_logger(): # Check that the config is edited using the previously used variable. # Basically, check for config file. -CONFIG_CHECK = environ.get( - '___________DELETE_______THIS_____LINE__________', None) - -if CONFIG_CHECK: - LOGS.warn(get_translation('removeFirstLine')) +if environ.get("___________DELETE_______THIS_____LINE__________", None): + LOGS.warn(get_translation("removeFirstLine")) quit(1) # Telegram APP ID and HASH -API_ID = environ.get('API_ID', None) +API_ID = environ.get("API_ID", None) if not API_ID: - LOGS.warn(get_translation('apiIdError')) + LOGS.warn(get_translation("apiIdError")) quit(1) -API_HASH = environ.get('API_HASH', None) +API_HASH = environ.get("API_HASH", None) if not API_HASH: - LOGS.warn(get_translation('apiHashError')) + LOGS.warn(get_translation("apiHashError")) quit(1) -BOT_VERSION = '1.4.3 Beta' -SUPPORT_GROUP = 'SedenUserBotSupport' -CHANNEL = 'SedenUserBot' +BOT_VERSION = "1.4.3 Beta" +SUPPORT_GROUP = "SedenUserBotSupport" +CHANNEL = "SedenUserBot" # Weather default city -WEATHER = environ.get('WEATHER', None) +WEATHER = environ.get("WEATHER", None) # Genius module -GENIUS_TOKEN = environ.get('GENIUS_TOKEN', None) or environ.get('GENIUS', None) +GENIUS_TOKEN = environ.get("GENIUS_TOKEN", None) or environ.get("GENIUS", None) # Lydia API -LYDIA_APIKEY = environ.get('LYDIA_APIKEY', None) +LYDIA_APIKEY = environ.get("LYDIA_APIKEY", None) # Change Alive Message -ALIVE_MSG = environ.get('ALIVE_MSG', None) +ALIVE_MSG = environ.get("ALIVE_MSG", None) # For neofetch -HOSTNAME = environ.get('HOSTNAME', 'DerUntergang') -USER = environ.get('USER', 'sedenecem') +HOSTNAME = environ.get("HOSTNAME", "DerUntergang") +USER = environ.get("USER", "sedenecem") # Chrome Driver and Headless Google Chrome Binaries -CHROME_DRIVER = environ.get('CHROME_DRIVER', 'chromedriver') +CHROME_DRIVER = environ.get("CHROME_DRIVER", "chromedriver") # OCR API key -OCR_APIKEY = environ.get('OCR_APIKEY', None) +OCR_APIKEY = environ.get("OCR_APIKEY", None) # Auto pp link -AUTO_PP = environ.get('AUTO_PP', None) +AUTO_PP = environ.get("AUTO_PP", None) # RBG API key -RBG_APIKEY = environ.get('RBG_APIKEY', None) +RBG_APIKEY = environ.get("RBG_APIKEY", None) # Custom sticker pack -PACKNAME = environ.get('PACKNAME', None) -PACKNICK = environ.get('PACKNICK', None) +PACKNAME = environ.get("PACKNAME", None) +PACKNICK = environ.get("PACKNICK", None) # Deezer ARL Token -DEEZER_TOKEN = environ.get('DEEZER_TOKEN', None) +DEEZER_TOKEN = environ.get("DEEZER_TOKEN", None) # SQL Database URL -DATABASE_URL = environ.get('DATABASE_URL', None) +DATABASE_URL = environ.get("DATABASE_URL", None) # Download directory -DOWNLOAD_DIRECTORY = environ.get('DOWNLOAD_DIRECTORY', './downloads') +DOWNLOAD_DIRECTORY = environ.get("DOWNLOAD_DIRECTORY", "./downloads") # SedenBot Session -SESSION = environ.get('SESSION', 'sedenuserbot') +SESSION = environ.get("SESSION", "sedenuserbot") # SedenBot repo url for updater -REPO_URL = environ.get( - 'REPO_URL', 'https://github.com/TeamDerUntergang/SedenUserBot') +REPO_URL = environ.get("REPO_URL", "https://github.com/TeamDerUntergang/SedenUserBot") # Heroku Credentials for updater -HEROKU_KEY = environ.get('HEROKU_KEY', None) -HEROKU_APPNAME = environ.get('HEROKU_APPNAME', None) +HEROKU_KEY = environ.get("HEROKU_KEY", None) +HEROKU_APPNAME = environ.get("HEROKU_APPNAME", None) # Chat ID for Bot Logs -LOG_ID = environ.get('LOG_ID', None) -LOG_ID = int(LOG_ID) if LOG_ID and resr(r'^-?\d+$', LOG_ID) else None +_LOG_ID = environ.get("LOG_ID", None) +LOG_ID = int(_LOG_ID) if _LOG_ID and resr(r"^-?\d+$", _LOG_ID) else None +del _LOG_ID # Connect to the test server # @@ -188,65 +192,68 @@ def set_logger(): # Also known as Deep Telegram # # For more information: https://docs.pyrogram.org/topics/test-servers -DEEPGRAM = sb(environ.get('DEEPGRAM', 'False')) +DEEPGRAM = sb(environ.get("DEEPGRAM", "False")) # PmPermit PM Auto Ban Stuffs -PM_AUTO_BAN = sb(environ.get('PM_AUTO_BAN', 'False')) -PM_MSG_COUNT = environ.get('PM_MSG_COUNT', 'default') -PM_MSG_COUNT = int(PM_MSG_COUNT) if PM_MSG_COUNT.isdigit() else 5 -PM_UNAPPROVED = environ.get('PM_UNAPPROVED', None) +PM_AUTO_BAN = sb(environ.get("PM_AUTO_BAN", "False")) +_PM_MSG_COUNT = environ.get("PM_MSG_COUNT", "default") +PM_MSG_COUNT = int(_PM_MSG_COUNT) if _PM_MSG_COUNT.isdigit() else 5 +del _PM_MSG_COUNT +PM_UNAPPROVED = environ.get("PM_UNAPPROVED", None) # Bot Prefix (Defaults to dot) -BOT_PREFIX = environ.get('BOT_PREFIX', None) +BOT_PREFIX = environ.get("BOT_PREFIX", None) -ENV_RESTRICTED_KEYS = [ - 'HEROKU_KEY', - 'HEROKU_APPNAME', - 'SESSION', - 'API_ID', - 'API_HASH'] +ENV_RESTRICTED_KEYS = ["HEROKU_KEY", "HEROKU_APPNAME", "SESSION", "API_ID", "API_HASH"] def load_brain(): - if path.exists('learning-data-root.check'): - remove('learning-data-root.check') - URL = 'https://raw.githubusercontent.com/NaytSeyd/'\ - 'databasescape/master/learning-data-root.check' - with open('learning-data-root.check', 'wb') as load: - load.write(get(URL).content) - DB = connect('learning-data-root.check') - CURSOR = DB.cursor() - CURSOR.execute('SELECT * FROM BRAIN1') - ALL_ROWS = CURSOR.fetchall() - for i in ALL_ROWS: - BRAIN.append(i[0]) - DB.close() + try: + if path.exists("learning-data-root.check"): + remove("learning-data-root.check") + URL = ( + "https://raw.githubusercontent.com/NaytSeyd/" + "databasescape/master/learning-data-root.check" + ) + with open("learning-data-root.check", "wb") as load: + load.write(get(URL).content) + DB = connect("learning-data-root.check") + CURSOR = DB.cursor() + CURSOR.execute("SELECT * FROM BRAIN1") + ALL_ROWS = CURSOR.fetchall() + for i in ALL_ROWS: + BRAIN.append(i[0]) + DB.close() + except BaseException: + pass def load_bl(): - if path.exists('blacklist.check'): - remove('blacklist.check') - URL = 'https://raw.githubusercontent.com/NaytSeyd/'\ - 'databaseblacklist/master/blacklist.check' - with open('blacklist.check', 'wb') as load: - load.write(get(URL).content) - DB = connect('blacklist.check') - CURSOR = DB.cursor() - CURSOR.execute('SELECT * FROM RETARDS') - ALL_ROWS = CURSOR.fetchall() - for i in ALL_ROWS: - BLACKLIST.append(i[0]) - DB.close() + try: + if path.exists("blacklist.check"): + remove("blacklist.check") + URL = ( + "https://raw.githubusercontent.com/NaytSeyd/" + "databaseblacklist/master/blacklist.check" + ) + with open("blacklist.check", "wb") as load: + load.write(get(URL).content) + DB = connect("blacklist.check") + CURSOR = DB.cursor() + CURSOR.execute("SELECT * FROM RETARDS") + ALL_ROWS = CURSOR.fetchall() + for i in ALL_ROWS: + BLACKLIST.append(i[0]) + DB.close() + except BaseException: + pass load_brain() load_bl() -me = [] - class PyroClient(Client): - @staticmethod def store_msg(_, message): try: @@ -261,45 +268,54 @@ def store_msg(_, message): def __init__(self, session, **args): super().__init__(session, **args) - self.add_handler(MessageHandler( - PyroClient.store_msg, filters.incoming)) + self.add_handler(MessageHandler(PyroClient.store_msg, filters.incoming)) + + def export_session_string(self): + raise NotImplementedError app = PyroClient( SESSION, api_id=API_ID, api_hash=API_HASH, - app_version=f'Seden UserBot', - device_model='DerUntergang', - system_version=f'v{BOT_VERSION}', - lang_code='tr', - test_mode=DEEPGRAM + app_version=f"Seden UserBot", + device_model="DerUntergang", + system_version=f"v{BOT_VERSION}", + lang_code="tr", + test_mode=DEEPGRAM, ) +# delete these variables to add some security +del SESSION +del API_ID +del API_HASH + + def __get_modules(): - folder = 'sedenbot/modules' + folder = "sedenbot/modules" modules = [ - f[:-3] for f in listdir(folder) - if isfile(f'{folder}/{f}') and f[-3:] == '.py' and f != '__init__.py' + f[:-3] + for f in listdir(folder) + if isfile(f"{folder}/{f}") and f[-3:] == ".py" and f != "__init__.py" ] return modules def __import_modules(): modules = sorted(__get_modules()) - LOGS.info(get_translation('loadedModules', [modules])) + LOGS.info(get_translation("loadedModules", [modules])) for module in modules: try: - LOGS.info(get_translation('loadedModules2', [module])) - import_module(f'sedenbot.modules.{module}') + LOGS.info(get_translation("loadedModules2", [module])) + import_module(f"sedenbot.modules.{module}") except Exception: if LOG_VERBOSE: LOGS.warn(format_exc()) - LOGS.warn(get_translation('loadedModulesError', [module])) + LOGS.warn(get_translation("loadedModulesError", [module])) __import_modules() -LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) -LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) +LOGS.info(get_translation("runningBot", [SUPPORT_GROUP])) +LOGS.info(get_translation("sedenVersion", [BOT_VERSION])) diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index ee713d9..79dfe69 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -12,149 +12,197 @@ from pyrogram import ContinuePropagation, StopPropagation -from sedenbot import (HELP, TEMP_SETTINGS, PM_AUTO_BAN, app, me as mel) -from sedenecem.core import (extract_args, sedenify, send_log, - edit, reply, get_translation) +from sedenbot import HELP, TEMP_SETTINGS, PM_AUTO_BAN, app +from sedenecem.core import ( + extract_args, + sedenify, + send_log, + edit, + reply, + get_translation, +) # ========================= CONSTANTS ============================ -AFKSTR = [get_translation(f'afkstr{i+1}') for i in range(0, 22)] -TEMP_SETTINGS['AFK_USERS'] = {} -TEMP_SETTINGS['IS_AFK'] = False -TEMP_SETTINGS['COUNT_MSG'] = 0 +AFKSTR = [get_translation(f"afkstr{i+1}") for i in range(0, 22)] +TEMP_SETTINGS["AFK_USERS"] = {} +TEMP_SETTINGS["IS_AFK"] = False +TEMP_SETTINGS["COUNT_MSG"] = 0 # ================================================================= -@sedenify(incoming=True, outgoing=False, disable_edited=True, - private=False, bot=False, disable_notify=True) +@sedenify( + incoming=True, + outgoing=False, + disable_edited=True, + private=False, + bot=False, + disable_notify=True, +) def mention_afk(msg): - me = mel[0] + me = TEMP_SETTINGS["ME"] mentioned = msg.mentioned rep_m = msg.reply_to_message if mentioned or rep_m and rep_m.from_user and rep_m.from_user.id == me.id: - if TEMP_SETTINGS['IS_AFK']: - if msg.from_user.id not in TEMP_SETTINGS['AFK_USERS']: - if 'AFK_REASON' in TEMP_SETTINGS: + if TEMP_SETTINGS["IS_AFK"]: + if msg.from_user.id not in TEMP_SETTINGS["AFK_USERS"]: + if "AFK_REASON" in TEMP_SETTINGS: reply( - msg, get_translation( - "afkMessage2", [ - '**', me.first_name, me.id, - '`', TEMP_SETTINGS['AFK_REASON']])) + msg, + get_translation( + "afkMessage2", + [ + "**", + me.first_name, + me.id, + "`", + TEMP_SETTINGS["AFK_REASON"], + ], + ), + ) else: reply(msg, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS['AFK_USERS'].update({msg.from_user.id: 1}) - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"].update({msg.from_user.id: 1}) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 else: - if TEMP_SETTINGS['AFK_USERS'][ - msg.from_user.id] % randint( - 2, 4) == 0: - if 'AFK_REASON' in TEMP_SETTINGS: + if TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] % randint(2, 4) == 0: + if "AFK_REASON" in TEMP_SETTINGS: reply( - msg, get_translation( - "afkMessage2", [ - '**', me.first_name, me.id, - '`', TEMP_SETTINGS['AFK_REASON']])) + msg, + get_translation( + "afkMessage2", + [ + "**", + me.first_name, + me.id, + "`", + TEMP_SETTINGS["AFK_REASON"], + ], + ), + ) else: reply(msg, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS['AFK_USERS'][ - msg.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - msg.from_user.id] + 1 - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] = ( + TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] + 1 + ) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 else: - TEMP_SETTINGS['AFK_USERS'][ - msg.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - msg.from_user.id] + 1 - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] = ( + TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] + 1 + ) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 raise ContinuePropagation -@sedenify(incoming=True, outgoing=False, disable_errors=True, - group=False, bot=False, disable_notify=True) +@sedenify( + incoming=True, + outgoing=False, + disable_errors=True, + group=False, + bot=False, + disable_notify=True, +) def afk_on_pm(message): if PM_AUTO_BAN: try: from sedenecem.sql.pm_permit_sql import is_approved + apprv = is_approved(message.from_user.id) except BaseException: apprv = True else: apprv = True - if apprv and TEMP_SETTINGS['IS_AFK']: - me = mel[0] - if message.from_user.id not in TEMP_SETTINGS['AFK_USERS']: - if 'AFK_REASON' in TEMP_SETTINGS: + if apprv and TEMP_SETTINGS["IS_AFK"]: + me = TEMP_SETTINGS["ME"] + if message.from_user.id not in TEMP_SETTINGS["AFK_USERS"]: + if "AFK_REASON" in TEMP_SETTINGS: reply( - message, get_translation( - "afkMessage2", [ - '**', me.first_name, me.id, - '`', TEMP_SETTINGS['AFK_REASON']])) + message, + get_translation( + "afkMessage2", + ["**", me.first_name, me.id, "`", TEMP_SETTINGS["AFK_REASON"]], + ), + ) else: reply(message, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS['AFK_USERS'].update({message.from_user.id: 1}) - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"].update({message.from_user.id: 1}) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 else: - if TEMP_SETTINGS['AFK_USERS'][ - message.from_user.id] % randint( - 2, 4) == 0: - if 'AFK_REASON' in TEMP_SETTINGS: + if TEMP_SETTINGS["AFK_USERS"][message.from_user.id] % randint(2, 4) == 0: + if "AFK_REASON" in TEMP_SETTINGS: reply( - message, get_translation( - "afkMessage2", [ - '**', me.first_name, me.id, - '`', TEMP_SETTINGS['AFK_REASON']])) + message, + get_translation( + "afkMessage2", + [ + "**", + me.first_name, + me.id, + "`", + TEMP_SETTINGS["AFK_REASON"], + ], + ), + ) else: reply(message, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS['AFK_USERS'][ - message.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - message.from_user.id] + 1 - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"][message.from_user.id] = ( + TEMP_SETTINGS["AFK_USERS"][message.from_user.id] + 1 + ) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 else: - TEMP_SETTINGS['AFK_USERS'][ - message.from_user.id] = TEMP_SETTINGS['AFK_USERS'][ - message.from_user.id] + 1 - TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 + TEMP_SETTINGS["AFK_USERS"][message.from_user.id] = ( + TEMP_SETTINGS["AFK_USERS"][message.from_user.id] + 1 + ) + TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 raise ContinuePropagation -@sedenify(pattern=r'^.afk') +@sedenify(pattern=r"^.afk") def set_afk(message): args = extract_args(message) if len(args) > 0: - TEMP_SETTINGS['AFK_REASON'] = args + TEMP_SETTINGS["AFK_REASON"] = args edit( - message, get_translation( - 'afkStartReason', [ - '**', '`', TEMP_SETTINGS['AFK_REASON']])) + message, + get_translation("afkStartReason", ["**", "`", TEMP_SETTINGS["AFK_REASON"]]), + ) else: edit(message, f'**{get_translation("afkStart")}**') - send_log(get_translation('afkLog')) - TEMP_SETTINGS['IS_AFK'] = True + send_log(get_translation("afkLog")) + TEMP_SETTINGS["IS_AFK"] = True raise StopPropagation @sedenify() def type_afk_is_not_true(message): - if TEMP_SETTINGS['IS_AFK']: - TEMP_SETTINGS['IS_AFK'] = False + if TEMP_SETTINGS["IS_AFK"]: + TEMP_SETTINGS["IS_AFK"] = False reply(message, f'**{get_translation("afkEnd")}**') sleep(2) send_log( get_translation( - 'afkMessages', - ['`', '**', str(len(TEMP_SETTINGS['AFK_USERS'])), - str(TEMP_SETTINGS['COUNT_MSG'])])) - for i in TEMP_SETTINGS['AFK_USERS']: + "afkMessages", + [ + "`", + "**", + str(len(TEMP_SETTINGS["AFK_USERS"])), + str(TEMP_SETTINGS["COUNT_MSG"]), + ], + ) + ) + for i in TEMP_SETTINGS["AFK_USERS"]: name = app.get_chat(i) name0 = str(name.first_name) send_log( get_translation( - 'afkMentionUsers', - ['**', name0, str(i), - '`', str(TEMP_SETTINGS['AFK_USERS'][i])])) - TEMP_SETTINGS['COUNT_MSG'] = 0 - TEMP_SETTINGS['AFK_USERS'] = {} - if 'AFK_REASON' in TEMP_SETTINGS: - del TEMP_SETTINGS['AFK_REASON'] + "afkMentionUsers", + ["**", name0, str(i), "`", str(TEMP_SETTINGS["AFK_USERS"][i])], + ) + ) + TEMP_SETTINGS["COUNT_MSG"] = 0 + TEMP_SETTINGS["AFK_USERS"] = {} + if "AFK_REASON" in TEMP_SETTINGS: + del TEMP_SETTINGS["AFK_REASON"] raise ContinuePropagation -HELP.update({'afk': get_translation('afkInfo')}) +HELP.update({"afk": get_translation("afkInfo")}) diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 55c352f..44e6bbc 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -14,24 +14,22 @@ from bs4 import BeautifulSoup from requests import get -from sedenbot import HELP, VALID_PROXY_URL +from sedenbot import HELP, TEMP_SETTINGS from sedenecem.core import edit, extract_args, sedenify, get_translation -GITHUB = 'https://github.com' +GITHUB = "https://github.com" -@sedenify(pattern='^.magisk$') +@sedenify(pattern="^.magisk$") def magisk(message): magisk_dict = { - 'Stable': - 'https://raw.githubusercontent.com/topjohnwu/' - 'magisk-files/master/stable.json', - 'Beta': - 'https://raw.githubusercontent.com/topjohnwu/' - 'magisk-files/master/beta.json', - 'Canary': - 'https://raw.githubusercontent.com/topjohnwu/' - 'magisk-files/master/canary.json'} + "Stable": "https://raw.githubusercontent.com/topjohnwu/" + "magisk-files/master/stable.json", + "Beta": "https://raw.githubusercontent.com/topjohnwu/" + "magisk-files/master/beta.json", + "Canary": "https://raw.githubusercontent.com/topjohnwu/" + "magisk-files/master/canary.json", + } releases = f'**{get_translation("magiskReleases")}**\n' for name, release_url in magisk_dict.items(): try: @@ -42,35 +40,37 @@ def magisk(message): edit(message, releases, preview=False) -@sedenify(pattern='^.phh') +@sedenify(pattern="^.phh") def phh(message): get_phh = get( - 'https://api.github.com/repos/phhusson/treble_experimentations/releases/latest').json() + "https://api.github.com/repos/phhusson/treble_experimentations/releases/latest" + ).json() search = extract_args(message) - releases = '{}\n'.format( + releases = "{}\n".format( get_translation( - 'androidPhhHeader', [ - '`', "{} ".format(search) if len(search) > 0 else ""])) + "androidPhhHeader", ["`", "{} ".format(search) if len(search) > 0 else ""] + ) + ) count = 0 for i in range(len(get_phh)): try: - name = get_phh['assets'][i]['name'] - if not name.endswith('img.xz'): + name = get_phh["assets"][i]["name"] + if not name.endswith("img.xz"): continue elif search not in name: continue count += 1 - url = get_phh['assets'][i]['browser_download_url'] - releases += f'[{name}]({url})\n' + url = get_phh["assets"][i]["browser_download_url"] + releases += f"[{name}]({url})\n" except IndexError: continue if count < 1: - releases = get_translation('phhError', ['`', '**', search]) + releases = get_translation("phhError", ["`", "**", search]) edit(message, releases, preview=False) -@sedenify(pattern=r'^.device') +@sedenify(pattern=r"^.device") def device(message): textx = message.reply_to_message codename = extract_args(message) @@ -81,90 +81,101 @@ def device(message): else: edit(message, f'`{get_translation("deviceUsage")}`') return - data = loads(get('https://raw.githubusercontent.com/androidtrackers/' - 'certified-android-devices/master/by_device.json').text) + data = loads( + get( + "https://raw.githubusercontent.com/androidtrackers/" + "certified-android-devices/master/by_device.json" + ).text + ) results = data.get(codename) if results: - reply = "{}\n".format( - get_translation( - 'deviceSearch', [ - '**', codename])) + reply = "{}\n".format(get_translation("deviceSearch", ["**", codename])) for item in results: - reply += get_translation('deviceSearchResultChild', - ['**', item['brand'], - item['name'], - item['model']]) + reply += get_translation( + "deviceSearchResultChild", + ["**", item["brand"], item["name"], item["model"]], + ) else: - reply = get_translation('deviceError', ['`', codename]) + reply = get_translation("deviceError", ["`", codename]) edit(message, reply) -@sedenify(pattern=r'^.codename') +@sedenify(pattern=r"^.codename") def codename(message): textx = message.reply_to_message arr = extract_args(message) brand = arr device = arr - if ' ' in arr: - args = arr.split(' ', 1) + if " " in arr: + args = arr.split(" ", 1) brand = args[0].lower() device = args[1].lower() elif textx: - brand = textx.text.split(' ')[0] - device = ' '.join(textx.text.split(' ')[1:]) + brand = textx.text.split(" ")[0] + device = " ".join(textx.text.split(" ")[1:]) else: edit(message, f'`{get_translation("codenameUsage")}`') return - data = loads(get('https://raw.githubusercontent.com/androidtrackers/' - 'certified-android-devices/master/by_brand.json').text) + data = loads( + get( + "https://raw.githubusercontent.com/androidtrackers/" + "certified-android-devices/master/by_brand.json" + ).text + ) devices_lower = {k.lower(): v for k, v in data.items()} devices = devices_lower.get(brand) if not devices: - reply = get_translation('codenameError', ['`', device]) + reply = get_translation("codenameError", ["`", device]) else: - results = [i for i in devices if device.lower( - ) in i['name'].lower() or device.lower() in i['model'].lower()] + results = [ + i + for i in devices + if device.lower() in i["name"].lower() + or device.lower() in i["model"].lower() + ] if results: reply = f'{get_translation("codenameSearch", ["**", brand, device])}\n' if len(results) > 8: results = results[:8] for item in results: - reply += get_translation('codenameSearchResultChild', - ['**', item['device'], - item['name'], item['model']]) + reply += get_translation( + "codenameSearchResultChild", + ["**", item["device"], item["name"], item["model"]], + ) else: - reply = get_translation('codenameError', ['`', device]) + reply = get_translation("codenameError", ["`", device]) edit(message, reply) -@sedenify(pattern=r'^.twrp') +@sedenify(pattern=r"^.twrp") def twrp(message): textx = message.reply_to_message device = extract_args(message) if device: pass elif textx: - device = textx.text.split(' ')[0] + device = textx.text.split(" ")[0] else: edit(message, f'`{get_translation("twrpUsage")}`') return - url = get(f'https://dl.twrp.me/{device}/') + url = get(f"https://dl.twrp.me/{device}/") if url.status_code == 404: - reply = get_translation('twrpError', ['`', device]) + reply = get_translation("twrpError", ["`", device]) edit(message, reply) return - page = BeautifulSoup(url.content, 'html.parser') - download = page.find('table').find('tr').find('a') + page = BeautifulSoup(url.content, "html.parser") + download = page.find("table").find("tr").find("a") dl_link = f"https://dl.twrp.me{download['href']}" dl_file = download.text - size = page.find('span', {'class': 'filesize'}).text - date = page.find('em').text.strip() + size = page.find("span", {"class": "filesize"}).text + date = page.find("em").text.strip() reply = get_translation( - 'twrpResult', ['**', '__', device, dl_file, dl_link, size, date]) + "twrpResult", ["**", "__", device, dl_file, dl_link, size, date] + ) edit(message, reply) -@sedenify(pattern=r'^.o(range|)f(ox|rp)') +@sedenify(pattern=r"^.o(range|)f(ox|rp)") def ofox(message): if len(args := extract_args(message)) < 1: edit(message, f'`{get_translation("ofrpUsage")}`') @@ -177,11 +188,14 @@ def ofox(message): releases = ofrp_get_packages(args) if len(releases.releases) < 1: - edit(message, get_translation('ofrpNotFound', ['`', args, OFOX_REPO]), - preview=False) + edit( + message, + get_translation("ofrpNotFound", ["`", args, OFOX_REPO]), + preview=False, + ) return - out = '' + out = "" for release in releases.releases: out += f"[{release.version}{' (Beta)' if release.is_beta() else ''}]({release.file_url}) **{release.file_size}**\n" @@ -191,10 +205,10 @@ def ofox(message): edit(message, f'`{get_translation("ofrpError")}`') return - edit(message, f'**OrangeFox Recovery ({args}):**\n{out}') + edit(message, f"**OrangeFox Recovery ({args}):**\n{out}") -@sedenify(pattern=r'^.specs') +@sedenify(pattern=r"^.specs") def specs(message): args = extract_args(message) if len(args) < 1: @@ -210,75 +224,84 @@ def specs(message): edit(message, f'`{get_translation("specsError")}`') return - req = get(link, - headers={'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' - '+http://www.google.com/bot.html)'}, - proxies=proxy) - soup = BeautifulSoup(req.text, features='html.parser') - - def get_spec(query, key='data-spec', cls='td'): + req = get( + link, + headers={ + "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; " + "+http://www.google.com/bot.html)" + }, + proxies=proxy, + ) + soup = BeautifulSoup(req.text, features="html.parser") + + def get_spec(query, key="data-spec", cls="td"): try: result = soup.find(cls, {key: query.split()}).text.strip() - result = get_translation('specsError2') if len( - result) < 1 else result + result = get_translation("specsError2") if len(result) < 1 else result return result except BaseException: - return get_translation('specsError2') - - title = get_spec('specs-phone-name-title', 'class', 'h1') - launch = get_spec('released-hl', cls='span') - body = sub(', ', 'g, ', get_spec('body-hl', cls='span')) - os = get_spec('os-hl', cls='span') - storage = get_spec('internalmemory') - stortyp = get_spec('memoryother') - dispsize = get_spec('displaysize-hl', cls='span') - dispres = get_spec('displayres-hl', cls='div') - bcampx = get_spec('cam1modules') - bcamft = get_spec('cam1features') - bcamvd = get_spec('cam1video') - fcampx = get_spec('cam2modules') - fcamft = get_spec('cam2features') - fcamvd = get_spec('cam2video') - cpuname = get_spec('chipset') - cpuchip = get_spec('cpu') - gpuname = get_spec('gpu') - battery = get_spec('batdescription1') - wlan = get_spec('wlan') - bluetooth = get_spec('bluetooth') - gps = get_spec('gps') - sensors = get_spec('sensors') - sarus = sub(r'\s\s+', ', ', get_spec('sar-us')) - sareu = sub(r'\s\s+', ', ', get_spec('sar-eu')) - - edit(message, - get_translation('specsResult', - ['**', - '`', - title, - launch, - body, - sarus, - sareu, - os, - cpuname, - cpuchip, - gpuname, - storage, - stortyp, - dispsize, - dispres, - bcampx, - bcamft, - bcamvd, - fcampx, - fcamft, - fcamvd, - battery, - wlan, - bluetooth, - gps, - sensors, - link])) + return get_translation("specsError2") + + title = get_spec("specs-phone-name-title", "class", "h1") + launch = get_spec("released-hl", cls="span") + body = sub(", ", "g, ", get_spec("body-hl", cls="span")) + os = get_spec("os-hl", cls="span") + storage = get_spec("internalmemory") + stortyp = get_spec("memoryother") + dispsize = get_spec("displaysize-hl", cls="span") + dispres = get_spec("displayres-hl", cls="div") + bcampx = get_spec("cam1modules") + bcamft = get_spec("cam1features") + bcamvd = get_spec("cam1video") + fcampx = get_spec("cam2modules") + fcamft = get_spec("cam2features") + fcamvd = get_spec("cam2video") + cpuname = get_spec("chipset") + cpuchip = get_spec("cpu") + gpuname = get_spec("gpu") + battery = get_spec("batdescription1") + wlan = get_spec("wlan") + bluetooth = get_spec("bluetooth") + gps = get_spec("gps") + sensors = get_spec("sensors") + sarus = sub(r"\s\s+", ", ", get_spec("sar-us")) + sareu = sub(r"\s\s+", ", ", get_spec("sar-eu")) + + edit( + message, + get_translation( + "specsResult", + [ + "**", + "`", + title, + launch, + body, + sarus, + sareu, + os, + cpuname, + cpuchip, + gpuname, + storage, + stortyp, + dispsize, + dispres, + bcampx, + bcamft, + bcamvd, + fcampx, + fcamft, + fcamvd, + battery, + wlan, + bluetooth, + gps, + sensors, + link, + ], + ), + ) def find_device(query, proxy): @@ -286,56 +309,70 @@ def find_device(query, proxy): raw_query = query.lower() def replace_query(query): - return urlencode({'sSearch': query}) + return urlencode({"sSearch": query}) query = replace_query(raw_query) - req = get(f'https://www.gsmarena.com/res.php3?{query}', - headers={'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' - '+http://www.google.com/bot.html)'}, - proxies=proxy) - soup = BeautifulSoup(req.text, features='html.parser') - - if 'Too' in soup.find('title').text: # GSMArena geçici ban atarsa + req = get( + f"https://www.gsmarena.com/res.php3?{query}", + headers={ + "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; " + "+http://www.google.com/bot.html)" + }, + proxies=proxy, + ) + soup = BeautifulSoup(req.text, features="html.parser") + + if "Too" in soup.find("title").text: # GSMArena geçici ban atarsa return None - res = soup.findAll('div', {'class': ['makers']}) + res = soup.findAll("div", {"class": ["makers"]}) if not res or len(res) < 1: # hiçbir cihaz bulunamazsa return None - res = res[0].findAll('li') + res = res[0].findAll("li") for item in res: - name = str(item.find('span')) - name = sub('(<|', '', name) - if name[name.find('>') + 1:].lower() == raw_query or sub('|/>)', - ' ', name).lower() == raw_query: + name = str(item.find("span")) + name = sub("(<|", "", name) + if ( + name[name.find(">") + 1 :].lower() == raw_query + or sub("|/>)", " ", name).lower() == raw_query + ): link = f"https://www.gsmarena.com/{item.find('a')['href']}" return link return None +def get_stored_proxy(): + return TEMP_SETTINGS.get("VALID_PROXY_URL", "") + + +def put_stored_proxy(proxy): + TEMP_SETTINGS["VALID_PROXY_URL"] = proxy + + def _xget_random_proxy(): - try_valid = tuple(VALID_PROXY_URL[0].split(':')) if len( - VALID_PROXY_URL) > 0 else None + proxy = get_stored_proxy() + try_valid = tuple(proxy.split(":")) if len(proxy) else None if try_valid: valid = _try_proxy(try_valid) if valid[0] == 200 and "Too" not in valid[1]: return try_valid head = { - 'Accept-Encoding': 'gzip, deflate, sdch', - 'Accept-Language': 'en-US,en;q=0.8', - 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', - 'Referer': 'https://www.google.com/search?q=sslproxies', + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "User-Agent": "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)", + "Referer": "https://www.google.com/search?q=sslproxies", } - req = get('https://sslproxies.org/', headers=head) - soup = BeautifulSoup(req.text, 'html.parser') - res = soup.find('table', {'id': 'proxylisttable'}).find('tbody') - res = res.findAll('tr') + req = get("https://sslproxies.org/", headers=head) + soup = BeautifulSoup(req.text, "html.parser") + res = soup.find("table", {"id": "proxylisttable"}).find("tbody") + res = res.findAll("tr") for item in res: - infos = item.findAll('td') + infos = item.findAll("td") ip = infos[0].text port = infos[1].text proxy = (ip, port) @@ -347,9 +384,12 @@ def _xget_random_proxy(): def _try_proxy(proxy): try: - prxy = f'http://{proxy[0]}:{proxy[1]}' - req = get('https://www.gsmarena.com/', - proxies={'http': prxy, 'https': prxy}, timeout=1) + prxy = f"http://{proxy[0]}:{proxy[1]}" + req = get( + "https://www.gsmarena.com/", + proxies={"http": prxy, "https": prxy}, + timeout=1, + ) if req.status_code == 200: return (200, req.text) raise Exception @@ -361,13 +401,12 @@ def get_random_proxy(): proxy = _xget_random_proxy() if not proxy: return None - proxy = f'http://{proxy[0]}:{proxy[1]}' - VALID_PROXY_URL.clear() - VALID_PROXY_URL.append(proxy) + proxy = f"http://{proxy[0]}:{proxy[1]}" + put_stored_proxy(proxy) proxy_dict = { - 'https': proxy, - 'http': proxy, + "https": proxy, + "http": proxy, } return proxy_dict @@ -379,30 +418,30 @@ def __init__(self, json, releases): self.releases = [] return - self.codename = json['codename'] - self.oem = json['oem_name'] - self.model = json['model_name'] - self.maintainer_name = json['maintainer']['name'] - self.maintainer_username = json['maintainer']['username'] + self.codename = json["codename"] + self.oem = json["oem_name"] + self.model = json["model_name"] + self.maintainer_name = json["maintainer"]["name"] + self.maintainer_username = json["maintainer"]["username"] self.releases = releases class OFRPRelease: def __init__(self, json): - self.id = json['_id'] - self.type = json['type'] - self.device = json['device_id'] + self.id = json["_id"] + self.type = json["type"] + self.device = json["device_id"] - date = datetime.utcfromtimestamp( - int(json['date'])).strftime('%d-%m-%Y %H:%M:%S') + date = datetime.utcfromtimestamp(int(json["date"])).strftime( + "%d-%m-%Y %H:%M:%S" + ) self.date = date - self.file_size = '{:,.2f} MB'.format( - int(json['size']) / float(1 << 20)) - self.md5 = json['md5'] - self.version = json['version'] + self.file_size = "{:,.2f} MB".format(int(json["size"]) / float(1 << 20)) + self.md5 = json["md5"] + self.version = json["version"] - url = f'https://api.orangefox.download/v3/releases/{self.id}' + url = f"https://api.orangefox.download/v3/releases/{self.id}" res = ofrp_get(url) if not res: @@ -412,23 +451,23 @@ def __init__(self, json): json = loads(res) - self.file_name = json['filename'] - self.file_url = list(json['mirrors'].values())[0] - self.changelog = '\n'.join(json['changelog']) + self.file_name = json["filename"] + self.file_url = list(json["mirrors"].values())[0] + self.changelog = "\n".join(json["changelog"]) def is_beta(self): - return self.type == 'beta' + return self.type == "beta" def ofrp_get(url): try: head = { - 'Accept-Language': 'en-US,en;q=0.8', - 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', - 'Referer': 'https://orangefox.download/en', + "Accept-Language": "en-US,en;q=0.8", + "User-Agent": "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)", + "Referer": "https://orangefox.download/en", } req = get(url, headers=head) - if '{' not in req.text: + if "{" not in req.text: raise BaseException return req.text except BaseException: @@ -436,7 +475,7 @@ def ofrp_get(url): def ofrp_get_packages(device): - url = f'https://api.orangefox.download/v3/devices/get?codename={device}' + url = f"https://api.orangefox.download/v3/devices/get?codename={device}" res = ofrp_get(url) if not res: @@ -444,7 +483,7 @@ def ofrp_get_packages(device): json = loads(res) - if '_id' not in json: + if "_id" not in json: return OFRPDeviceInfo(None, None) url = f'https://api.orangefox.download/v3/releases/?device_id={json["_id"]}' @@ -453,10 +492,10 @@ def ofrp_get_packages(device): out = [] json2 = loads(res) - json2 = json2['data'] + json2 = json2["data"] - stables = [x for x in json2 if x['type'] == 'stable'] - betas = [x for x in json2 if x['type'] == 'beta'] + stables = [x for x in json2 if x["type"] == "stable"] + betas = [x for x in json2 if x["type"] == "beta"] if len(stables): stable = [OFRPRelease(x) for x in stables] @@ -469,4 +508,4 @@ def ofrp_get_packages(device): return OFRPDeviceInfo(json, out) -HELP.update({'android': get_translation('androidInfo')}) +HELP.update({"android": get_translation("androidInfo")}) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index cc94121..916681a 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -7,18 +7,26 @@ # All rights reserved. See COPYING, AUTHORS. # +from dotenv import compat +from sedenbot.modules.admin.helpers import is_admin from time import sleep from pyrogram.types import ChatPermissions from pyrogram.errors import MessageTooLong, UserAdminInvalid from sedenbot import HELP, BRAIN -from sedenecem.core import (edit, sedenify, send_log, reply_doc, - extract_args, get_translation) +from sedenecem.core import ( + edit, + sedenify, + send_log, + reply_doc, + extract_args, + get_translation, +) from sedenecem.sql import mute_sql as sql -@sedenify(pattern='^.ban', compat=False, private=False, admin=True) +@sedenify(pattern="^.ban", compat=False, private=False, admin=True) def ban_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -45,29 +53,29 @@ def ban_user(client, message): if user.id in BRAIN: return edit( - message, get_translation( - 'brainError', [ - '`', '**', user.first_name, user.id])) + message, + get_translation("brainError", ["`", "**", user.first_name, user.id]), + ) try: chat_id = message.chat.id client.kick_chat_member(chat_id, user.id) edit( - message, get_translation( - 'banResult', [ - '**', user.first_name, user.id, '`'])) + message, get_translation("banResult", ["**", user.first_name, user.id, "`"]) + ) sleep(1) send_log( get_translation( - 'banLog', - ['**', user.first_name, user.id, message.chat.title, - '`', chat_id])) + "banLog", + ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + ) + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.unban', compat=False, private=False, admin=True) +@sedenify(pattern="^.unban", compat=False, private=False, admin=True) def unban_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -96,15 +104,15 @@ def unban_user(client, message): chat_id = message.chat.id client.unban_chat_member(chat_id, user.id) edit( - message, get_translation( - 'unbanResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("unbanResult", ["**", user.first_name, user.id, "`"]), + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.kick', compat=False, private=False, admin=True) +@sedenify(pattern="^.kick", compat=False, private=False, admin=True) def kick_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -131,29 +139,38 @@ def kick_user(client, message): if user.id in BRAIN: return edit( - message, get_translation( - 'brainError', [ - '`', '**', user.first_name, user.id])) + message, + get_translation("brainError", ["`", "**", user.first_name, user.id]), + ) try: chat_id = message.chat.id client.kick_chat_member(chat_id, user.id) client.unban_chat_member(chat_id, user.id) edit( - message, get_translation( - 'kickResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("kickResult", ["**", user.first_name, user.id, "`"]), + ) sleep(1) send_log( get_translation( - 'kickLog', [ - '**', user.first_name, user.id, message.chat.title, '`', message.chat.id])) + "kickLog", + [ + "**", + user.first_name, + user.id, + message.chat.title, + "`", + message.chat.id, + ], + ) + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.mute', compat=False, private=False, admin=True) +@sedenify(pattern="^.mute", compat=False, private=False, admin=True) def mute_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -180,9 +197,9 @@ def mute_user(client, message): if user.id in BRAIN: return edit( - message, get_translation( - 'brainError', [ - '`', '**', user.first_name, user.id])) + message, + get_translation("brainError", ["`", "**", user.first_name, user.id]), + ) try: chat_id = message.chat.id @@ -190,20 +207,22 @@ def mute_user(client, message): return sql.mute(chat_id, user.id) edit( - message, get_translation( - 'muteResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("muteResult", ["**", user.first_name, user.id, "`"]), + ) sleep(1) send_log( get_translation( - 'muteLog', [ - '**', user.first_name, user.id, message.chat.title, '`', chat_id])) + "muteLog", + ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + ) + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.unmute', compat=False, private=False, admin=True) +@sedenify(pattern="^.unmute", compat=False, private=False, admin=True) def unmute_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -233,15 +252,15 @@ def unmute_user(client, message): sql.unmute(chat_id, user.id) client.unban_chat_member(chat_id, user.id) edit( - message, get_translation( - 'unmuteResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("unmuteResult", ["**", user.first_name, user.id, "`"]), + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.promote', admin=True, private=False, compat=False) +@sedenify(pattern="^.promote", admin=True, private=False, compat=False) def promote_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -254,14 +273,14 @@ def promote_user(client, message): rank = args except Exception: return edit(message, f'`{get_translation("banFailUser")}`') - elif ' ' not in args: + elif " " not in args: try: user = client.get_users(args) except Exception: return edit(message, f'`{get_translation("banFailUser")}`') elif args: try: - arr = args.split(' ', 1) + arr = args.split(" ", 1) user = client.get_users(arr[0]) rank = arr[1] except Exception: @@ -271,32 +290,37 @@ def promote_user(client, message): try: chat_id = message.chat.id - client.promote_chat_member(chat_id, user.id, - can_change_info=True, - can_delete_messages=True, - can_restrict_members=True, - can_invite_users=True, - can_pin_messages=True, - can_promote_members=True) + client.promote_chat_member( + chat_id, + user.id, + can_change_info=True, + can_delete_messages=True, + can_restrict_members=True, + can_invite_users=True, + can_pin_messages=True, + can_promote_members=True, + ) if rank is not None: if len(rank) > 16: rank = rank[:16] client.set_administrator_title(chat_id, user.id, rank) edit( - message, get_translation( - 'promoteResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("promoteResult", ["**", user.first_name, user.id, "`"]), + ) sleep(1) send_log( get_translation( - 'promoteLog', [ - '**', user.first_name, user.id, message.chat.title, '`', chat_id])) + "promoteLog", + ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + ) + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.demote', compat=False, private=False, admin=True) +@sedenify(pattern="^.demote", compat=False, private=False, admin=True) def demote_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -323,23 +347,26 @@ def demote_user(client, message): try: chat_id = message.chat.id - client.promote_chat_member(chat_id, user.id, - can_change_info=False, - can_delete_messages=False, - can_restrict_members=False, - can_invite_users=False, - can_pin_messages=False, - can_promote_members=False) + client.promote_chat_member( + chat_id, + user.id, + can_change_info=False, + can_delete_messages=False, + can_restrict_members=False, + can_invite_users=False, + can_pin_messages=False, + can_promote_members=False, + ) edit( - message, get_translation( - 'demoteResult', [ - '**', user.first_name, user.id, '`'])) + message, + get_translation("demoteResult", ["**", user.first_name, user.id, "`"]), + ) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.pin$', compat=False, private=False, admin=True) +@sedenify(pattern="^.pin$", compat=False, private=False, admin=True) def pin_message(client, message): reply = message.reply_to_message @@ -352,16 +379,13 @@ def pin_message(client, message): client.pin_chat_message(chat_id, message_id) edit(message, f'`{get_translation("pinResult")}`') sleep(1) - send_log( - get_translation( - 'pinLog', [ - '**', message.chat.title, '`', chat_id])) + send_log(get_translation("pinLog", ["**", message.chat.title, "`", chat_id])) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return -@sedenify(pattern='^.unpin$', compat=False, private=False, admin=True) +@sedenify(pattern="^.unpin$", compat=False, private=False, admin=True) def unpin_message(client, message): reply = message.reply_to_message chat_id = message.chat.id @@ -369,39 +393,44 @@ def unpin_message(client, message): try: client.unpin_chat_message(chat_id, reply.message_id) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return else: try: client.unpin_all_chat_messages(chat_id) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) + edit(message, get_translation("banError", ["`", "**", e])) return message.delete() -@sedenify(pattern='^.(admins|bots|user(s|sdel))$', compat=False, private=False) +@sedenify(pattern="^.(admins|bots|user(s|sdel))$", compat=False, private=False) def get_users(client, message): - args = message.text.split(' ', 1) - users = args[0][1:5] == 'user' - showdel = users and args[0][-3:] == 'del' - bots = not users and args[0][1:5] == 'bots' - admins = not bots and args[0][1:7] == 'admins' + args = message.text.split(" ", 1) + users = args[0][1:5] == "user" + showdel = users and args[0][-3:] == "del" + bots = not users and args[0][1:5] == "bots" + admins = not bots and args[0][1:7] == "admins" - out = '' + out = "" if users: out = get_translation( - 'userlist', - ['**', f'{get_translation("deleted") if showdel else ""}', '`', - message.chat.title]) - filtr = 'all' + "userlist", + [ + "**", + f'{get_translation("deleted") if showdel else ""}', + "`", + message.chat.title, + ], + ) + filtr = "all" elif admins: - out = get_translation('adminlist', ['**', '`', message.chat.title]) - filtr = 'administrators' + out = get_translation("adminlist", ["**", "`", message.chat.title]) + filtr = "administrators" elif bots: - out = get_translation('botlist', ['**', '`', message.chat.title]) - filtr = 'bots' + out = get_translation("botlist", ["**", "`", message.chat.title]) + filtr = "bots" try: chat_id = message.chat.id @@ -410,7 +439,7 @@ def get_users(client, message): if not i.user.is_deleted and showdel: continue name = f'[{get_translation("deletedAcc") if i.user.is_deleted else i.user.first_name}](tg://user?id={i.user.id}) | `{i.user.id}`' - out += f'\n`•` **{name}**' + out += f"\n`•` **{name}**" except Exception as e: out += f'\n{get_translation("banError", ["`", "**", e])}' @@ -418,35 +447,47 @@ def get_users(client, message): edit(message, out) except MessageTooLong: edit(message, f'`{get_translation("outputTooLarge")}`') - file = open('userslist.txt', 'w+') + file = open("userslist.txt", "w+") file.write(out) file.close() reply_doc( - message, 'userslist.txt', + message, + "userslist.txt", caption=get_translation( - 'userlist', - ['**', f'{get_translation("deleted") if showdel else ""}', '`', - message.chat.title]), - delete_after_send=True, delete_orig=True) - - -@sedenify(pattern='^.zombies', private=False, admin=True, compat=False) + "userlist", + [ + "**", + f'{get_translation("deleted") if showdel else ""}', + "`", + message.chat.title, + ], + ), + delete_after_send=True, + delete_orig=True, + ) + + +@sedenify(pattern="^.zombies", private=False, compat=False) def zombie_accounts(client, message): args = extract_args(message).lower() chat_id = message.chat.id count = 0 msg = f'`{get_translation("zombiesNoAccount")}`' - if args != 'clean': + if args != "clean": edit(message, f'`{get_translation("zombiesFind")}`') for i in client.iter_chat_members(chat_id): if i.user.is_deleted: count += 1 sleep(1) if count > 0: - msg = get_translation('zombiesFound', ['**', '`', count]) + msg = get_translation("zombiesFound", ["**", "`", count]) return edit(message, msg) + if not is_admin(message): + edit(message, f'`{get_translation("adminUsage")}`') + return message.continue_propagation() + edit(message, f'`{get_translation("zombiesRemove")}`') count = 0 users = 0 @@ -464,19 +505,18 @@ def zombie_accounts(client, message): count += 1 if count > 0: - msg = get_translation('zombiesResult', ['**', '`', count]) + msg = get_translation("zombiesResult", ["**", "`", count]) if users > 0: - msg = get_translation('zombiesResult2', ['**', '`', count, users]) + msg = get_translation("zombiesResult2", ["**", "`", count, users]) edit(message, msg) sleep(2) message.delete() send_log( - get_translation( - 'zombiesLog', [ - '**', '`', count, message.chat.title, chat_id])) + get_translation("zombiesLog", ["**", "`", count, message.chat.title, chat_id]) + ) @sedenify(incoming=True, outgoing=False, compat=False) @@ -497,4 +537,4 @@ def mute_check(client, message): message.continue_propagation() -HELP.update({'admin': get_translation('adminInfo')}) +HELP.update({"admin": get_translation("adminInfo")}) diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 133f689..7630c9c 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -10,12 +10,20 @@ from sqlalchemy.exc import IntegrityError from sedenbot.modules.chat import is_muted -from sedenbot import (PM_COUNT, HELP, PM_AUTO_BAN, LOGS, - PM_LAST_MSG, PM_UNAPPROVED, PM_MSG_COUNT) -from sedenecem.core import (sedenify, send_log, me, - edit, reply, get_translation) +from sedenbot import ( + PM_COUNT, + HELP, + PM_AUTO_BAN, + LOGS, + PM_LAST_MSG, + PM_UNAPPROVED, + PM_MSG_COUNT, + TEMP_SETTINGS, +) +from sedenecem.core import sedenify, send_log, edit, reply, get_translation + # ========================= CONSTANTS ============================ -UNAPPROVED_MSG = PM_UNAPPROVED or get_translation('pmpermitMessage', ['`']) +UNAPPROVED_MSG = PM_UNAPPROVED or get_translation("pmpermitMessage", ["`"]) # ================================================================= @@ -23,18 +31,26 @@ def pmpermit_init(): try: global sql from importlib import import_module - sql = import_module('sedenecem.sql.pm_permit_sql') + + sql = import_module("sedenecem.sql.pm_permit_sql") except Exception as e: sql = None - LOGS.warn(get_translation('pmpermitSqlLog')) + LOGS.warn(get_translation("pmpermitSqlLog")) raise e pmpermit_init() -@sedenify(incoming=True, outgoing=False, disable_edited=True, - disable_notify=True, group=False, compat=False, bot=False) +@sedenify( + incoming=True, + outgoing=False, + disable_edited=True, + disable_notify=True, + group=False, + compat=False, + bot=False, +) def permitpm(client, message): if message.from_user and message.from_user.is_self: message.continue_propagation() @@ -45,7 +61,7 @@ def permitpm(client, message): if auto_accept(client, message): return - self_user = me[0] + self_user = TEMP_SETTINGS["ME"] if message.chat.id not in [self_user.id, 777000]: try: from sedenecem.sql.pm_permit_sql import is_approved @@ -59,8 +75,7 @@ def permitpm(client, message): if message.chat.id in PM_LAST_MSG: prevmsg = PM_LAST_MSG[message.chat.id] if message.text != prevmsg: - for message in _find_unapproved_msg( - client, message.chat.id): + for message in _find_unapproved_msg(client, message.chat.id): message.delete() if PM_COUNT[message.chat.id] < (PM_MSG_COUNT - 1): ret = reply(message, UNAPPROVED_MSG) @@ -91,14 +106,15 @@ def permitpm(client, message): send_log( get_translation( - 'pmpermitLog', [ - message.chat.first_name, message.chat.id])) + "pmpermitLog", [message.chat.first_name, message.chat.id] + ) + ) message.continue_propagation() def auto_accept(client, message): - self_user = me[0] + self_user = TEMP_SETTINGS["ME"] if message.chat.id not in [self_user.id, 777000]: try: from sedenecem.sql.pm_permit_sql import approve, is_approved @@ -110,7 +126,11 @@ def auto_accept(client, message): return True for msg in client.get_history(chat.id, limit=3): - if chat.id in PM_LAST_MSG and msg.text != PM_LAST_MSG[chat.id] and msg.from_user.is_self: + if ( + chat.id in PM_LAST_MSG + and msg.text != PM_LAST_MSG[chat.id] + and msg.from_user.is_self + ): try: del PM_COUNT[chat.id] del PM_LAST_MSG[chat.id] @@ -122,9 +142,8 @@ def auto_accept(client, message): for message in _find_unapproved_msg(client, chat.id): message.delete() send_log( - get_translation( - 'pmAutoAccept', [ - chat.first_name, chat.id])) + get_translation("pmAutoAccept", [chat.first_name, chat.id]) + ) return True except BaseException: pass @@ -132,7 +151,7 @@ def auto_accept(client, message): return False -@sedenify(outgoing=True, pattern='^.notifoff$') +@sedenify(outgoing=True, pattern="^.notifoff$") def notifoff(message): try: from sedenecem.sql.keep_read_sql import kread @@ -144,7 +163,7 @@ def notifoff(message): edit(message, f'`{get_translation("pmNotifOff")}`') -@sedenify(outgoing=True, pattern='^.notifon$') +@sedenify(outgoing=True, pattern="^.notifon$") def notifon(message): try: from sedenecem.sql.keep_read_sql import unkread @@ -156,7 +175,7 @@ def notifon(message): edit(message, f'`{get_translation("pmNotifOn")}`') -@sedenify(outgoing=True, pattern='^.approve$', compat=False) +@sedenify(outgoing=True, pattern="^.approve$", compat=False) def approvepm(client, message): try: from sedenecem.sql.pm_permit_sql import approve @@ -175,7 +194,7 @@ def approvepm(client, message): uid = replied_user.id else: aname = message.chat - if not aname.type == 'private': + if not aname.type == "private": edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -189,9 +208,9 @@ def approvepm(client, message): edit(message, f'`{get_translation("pmApproveError2")}`') return - edit(message, get_translation('pmApproveSuccess', [name0, uid, '`'])) + edit(message, get_translation("pmApproveSuccess", [name0, uid, "`"])) - send_log(get_translation('pmApproveLog', [name0, uid])) + send_log(get_translation("pmApproveLog", [name0, uid])) @sedenify(outgoing=True, pattern="^.disapprove$") @@ -213,7 +232,7 @@ def disapprovepm(message): uid = replied_user.id else: aname = message.chat - if not aname.type == 'private': + if not aname.type == "private": edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -221,20 +240,18 @@ def disapprovepm(message): dissprove(uid) - edit(message, get_translation('pmDisapprove', [name0, uid, '`'])) + edit(message, get_translation("pmDisapprove", [name0, uid, "`"])) - send_log(get_translation('pmDisapprove', [name0, uid, '`'])) + send_log(get_translation("pmDisapprove", [name0, uid, "`"])) def _find_unapproved_msg(client, chat_id): try: return client.search_messages( - chat_id, - from_user='me', - limit=10, - query=UNAPPROVED_MSG) + chat_id, from_user="me", limit=10, query=UNAPPROVED_MSG + ) except BaseException: return [] -HELP.update({'pmpermit': get_translation('pmpermitInfo')}) +HELP.update({"pmpermit": get_translation("pmpermitInfo")}) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index a6bc267..c8a1a37 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -13,28 +13,29 @@ from pyrogram.raw.functions.messages import GetStickerSet from pyrogram.raw.types import InputStickerSetShortName -from sedenbot import HELP, PACKNAME, PACKNICK +from sedenbot import HELP, PACKNAME, PACKNICK, TEMP_SETTINGS from sedenecem.core import ( sedenify, edit, - me, download_media_wc, reply_doc, get_translation, extract_args, PyroConversation, - sticker_resize as resizer) + sticker_resize as resizer, +) + # ================= CONSTANT ================= -DIZCILIK = [get_translation(f'kangstr{i+1}') for i in range(0, 12)] +DIZCILIK = [get_translation(f"kangstr{i+1}") for i in range(0, 12)] # ================= CONSTANT ================= -@sedenify(pattern='^.(d[ıi]zla|kang)', compat=False) +@sedenify(pattern="^.(d[ıi]zla|kang)", compat=False) def kang(client, message): - myacc = me[0] + myacc = TEMP_SETTINGS["ME"] kanger = myacc.username or myacc.first_name if myacc.username: - kanger = f'@{kanger}' + kanger = f"@{kanger}" args = extract_args(message) reply = message.reply_to_message @@ -45,8 +46,8 @@ def kang(client, message): anim = False media = None - if(reply.photo or reply.document or reply.sticker): - edit(message, f'`{choice(DIZCILIK)}`') + if reply.photo or reply.document or reply.sticker: + edit(message, f"`{choice(DIZCILIK)}`") anim = reply.sticker and reply.sticker.is_animated media = download_media_wc(reply, sticker_orig=anim) else: @@ -54,28 +55,38 @@ def kang(client, message): return if len(args) < 1: - args = 1 - - emoji = '🤤' - - if ' ' in str(args): - emoji, args = args.split(' ', 1) - - pack = 1 if not str(args).isdigit() else int(args) - - pname = PACKNAME.replace( - ' ', '') if PACKNAME else f'a{myacc.id}_by_{myacc.username}_{pack}' + args = "1" + + emoji = "🤤" + pack = 1 + + for item in args.split(): + if item.isdigit(): + pack = int(item) + args = args.replace(item, "").strip() + elif "e=" in item: + emoji = item.replace("e=", "") + args = args.replace(item, "").strip() + + pname = ( + PACKNAME.replace(" ", "") + if PACKNAME + else f"a{myacc.id}_by_{myacc.username}_{pack}" + ) pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" - limit = '50' if anim else '120' - - def pack_created(): - created = get(f'https://telegram.me/addstickers/{pname}') - created = (('A <strong>Telegram</strong> user has created the ' - '<strong>Sticker Set</strong>') not in created.text) - return created + limit = "50" if anim else "120" - def create_new(conv, pack): + def pack_created(name): + try: + set_name = InputStickerSetShortName(short_name=name) + set = GetStickerSet(stickerset=set_name) + client.send(data=set) + return True + except BaseException as e: + return False + + def create_new(conv, pname, pnick): cmd = f'/new{"animated" if anim else "pack"}' try: @@ -83,23 +94,23 @@ def create_new(conv, pack): except Exception as e: raise e msg = send_recv(conv, pnick) - if msg.text == 'Invalid pack selected.': + if msg.text == "Invalid pack selected.": pack += 1 return create_new(conv) msg = send_recv(conv, media, doc=True) - if 'Sorry, the file type is invalid.' in msg.text: + if "Sorry, the file type is invalid." in msg.text: edit(message, f'`{get_translation("stickerError")}`') return send_recv(conv, emoji) - send_recv(conv, '/publish') + send_recv(conv, "/publish") if anim: - send_recv(conv, f'<{pnick}>') - send_recv(conv, '/skip') + send_recv(conv, f"<{pnick}>") + send_recv(conv, "/skip") send_recv(conv, pname) def add_exist(conv, pack, pname, pnick): try: - send_recv(conv, '/addsticker') + send_recv(conv, "/addsticker") except Exception as e: raise e @@ -107,33 +118,40 @@ def add_exist(conv, pack, pname, pnick): if limit in status.text: pack += 1 - pname = PACKNAME.replace( - ' ', '') if PACKNAME else f'a{myacc.id}_by_{myacc.username}_{pack}' + pname = ( + PACKNAME.replace(" ", "") + if PACKNAME + else f"a{myacc.id}_by_{myacc.username}_{pack}" + ) pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" - edit(message, get_translation('packFull', ['`', '**', str(pack)])) - return add_exist(conv, pack, pname, pnick) + edit(message, get_translation("packFull", ["`", "**", str(pack)])) + if pack_created(pname): + return add_exist(conv, pack, pname, pnick) + else: + return create_new(conv, pname, pnick) send_recv(conv, media, doc=True) send_recv(conv, emoji) - send_recv(conv, '/done') + send_recv(conv, "/done") return True if anim: - pname += '_anim' - pnick += ' (Animated)' + pname += "_anim" + pnick += " (Animated)" else: if not reply.sticker: media = resizer(media) - with PyroConversation(client, 'Stickers') as conv: - if pack_created(): + with PyroConversation(client, "Stickers") as conv: + send_recv(conv, "/cancel") + if pack_created(pname): ret = add_exist(conv, pack, pname, pnick) if not ret: return else: - create_new(conv, pack) + create_new(conv, pname, pnick) - edit(message, get_translation('stickerAdded', ['`', pname])) + edit(message, get_translation("stickerAdded", ["`", pname])) def send_recv(conv, msg, doc=False): @@ -144,7 +162,7 @@ def send_recv(conv, msg, doc=False): return conv.recv_msg() -@sedenify(pattern='^.getsticker$') +@sedenify(pattern="^.getsticker$") def getsticker(message): reply = message.reply_to_message if not reply or not reply.sticker: @@ -156,13 +174,14 @@ def getsticker(message): reply_doc( message, photo, - caption=f'**Sticker ID:** `{reply.sticker.file_id}' - f'`\n**Emoji**: `{reply.sticker.emoji}`', + caption=f"**Sticker ID:** `{reply.sticker.file_id}" + f"`\n**Emoji**: `{reply.sticker.emoji}`", delete_after_send=True, - delete_orig=True) + delete_orig=True, + ) -@sedenify(pattern='.packinfo$', compat=False) +@sedenify(pattern=".packinfo$", compat=False) def packinfo(client, message): reply = message.reply_to_message if not reply: @@ -177,25 +196,30 @@ def packinfo(client, message): get_stickerset = client.send( GetStickerSet( - stickerset=InputStickerSetShortName( - short_name=reply.sticker.set_name))) + stickerset=InputStickerSetShortName(short_name=reply.sticker.set_name) + ) + ) pack_emojis = [] for document_sticker in get_stickerset.packs: if document_sticker.emoticon not in pack_emojis: pack_emojis.append(document_sticker.emoticon) - out = get_translation('packinfoResult', - ['**', - '`', - get_stickerset.set.title, - get_stickerset.set.short_name, - get_stickerset.set.official, - get_stickerset.set.archived, - get_stickerset.set.animated, - get_stickerset.set.count, - ' '.join(pack_emojis)]) + out = get_translation( + "packinfoResult", + [ + "**", + "`", + get_stickerset.set.title, + get_stickerset.set.short_name, + get_stickerset.set.official, + get_stickerset.set.archived, + get_stickerset.set.animated, + get_stickerset.set.count, + " ".join(pack_emojis), + ], + ) edit(message, out) -HELP.update({'stickers': get_translation('stickerInfo')}) +HELP.update({"stickers": get_translation("stickerInfo")}) diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index f6cec70..c968472 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -12,98 +12,105 @@ from heroku3 import from_key from sedenbot import HELP, HEROKU_KEY, HEROKU_APPNAME, REPO_URL -from sedenecem.core import (extract_args, sedenify, edit, me, - reply, reply_doc, get_translation) - -from git import Repo -from git.exc import (GitCommandError, InvalidGitRepositoryError, - NoSuchPathError) +from sedenecem.core import ( + extract_args, + sedenify, + edit, + reply, + reply_doc, + get_translation, +) + +from git import Repo # type: ignore +from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError requirements_path = path.join( - path.dirname(path.dirname(path.dirname(__file__))), 'requirements.txt') + path.dirname(path.dirname(path.dirname(__file__))), "requirements.txt" +) def gen_chlog(repo, diff): - ch_log = '' - d_form = '%d/%m/%y' + ch_log = "" + d_form = "%d/%m/%y" for c in repo.iter_commits(diff): - ch_log += f'%1•%1 %2[{c.committed_datetime.strftime(d_form)}]: {c.summary} <{c.author}>%2\n' + ch_log += f"%1•%1 %2[{c.committed_datetime.strftime(d_form)}]: {c.summary} <{c.author}>%2\n" return ch_log def update_requirements(): reqs = str(requirements_path) try: - _, ret = execute_command(f'{executable} -m pip install -r {reqs}') + _, ret = execute_command(f"{executable} -m pip install -r {reqs}") return ret except Exception as e: return repr(e) -@sedenify(pattern='^.update') +@sedenify(pattern="^.update") def upstream(ups): edit(ups, f'`{get_translation("updateCheck")}`') conf = extract_args(ups) off_repo = REPO_URL force_update = False + repo = None try: txt = f'`{get_translation("updateFailed")}`\n\n' txt += f'**{get_translation("updateLog")}**\n' repo = Repo() except NoSuchPathError as error: - edit(ups, get_translation('updateFolderError', [txt, '`', error])) + edit(ups, get_translation("updateFolderError", [txt, "`", error])) repo.__del__() return except GitCommandError as error: - edit(ups, get_translation('updateFolderError', [txt, '`', error])) + edit(ups, get_translation("updateFolderError", [txt, "`", error])) repo.__del__() return except InvalidGitRepositoryError as error: - if conf != 'now': - edit(ups, get_translation('updateGitNotFound', [error])) + if conf != "now": + edit(ups, get_translation("updateGitNotFound", [error])) return repo = Repo.init() - origin = repo.create_remote('upstream', off_repo) + origin = repo.create_remote("upstream", off_repo) origin.fetch() force_update = True - repo.create_head('seden', origin.refs.seden) + repo.create_head("seden", origin.refs.seden) repo.heads.seden.set_tracking_branch(origin.refs.seden) repo.heads.seden.checkout(True) ac_br = repo.active_branch.name - if ac_br != 'seden': - edit(ups, get_translation('updateFolderError', ['**', ac_br])) + if ac_br != "seden": + edit(ups, get_translation("updateFolderError", ["**", ac_br])) repo.__del__() return try: - repo.create_remote('upstream', off_repo) + repo.create_remote("upstream", off_repo) except BaseException: pass - ups_rem = repo.remote('upstream') + ups_rem = repo.remote("upstream") ups_rem.fetch(ac_br) - changelog = gen_chlog(repo, f'HEAD..upstream/{ac_br}') + changelog = gen_chlog(repo, f"HEAD..upstream/{ac_br}") if not changelog and not force_update: - edit(ups, get_translation('updaterUsingLatest', ['**', '`', ac_br])) + edit(ups, get_translation("updaterUsingLatest", ["**", "`", ac_br])) repo.__del__() return - if conf != 'now' and not force_update: + if conf != "now" and not force_update: if len(changelog) > 4096: edit(ups, f'`{get_translation("updateOutput")}`') - file = open('changelog.txt', 'w+') + file = open("changelog.txt", "w+") file.write(changelog) file.close() - reply_doc(ups, ups.chat.id, 'changelog.txt', - delete_after_send=True) + reply_doc(ups, ups.chat.id, "changelog.txt", delete_after_send=True) else: - edit(ups, get_translation( - 'updaterHasUpdate', ['**', '`', ac_br, changelog])) - reply(ups, get_translation('updateNow', ['**', '`'])) + edit( + ups, get_translation("updaterHasUpdate", ["**", "`", ac_br, changelog]) + ) + reply(ups, get_translation("updateNow", ["**", "`"])) return if force_update: @@ -117,7 +124,6 @@ def upstream(ups): heroku_applications = heroku.apps() if not HEROKU_APPNAME: edit(ups, f'`{get_translation("updateHerokuAppName")}`') - me[1] = False repo.__del__() return for app in heroku_applications: @@ -126,35 +132,35 @@ def upstream(ups): break if heroku_app is None: edit(ups, f'`{get_translation("updateHerokuAppName", [txt])}`') - me[1] = False repo.__del__() return edit(ups, f'`{get_translation("updateBotUpdating")}`') ups_rem.fetch(ac_br) - repo.git.reset('--hard', 'FETCH_HEAD') + repo.git.reset("--hard", "FETCH_HEAD") heroku_git_url = heroku_app.git_url.replace( - 'https://', 'https://api:' + HEROKU_KEY + '@') - if 'heroku' in repo.remotes: - remote = repo.remote('heroku') + "https://", f"https://api:{HEROKU_KEY}@" + ) + if "heroku" in repo.remotes: + remote = repo.remote("heroku") remote.set_url(heroku_git_url) else: - remote = repo.create_remote('heroku', heroku_git_url) + remote = repo.create_remote("heroku", heroku_git_url) try: - remote.push(refspec='HEAD:refs/heads/master', force=True) + remote.push(refspec="HEAD:refs/heads/master", force=True) except GitCommandError as error: - edit(ups, get_translation('updaterGitError', ['`', txt, error])) + edit(ups, get_translation("updaterGitError", ["`", txt, error])) repo.__del__() return edit(ups, f'`{get_translation("updateComplete")}`') try: - heroku_app.scale_formation_process('seden', 1) + heroku_app.scale_formation_process("seden", 1) except BaseException: pass else: try: ups_rem.pull(ac_br) except GitCommandError: - repo.git.reset('--hard', 'FETCH_HEAD') + repo.git.reset("--hard", "FETCH_HEAD") update_requirements() edit(ups, f'`{get_translation("updateLocalComplate")}`') @@ -170,12 +176,12 @@ def execute_command(command): sonuc = None try: from subprocess import PIPE, Popen - islem = Popen(command, stdout=PIPE, stderr=PIPE, - universal_newlines=True) + + islem = Popen(command, stdout=PIPE, stderr=PIPE, universal_newlines=True) sonuc, _ = islem.communicate() except BaseException: pass return sonuc, islem.returncode -HELP.update({'update': get_translation('updateInfo')}) +HELP.update({"update": get_translation("updateInfo")}) diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 35df798..d3d2790 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -10,27 +10,22 @@ from re import escape, sub from pyrogram.types import Message -from sedenbot import app, me, BRAIN, BOT_PREFIX +from sedenbot import TEMP_SETTINGS, app, BRAIN, BOT_PREFIX -MARKDOWN_FIX_CHAR = '\u2064' +MARKDOWN_FIX_CHAR = "\u2064" SPAM_COUNT = [0] -_parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r'\.' +_parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r"\." def reply( - message, - text, - preview=True, - fix_markdown=False, - delete_orig=False, - parse='md'): + message, text, preview=True, fix_markdown=False, delete_orig=False, parse="md" +): try: if fix_markdown: text += MARKDOWN_FIX_CHAR ret = message.reply_text( - text.strip(), - disable_web_page_preview=not preview, - parse_mode=parse) + text.strip(), disable_web_page_preview=not preview, parse_mode=parse + ) if delete_orig: message.delete() return ret @@ -45,11 +40,11 @@ def extract_args(message, markdown=True): text = message.text or message.caption text = text.markdown if markdown else text - if ' ' not in text: - return '' + if " " not in text: + return "" - text = sub(r'\s+', ' ', text) - text = text[text.find(' '):].strip() + text = sub(r"\s+", " ", text) + text = text[text.find(" ") :].strip() return text @@ -57,56 +52,55 @@ def extract_args_arr(message, markdown=True): return extract_args(message, markdown).split() -def edit(message, text, preview=True, fix_markdown=False, parse='md'): +def edit(message, text, preview=True, fix_markdown=False, parse="md"): try: if fix_markdown: text += MARKDOWN_FIX_CHAR - if message.from_user.id != me[0].id: + if message.from_user.id != TEMP_SETTINGS["ME"].id: reply(message, text, preview=preview, parse=parse) return message.edit_text( - text.strip(), - disable_web_page_preview=not preview, - parse_mode=parse) + text.strip(), disable_web_page_preview=not preview, parse_mode=parse + ) except BaseException: pass -def download_media( - client, - data, - file_name=None, - progress=None, - sticker_orig=True): +def download_media(client, data, file_name=None, progress=None, sticker_orig=True): if not file_name: if data.document: - file_name = (data.document.file_name - if data.document.file_name - else f'{data.document.file_id}.bin') + file_name = ( + data.document.file_name + if data.document.file_name + else f"{data.document.file_id}.bin" + ) elif data.audio: - file_name = (data.audio.file_name - if data.audio.file_name - else f'{data.audio.file_id}.mp3') + file_name = ( + data.audio.file_name + if data.audio.file_name + else f"{data.audio.file_id}.mp3" + ) elif data.photo: - file_name = f'{data.photo.file_id}.png' + file_name = f"{data.photo.file_id}.png" elif data.voice: - file_name = f'{data.voice.file_id}.ogg' + file_name = f"{data.voice.file_id}.ogg" elif data.video: - file_name = (data.video.file_name - if data.video.file_name - else f'{data.video.file_id}.mp4') + file_name = ( + data.video.file_name + if data.video.file_name + else f"{data.video.file_id}.mp4" + ) elif data.animation: - file_name = f'{data.animation.file_id}.mp4' + file_name = f"{data.animation.file_id}.mp4" elif data.video_note: - file_name = f'{data.video_note.file_id}.mp4' + file_name = f"{data.video_note.file_id}.mp4" elif data.sticker: file_name = f'sticker.{("tgs" if sticker_orig else "TGS") if data.sticker.is_animated else ("webp" if sticker_orig else "png")}' else: return None if progress: - return client.download_media( - data, file_name=file_name, progress=progress) + return client.download_media(data, file_name=file_name, progress=progress) return client.download_media(data, file_name=file_name) @@ -121,22 +115,21 @@ def get_me(): def forward(message, chat_id): try: - return message.forward(chat_id or 'me') + return message.forward(chat_id or "me") except Exception as e: raise e def get_messages(chat_id, msg_ids=None, client=app): try: - ret = client.get_messages( - chat_id=(chat_id or 'me'), message_ids=msg_ids) + ret = client.get_messages(chat_id=(chat_id or "me"), message_ids=msg_ids) return [ret] if ret and isinstance(ret, Message) else ret except BaseException: return [] def amisudo(): - return me[0].id in BRAIN + return TEMP_SETTINGS["ME"].id in BRAIN def increment_spam_count(): @@ -153,11 +146,11 @@ def get_cmd(message): if text: text = text.strip() return parse_cmd(text) - return '' + return "" def parse_cmd(text): - cmd = sub(r'\s+', ' ', text) + cmd = sub(r"\s+", " ", text) cmd = cmd.split()[0] cmd = cmd.split(_parsed_prefix)[-1] if BOT_PREFIX else cmd[1:] return cmd diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 6dd92b7..62d16ae 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -7,6 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # +from os import getpid, system from subprocess import Popen, PIPE from sys import exc_info from time import gmtime, strftime @@ -16,29 +17,36 @@ from pyrogram.handlers import MessageHandler from pyrogram import filters -from sedenbot import (SUPPORT_GROUP, BLACKLIST, BOT_VERSION, - BRAIN, me, app, get_translation) +from sedenbot import ( + SUPPORT_GROUP, + BLACKLIST, + BOT_VERSION, + BRAIN, + TEMP_SETTINGS, + app, + get_translation, +) from .sedenlog import send_log_doc from .misc import edit, _parsed_prefix, get_cmd from sedenbot.modules.admin import is_admin def sedenify(**args): - pattern = args.get('pattern', None) - outgoing = args.get('outgoing', True) - incoming = args.get('incoming', False) - disable_edited = args.get('disable_edited', False) - disable_notify = args.get('disable_notify', False) - compat = args.get('compat', True) - brain = args.get('brain', False) - private = args.get('private', True) - group = args.get('group', True) - bot = args.get('bot', True) - service = args.get('service', False) - admin = args.get('admin', False) - - if pattern and '.' in pattern[:2]: - args['pattern'] = pattern = pattern.replace('.', _parsed_prefix, 1) + pattern = args.get("pattern", None) + outgoing = args.get("outgoing", True) + incoming = args.get("incoming", False) + disable_edited = args.get("disable_edited", False) + disable_notify = args.get("disable_notify", False) + compat = args.get("compat", True) + brain = args.get("brain", False) + private = args.get("private", True) + group = args.get("group", True) + bot = args.get("bot", True) + service = args.get("service", False) + admin = args.get("admin", False) + + if pattern and "." in pattern[:2]: + args["pattern"] = pattern = pattern.replace(".", _parsed_prefix, 1) def msg_decorator(func): def wrap(client, message): @@ -46,27 +54,28 @@ def wrap(client, message): return try: - if len(me) < 1: - me.append(app.get_me()) + if not TEMP_SETTINGS.get("ME", None): + me = app.get_me() + TEMP_SETTINGS["ME"] = me - if me[0].id in BLACKLIST: - raise RetardsException('RETARDS CANNOT USE THIS BOT') + if me.id in BLACKLIST: + raise RetardsException("RETARDS CANNOT USE THIS BOT") if message.service and not service: return - if message.chat.type == 'channel': + if message.chat.type == "channel": return - if not bot and message.chat.type == 'bot': + if not bot and message.chat.type == "bot": message.continue_propagation() - if not private and message.chat.type in ['private', 'bot']: + if not private and message.chat.type in ["private", "bot"]: if not disable_notify: edit(message, f'`{get_translation("groupUsage")}`') message.continue_propagation() - if not group and message.chat.type in ['group', 'supergroup']: + if not group and "group" in message.chat.type: if not disable_notify: edit(message, f'`{get_translation("privateUsage")}`') message.continue_propagation() @@ -82,7 +91,7 @@ def wrap(client, message): func(message) except RetardsException: try: - app.stop() + system(f"kill -9 {getpid()}") except BaseException: pass except (ContinuePropagation, StopPropagation) as c: @@ -91,36 +100,45 @@ def wrap(client, message): try: date = strftime("%Y-%m-%d %H:%M:%S", gmtime()) - if get_cmd(message) == 'crash': - text = get_translation('logidTest') + if get_cmd(message) == "crash": + text = get_translation("logidTest") else: if not disable_notify: - edit( - message, - f'`{get_translation("errorLogSend")}`') - text = get_translation('sedenErrorText', ['**', '`', exc_info()[1]]) + edit(message, f'`{get_translation("errorLogSend")}`') + text = get_translation( + "sedenErrorText", ["**", "`", exc_info()[1]] + ) ftext = get_translation( - 'sedenErrorText2', - [date, message.chat.id, message.from_user.id - if message.from_user else 'Unknown', BOT_VERSION, - message.text, format_exc(), - exc_info()[1]]) + "sedenErrorText2", + [ + date, + message.chat.id, + message.from_user.id if message.from_user else "Unknown", + BOT_VERSION, + message.text, + format_exc(), + exc_info()[1], + ], + ) process = Popen( - ['git', 'log', '--pretty=format:"%an: %s"', '-10'], - stdout=PIPE, stderr=PIPE) + ["git", "log", '--pretty=format:"%an: %s"', "-10"], + stdout=PIPE, + stderr=PIPE, + ) out, err = process.communicate() - out = f'{out.decode()}\n{err.decode()}'.strip() + out = f"{out.decode()}\n{err.decode()}".strip() ftext += out - file = open(get_translation('rbgLog'), 'w+') + file = open(get_translation("rbgLog"), "w+") file.write(ftext) file.close() - send_log_doc(get_translation('rbgLog'), - caption=text, remove_file=True) + send_log_doc( + get_translation("rbgLog"), caption=text, remove_file=True + ) raise e except Exception as x: raise x @@ -133,12 +151,12 @@ def wrap(client, message): if outgoing and not incoming: filter &= filters.me elif incoming and not outgoing: - filter &= (filters.incoming & ~filters.bot & ~filters.me) + filter &= filters.incoming & ~filters.bot & ~filters.me else: if outgoing and not incoming: filter = filters.me elif incoming and not outgoing: - filter = (filters.incoming & ~filters.bot & ~filters.me) + filter = filters.incoming & ~filters.bot & ~filters.me else: filter = (filters.me | filters.incoming) & ~filters.bot From c2b0c502caddfc4542abd270dd4c97a615748167 Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Fri, 16 Apr 2021 06:12:44 +0300 Subject: [PATCH 031/242] Bring back covid19 module --- sedenbot/modules/covid19.py | 88 ++++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 12 +++++ sedenecem/translator/tr.json | 12 +++++ 3 files changed, 112 insertions(+) create mode 100644 sedenbot/modules/covid19.py diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py new file mode 100644 index 0000000..5d66b03 --- /dev/null +++ b/sedenbot/modules/covid19.py @@ -0,0 +1,88 @@ +# Copyright (C) 2020 TeamDerUntergang. +# +# SedenUserBot is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SedenUserBot is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# + +from requests import get +from json import loads +from bs4 import BeautifulSoup +from re import sub + +from sedenbot import HELP +from sedenecem.core import edit, sedenify, get_translation + +# Copyright (c) @frknkrc44 | 2020 + + +@sedenify(pattern="^.covid(|19)$") +def covid(message): + try: + req = get( + "https://covid19.saglik.gov.tr/", + headers={ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3", + "Cache-Control": "max-age=0", + "Connection": "keep-alive", + "Referer": "https://covid19.saglik.gov.tr/", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36", + }, + ) + + soup = BeautifulSoup(req.text, "html.parser") + scripts = soup.find_all("script") + for script in scripts: + turejq = str(script) + if "var sondurumjson" in turejq: + result = loads( + sub( + "(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s)", + "", + turejq, + ) + ) + break + except BaseException: + edit(message, f'`{get_translation("covidError")}`') + return + + if len(result) > 0: + result = result[0] + + def del_dots(res): + return res.replace(".", "") + + sonuclar = ( + f'**{get_translation("covidData")}**\n' + + f'\n**{get_translation("covidDate")}** {result["tarih"]}\n' + + f'\n**{get_translation("covidTotal")}**\n' + + f'**{get_translation("covidTests")}** `{del_dots(result["toplam_test"])}`\n' + + f'**{get_translation("covidCases")}** `{del_dots(result["toplam_hasta"])}`\n' + + f'**{get_translation("covidDeaths")}** `{del_dots(result["toplam_vefat"])}`\n' + + f'**{get_translation("covidSeriouslyill")}** `{del_dots(result["agir_hasta_sayisi"])}`\n' + + f'**{get_translation("covidPneumonia")}** `%{result["hastalarda_zaturre_oran"]}`\n' + + f'**{get_translation("covidHealed")}** `{del_dots(result["toplam_iyilesen"])}`\n' + + f'\n**{get_translation("covidToday")}**\n' + + f'**{get_translation("covidTests")}** `{del_dots(result["gunluk_test"])}`\n' + + f'**{get_translation("covidCases")}** `{del_dots(result["gunluk_vaka"])}`\n' + + f'**{get_translation("covidPatients")}** `{del_dots(result["gunluk_hasta"])}`\n' + + f'**{get_translation("covidDeaths")}** `{del_dots(result["gunluk_vefat"])}`\n' + + f'**{get_translation("covidHealed")}** `{del_dots(result["gunluk_iyilesen"])}`' + ) + + edit(message, sonuclar) + + +HELP.update({"covid19": get_translation("covidInfo")}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 6b3e886..83980f9 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -107,7 +107,19 @@ "codenameUsage": "Usage: .codename <brand> <device> eg: .codename Xiaomi Mi 9T Pro", "colorsInfo": ".color <color code>\nUsage: Print out color code you specified.\neg: .color #330066", "colorsUsage": "Maybe you can learn something by reading here..\n.color <color code> | eg: .color #330066", + "covidCases": "Cases", + "covidData": "\ud83c\uddf9\ud83c\uddf7 Coronavirus Data \ud83c\uddf9\ud83c\uddf7", + "covidDate": "Date", + "covidDeaths": "Deaths", "covidError": "Something went wrong.", + "covidHealed": "Healed", + "covidInfo": ".covid\nUsage: Latest Covid 19 statistics for Turkey.", + "covidPatients": "Patients", + "covidPneumonia": "Pneumonia", + "covidSeriouslyill": "Seriously ill", + "covidTests": "Test", + "covidToday": "Today", + "covidTotal": "Total", "cpUsage": "\ud83d\ude02\ud83c\udd71\ufe0fIvE\ud83d\udc50sOME\ud83d\udc45text\ud83d\udc45for\u270c\ufe0fMe\ud83d\udc4ctO\ud83d\udc50MAkE\ud83d\udc40iT\ud83d\udc9efunNy!\ud83d\udca6", "currencyError": "What you're writing looks like an alien currency, so I can't convert it.", "currencyInfo": ".currency <amount> <from> <to>\nUsage: Converts various currencies for you.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 5e5b818..2876375 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -108,7 +108,19 @@ "codenameUsage": "Kullan\u0131m: .codename <marka> <cihaz> \u00d6rnek: .codename Xiaomi Mi 9T Pro", "colorsInfo": ".color <renk kodu>\nKullan\u0131m: Belirtti\u011fniz renk kodunun \u00e7\u0131kt\u0131s\u0131n\u0131 al\u0131n.\n\u00d6rnek: .color #330066", "colorsUsage": "Belki buray\u0131 okuyarak bir \u015feyler \u00f6\u011frenebilirsin..\n.color <renk kodu> | \u00d6rnek: .color #330066", + "covidCases": "Vaka", + "covidData": "\ud83c\uddf9\ud83c\uddf7 Koronavir\u00fcs Verileri \ud83c\uddf9\ud83c\uddf7", + "covidDate": "Tarih", + "covidDeaths": "\u00d6l\u00fcm", "covidError": "Bir hata olu\u015ftu.", + "covidHealed": "\u0130yile\u015fen", + "covidInfo": ".covid\nKullan\u0131m: T\u00fcrkiye i\u00e7in g\u00fcncel Covid 19 istatistikleri.", + "covidPatients": "Hasta", + "covidPneumonia": "Zat\u00fcrre", + "covidSeriouslyill": "A\u011f\u0131r hasta", + "covidTests": "Test", + "covidToday": "Bug\u00fcn", + "covidTotal": "Toplam", "cpUsage": "\ud83d\ude02Bana\ud83d\udcafBIR\u270c\ufe0fmE\ud83c\udd71\ufe0fIn\ud83d\udc50Ver\ud83d\udc4f", "currencyError": "Yazd\u0131\u011f\u0131n \u015fey uzayl\u0131lar\u0131n kulland\u0131\u011f\u0131 bir para birimine benziyor, bu y\u00fczden d\u00f6n\u00fc\u015ft\u00fcremiyorum.", "currencyInfo": ".currency <miktar> <d\u00f6n\u00fc\u015ft\u00fcr\u00fclecek birim> <d\u00f6n\u00fc\u015fecek birim>\nKullan\u0131m: Belirtilen para miktarlar\u0131n\u0131 d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n\n.d\u00f6viz\nKullan\u0131m: G\u00fcncel d\u00f6viz kurlar\u0131n\u0131 getirir.", From 9d45e007f15043da57bf548dc6101021b8fa5551 Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Fri, 16 Apr 2021 12:13:39 +0300 Subject: [PATCH 032/242] seden: Release v1.4.4 Beta * Added listgmute & listgban & online & ascii & zombies command * Added covid19 module and doviz command (Turkish users) * GBan and GMute commands moved to globals.py * Updated all packages * Fixed known bugs *NOTE:* Windows is restricted in bot (use Linux or something) Co-authored-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> Signed-off-by: frknkrc44 <krc440002@gmail.com> --- seden.py | 2 +- sedenbot/__init__.py | 173 +++++----- sedenbot/modules/admin/helpers.py | 4 +- sedenbot/modules/afk.py | 133 ++++---- sedenbot/modules/android.py | 292 ++++++++-------- sedenbot/modules/autopp.py | 23 +- sedenbot/modules/ban.py | 151 ++++----- sedenbot/modules/blacklist.py | 32 +- sedenbot/modules/chat.py | 5 +- sedenbot/modules/colors.py | 7 +- sedenbot/modules/covid19.py | 58 ++-- sedenbot/modules/deepfry.py | 66 ++-- sedenbot/modules/deezer.py | 6 +- sedenbot/modules/direct_link.py | 34 +- sedenbot/modules/dogbin.py | 24 +- sedenbot/modules/ecem.py | 383 ++++++++++++++------- sedenbot/modules/effects.py | 110 ++++-- sedenbot/modules/env.py | 78 ++--- sedenbot/modules/ezanvakti.py | 138 +++++--- sedenbot/modules/filters.py | 27 +- sedenbot/modules/gban.py | 132 ------- sedenbot/modules/git.py | 50 +-- sedenbot/modules/globals.py | 259 ++++++++++++++ sedenbot/modules/gmute.py | 132 ------- sedenbot/modules/horeke.py | 69 ++-- sedenbot/modules/lastfm.py | 29 +- sedenbot/modules/locks.py | 48 +-- sedenbot/modules/lydia.py | 50 +-- sedenbot/modules/lyrics.py | 17 +- sedenbot/modules/memes.py | 547 ++++++++++++++++++++++++------ sedenbot/modules/misc.py | 75 ++-- sedenbot/modules/notes.py | 21 +- sedenbot/modules/ocr.py | 23 +- sedenbot/modules/pmpermit.py | 49 ++- sedenbot/modules/profile.py | 30 +- sedenbot/modules/purge.py | 27 +- sedenbot/modules/qrcode.py | 32 +- sedenbot/modules/quotly.py | 4 +- sedenbot/modules/remove_bg.py | 21 +- sedenbot/modules/reverse.py | 44 ++- sedenbot/modules/rgb.py | 9 +- sedenbot/modules/sangmata.py | 3 +- sedenbot/modules/scrapers.py | 176 ++++++---- sedenbot/modules/screencapture.py | 22 +- sedenbot/modules/sed.py | 23 +- sedenbot/modules/seden.py | 3 +- sedenbot/modules/snips.py | 21 +- sedenbot/modules/spammer.py | 15 +- sedenbot/modules/speedtest.py | 73 ++-- sedenbot/modules/stickers.py | 91 +++-- sedenbot/modules/system.py | 135 +++++--- sedenbot/modules/updater.py | 78 ++--- sedenbot/modules/updown.py | 16 +- sedenbot/modules/weather.py | 12 +- sedenbot/modules/whois.py | 48 ++- sedenbot/modules/youtubedl.py | 24 +- sedenecem/core/__init__.py | 6 +- sedenecem/core/conv.py | 4 +- sedenecem/core/image.py | 1 + sedenecem/core/misc.py | 44 +-- sedenecem/core/replier.py | 48 ++- sedenecem/core/sedenify.py | 77 +++-- sedenecem/core/sedenlog.py | 10 +- sedenecem/core/send.py | 19 +- sedenecem/core/webdriver.py | 2 +- sedenecem/sql/__init__.py | 4 +- sedenecem/sql/blacklist_sql.py | 26 +- sedenecem/sql/filters_sql.py | 13 +- sedenecem/sql/gban_sql.py | 2 +- sedenecem/sql/gmute_sql.py | 2 +- sedenecem/sql/keep_read_sql.py | 2 +- sedenecem/sql/lydia_sql.py | 74 ---- sedenecem/sql/mute_sql.py | 16 +- sedenecem/sql/notes_sql.py | 4 +- sedenecem/sql/pm_permit_sql.py | 5 +- sedenecem/sql/snips_sql.py | 4 +- sedenecem/translator/__init__.py | 2 +- sedenecem/translator/en.json | 28 +- sedenecem/translator/tr.json | 20 +- 79 files changed, 2619 insertions(+), 1948 deletions(-) delete mode 100644 sedenbot/modules/gban.py create mode 100644 sedenbot/modules/globals.py delete mode 100644 sedenbot/modules/gmute.py delete mode 100644 sedenecem/sql/lydia_sql.py diff --git a/seden.py b/seden.py index f2debf4..c335396 100644 --- a/seden.py +++ b/seden.py @@ -7,7 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -if __name__ == "__main__": +if __name__ == '__main__': from sedenbot import app app.run() diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 9a4c389..8306ee2 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -9,32 +9,31 @@ from os import name -if name == "nt": - print("Uninstall Windows to use this bot") +if name == 'nt': + print('Uninstall Windows to use this bot') quit(1) -from sqlite3 import connect -from sys import version_info -from os.path import isfile -from os import environ, listdir, path, remove -from re import search as resr from distutils.util import strtobool as sb from importlib import import_module -from logging import basicConfig, getLogger, INFO, DEBUG, CRITICAL +from logging import CRITICAL, DEBUG, INFO, basicConfig, getLogger +from os import environ, listdir, path, remove +from os.path import isfile from pathlib import PurePath +from re import search as resr +from sqlite3 import connect +from sys import version_info from traceback import format_exc from typing import Any, Dict -from requests import get -from dotenv import load_dotenv, set_key, unset_key +import sedenecem.translator as _tr +from dotenv import load_dotenv, set_key, unset_key from pyrogram import Client, filters from pyrogram.handlers import MessageHandler - -import sedenecem.translator as _tr +from requests import get def reload_env(): - return load_dotenv("config.env", override=True) + return load_dotenv('config.env', override=True) reload_env() @@ -47,15 +46,15 @@ def get_translation(transKey, params: list = None): if params and len(params) > 0: for i in reversed(range(len(params))): - ret = ret.replace(f"%{i+1}", str(params[i])) + ret = ret.replace(f'%{i+1}', str(params[i])) - ret = ret.replace("½", "%") + ret = ret.replace('½', '%') return ret if version_info[0] < 3 or version_info[1] < 8: - LOGS.warn(get_translation("pythonVersionError")) + LOGS.warn(get_translation('pythonVersionError')) quit(1) HELP: Dict[str, str] = {} @@ -67,10 +66,10 @@ def get_translation(transKey, params: list = None): TEMP_SETTINGS: Dict[Any, Any] = {} # Console verbose logging -LOG_VERBOSE = sb(environ.get("LOG_VERBOSE", "False")) +LOG_VERBOSE = sb(environ.get('LOG_VERBOSE', 'False')) basicConfig( - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=DEBUG if LOG_VERBOSE else INFO, ) @@ -78,29 +77,29 @@ def get_translation(transKey, params: list = None): # Bot lang # # If missted, the default lang is English. -SEDEN_LANG = environ.get("SEDEN_LANG", "en") +SEDEN_LANG = environ.get('SEDEN_LANG', 'en') def set_local_env(key: str, value: str): - return set_key(PurePath("config.env"), key, value) + return set_key(PurePath('config.env'), key, value) def unset_local_env(key: str): if key in environ: del environ[key] - return unset_key(PurePath("config.env"), key) + return unset_key('config.env', key) def set_logger(): # Turns off out printing Session value - pyrogram_syncer = getLogger("pyrogram.syncer") + pyrogram_syncer = getLogger('pyrogram.syncer') pyrogram_syncer.setLevel(CRITICAL) # Closes some junk outputs - pyrogram_session = getLogger("pyrogram.session.session") + pyrogram_session = getLogger('pyrogram.session.session') pyrogram_session.setLevel(CRITICAL) - pyrogram_auth = getLogger("pyrogram.session.auth") + pyrogram_auth = getLogger('pyrogram.session.auth') pyrogram_auth.setLevel(CRITICAL) @@ -108,79 +107,81 @@ def set_logger(): # Check that the config is edited using the previously used variable. # Basically, check for config file. -if environ.get("___________DELETE_______THIS_____LINE__________", None): - LOGS.warn(get_translation("removeFirstLine")) +CONFIG_CHECK = environ.get('___________DELETE_______THIS_____LINE__________', None) + +if CONFIG_CHECK: + LOGS.warn(get_translation('removeFirstLine')) quit(1) # Telegram APP ID and HASH -API_ID = environ.get("API_ID", None) +API_ID = environ.get('API_ID', None) if not API_ID: - LOGS.warn(get_translation("apiIdError")) + LOGS.warn(get_translation('apiIdError')) quit(1) -API_HASH = environ.get("API_HASH", None) +API_HASH = environ.get('API_HASH', None) if not API_HASH: - LOGS.warn(get_translation("apiHashError")) + LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = "1.4.3 Beta" -SUPPORT_GROUP = "SedenUserBotSupport" -CHANNEL = "SedenUserBot" +BOT_VERSION = '1.4.4 Beta' +SUPPORT_GROUP = 'SedenUserBotSupport' +CHANNEL = 'SedenUserBot' # Weather default city -WEATHER = environ.get("WEATHER", None) +WEATHER = environ.get('WEATHER', None) # Genius module -GENIUS_TOKEN = environ.get("GENIUS_TOKEN", None) or environ.get("GENIUS", None) +GENIUS_TOKEN = environ.get('GENIUS_TOKEN', None) or environ.get('GENIUS', None) # Lydia API -LYDIA_APIKEY = environ.get("LYDIA_APIKEY", None) +LYDIA_APIKEY = environ.get('LYDIA_APIKEY', None) # Change Alive Message -ALIVE_MSG = environ.get("ALIVE_MSG", None) +ALIVE_MSG = environ.get('ALIVE_MSG', None) # For neofetch -HOSTNAME = environ.get("HOSTNAME", "DerUntergang") -USER = environ.get("USER", "sedenecem") +HOSTNAME = environ.get('HOSTNAME', 'DerUntergang') +USER = environ.get('USER', 'sedenecem') # Chrome Driver and Headless Google Chrome Binaries -CHROME_DRIVER = environ.get("CHROME_DRIVER", "chromedriver") +CHROME_DRIVER = environ.get('CHROME_DRIVER', 'chromedriver') # OCR API key -OCR_APIKEY = environ.get("OCR_APIKEY", None) +OCR_APIKEY = environ.get('OCR_APIKEY', None) # Auto pp link -AUTO_PP = environ.get("AUTO_PP", None) +AUTO_PP = environ.get('AUTO_PP', None) # RBG API key -RBG_APIKEY = environ.get("RBG_APIKEY", None) +RBG_APIKEY = environ.get('RBG_APIKEY', None) # Custom sticker pack -PACKNAME = environ.get("PACKNAME", None) -PACKNICK = environ.get("PACKNICK", None) +PACKNAME = environ.get('PACKNAME', None) +PACKNICK = environ.get('PACKNICK', None) # Deezer ARL Token -DEEZER_TOKEN = environ.get("DEEZER_TOKEN", None) +DEEZER_TOKEN = environ.get('DEEZER_TOKEN', None) # SQL Database URL -DATABASE_URL = environ.get("DATABASE_URL", None) +DATABASE_URL = environ.get('DATABASE_URL', None) # Download directory -DOWNLOAD_DIRECTORY = environ.get("DOWNLOAD_DIRECTORY", "./downloads") +DOWNLOAD_DIRECTORY = environ.get('DOWNLOAD_DIRECTORY', './downloads') # SedenBot Session -SESSION = environ.get("SESSION", "sedenuserbot") +SESSION = environ.get('SESSION', 'sedenuserbot') # SedenBot repo url for updater -REPO_URL = environ.get("REPO_URL", "https://github.com/TeamDerUntergang/SedenUserBot") +REPO_URL = environ.get('REPO_URL', 'https://github.com/TeamDerUntergang/SedenUserBot') # Heroku Credentials for updater -HEROKU_KEY = environ.get("HEROKU_KEY", None) -HEROKU_APPNAME = environ.get("HEROKU_APPNAME", None) +HEROKU_KEY = environ.get('HEROKU_KEY', None) +HEROKU_APPNAME = environ.get('HEROKU_APPNAME', None) # Chat ID for Bot Logs -_LOG_ID = environ.get("LOG_ID", None) -LOG_ID = int(_LOG_ID) if _LOG_ID and resr(r"^-?\d+$", _LOG_ID) else None +_LOG_ID = environ.get('LOG_ID', None) +LOG_ID = int(_LOG_ID) if _LOG_ID and resr(r'^-?\d+$', _LOG_ID) else None del _LOG_ID # Connect to the test server @@ -192,34 +193,34 @@ def set_logger(): # Also known as Deep Telegram # # For more information: https://docs.pyrogram.org/topics/test-servers -DEEPGRAM = sb(environ.get("DEEPGRAM", "False")) +DEEPGRAM = sb(environ.get('DEEPGRAM', 'False')) # PmPermit PM Auto Ban Stuffs -PM_AUTO_BAN = sb(environ.get("PM_AUTO_BAN", "False")) -_PM_MSG_COUNT = environ.get("PM_MSG_COUNT", "default") +PM_AUTO_BAN = sb(environ.get('PM_AUTO_BAN', 'False')) +_PM_MSG_COUNT = environ.get('PM_MSG_COUNT', 'default') PM_MSG_COUNT = int(_PM_MSG_COUNT) if _PM_MSG_COUNT.isdigit() else 5 del _PM_MSG_COUNT -PM_UNAPPROVED = environ.get("PM_UNAPPROVED", None) +PM_UNAPPROVED = environ.get('PM_UNAPPROVED', None) # Bot Prefix (Defaults to dot) -BOT_PREFIX = environ.get("BOT_PREFIX", None) +BOT_PREFIX = environ.get('BOT_PREFIX', None) -ENV_RESTRICTED_KEYS = ["HEROKU_KEY", "HEROKU_APPNAME", "SESSION", "API_ID", "API_HASH"] +ENV_RESTRICTED_KEYS = ['HEROKU_KEY', 'HEROKU_APPNAME', 'SESSION', 'API_ID', 'API_HASH'] def load_brain(): try: - if path.exists("learning-data-root.check"): - remove("learning-data-root.check") + if path.exists('learning-data-root.check'): + remove('learning-data-root.check') URL = ( - "https://raw.githubusercontent.com/NaytSeyd/" - "databasescape/master/learning-data-root.check" + 'https://raw.githubusercontent.com/NaytSeyd/' + 'databasescape/master/learning-data-root.check' ) - with open("learning-data-root.check", "wb") as load: + with open('learning-data-root.check', 'wb') as load: load.write(get(URL).content) - DB = connect("learning-data-root.check") + DB = connect('learning-data-root.check') CURSOR = DB.cursor() - CURSOR.execute("SELECT * FROM BRAIN1") + CURSOR.execute('SELECT * FROM BRAIN1') ALL_ROWS = CURSOR.fetchall() for i in ALL_ROWS: BRAIN.append(i[0]) @@ -230,17 +231,17 @@ def load_brain(): def load_bl(): try: - if path.exists("blacklist.check"): - remove("blacklist.check") + if path.exists('blacklist.check'): + remove('blacklist.check') URL = ( - "https://raw.githubusercontent.com/NaytSeyd/" - "databaseblacklist/master/blacklist.check" + 'https://raw.githubusercontent.com/NaytSeyd/' + 'databaseblacklist/master/blacklist.check' ) - with open("blacklist.check", "wb") as load: + with open('blacklist.check', 'wb') as load: load.write(get(URL).content) - DB = connect("blacklist.check") + DB = connect('blacklist.check') CURSOR = DB.cursor() - CURSOR.execute("SELECT * FROM RETARDS") + CURSOR.execute('SELECT * FROM RETARDS') ALL_ROWS = CURSOR.fetchall() for i in ALL_ROWS: BLACKLIST.append(i[0]) @@ -278,10 +279,10 @@ def export_session_string(self): SESSION, api_id=API_ID, api_hash=API_HASH, - app_version=f"Seden UserBot", - device_model="DerUntergang", - system_version=f"v{BOT_VERSION}", - lang_code="tr", + app_version=f'Seden UserBot', + device_model='DerUntergang', + system_version=f'v{BOT_VERSION}', + lang_code='tr', test_mode=DEEPGRAM, ) @@ -293,29 +294,29 @@ def export_session_string(self): def __get_modules(): - folder = "sedenbot/modules" + folder = 'sedenbot/modules' modules = [ f[:-3] for f in listdir(folder) - if isfile(f"{folder}/{f}") and f[-3:] == ".py" and f != "__init__.py" + if isfile(f'{folder}/{f}') and f[-3:] == '.py' and f != '__init__.py' ] return modules def __import_modules(): modules = sorted(__get_modules()) - LOGS.info(get_translation("loadedModules", [modules])) + LOGS.info(get_translation('loadedModules', [modules])) for module in modules: try: - LOGS.info(get_translation("loadedModules2", [module])) - import_module(f"sedenbot.modules.{module}") + LOGS.info(get_translation('loadedModules2', [module])) + import_module(f'sedenbot.modules.{module}') except Exception: if LOG_VERBOSE: LOGS.warn(format_exc()) - LOGS.warn(get_translation("loadedModulesError", [module])) + LOGS.warn(get_translation('loadedModulesError', [module])) __import_modules() -LOGS.info(get_translation("runningBot", [SUPPORT_GROUP])) -LOGS.info(get_translation("sedenVersion", [BOT_VERSION])) +LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) +LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) diff --git a/sedenbot/modules/admin/helpers.py b/sedenbot/modules/admin/helpers.py index e165a11..8f27feb 100644 --- a/sedenbot/modules/admin/helpers.py +++ b/sedenbot/modules/admin/helpers.py @@ -11,10 +11,10 @@ _admin_status_list = ['creator', 'administrator'] + def is_admin(message): if not 'group' in message.chat.type: return True - user = app.get_chat_member(chat_id=message.chat.id, - user_id=message.from_user.id) + user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) return user.status in _admin_status_list diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 79dfe69..a366c74 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -11,22 +11,21 @@ from time import sleep from pyrogram import ContinuePropagation, StopPropagation - -from sedenbot import HELP, TEMP_SETTINGS, PM_AUTO_BAN, app +from sedenbot import HELP, PM_AUTO_BAN, TEMP_SETTINGS, app from sedenecem.core import ( + edit, extract_args, + get_translation, + reply, sedenify, send_log, - edit, - reply, - get_translation, ) # ========================= CONSTANTS ============================ -AFKSTR = [get_translation(f"afkstr{i+1}") for i in range(0, 22)] -TEMP_SETTINGS["AFK_USERS"] = {} -TEMP_SETTINGS["IS_AFK"] = False -TEMP_SETTINGS["COUNT_MSG"] = 0 +AFKSTR = [get_translation(f'afkstr{i+1}') for i in range(0, 22)] +TEMP_SETTINGS['AFK_USERS'] = {} +TEMP_SETTINGS['IS_AFK'] = False +TEMP_SETTINGS['COUNT_MSG'] = 0 # ================================================================= @@ -39,57 +38,57 @@ disable_notify=True, ) def mention_afk(msg): - me = TEMP_SETTINGS["ME"] + me = TEMP_SETTINGS['ME'] mentioned = msg.mentioned rep_m = msg.reply_to_message if mentioned or rep_m and rep_m.from_user and rep_m.from_user.id == me.id: - if TEMP_SETTINGS["IS_AFK"]: - if msg.from_user.id not in TEMP_SETTINGS["AFK_USERS"]: - if "AFK_REASON" in TEMP_SETTINGS: + if TEMP_SETTINGS['IS_AFK']: + if msg.from_user.id not in TEMP_SETTINGS['AFK_USERS']: + if 'AFK_REASON' in TEMP_SETTINGS: reply( msg, get_translation( "afkMessage2", [ - "**", + '**', me.first_name, me.id, - "`", - TEMP_SETTINGS["AFK_REASON"], + '`', + TEMP_SETTINGS['AFK_REASON'], ], ), ) else: reply(msg, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS["AFK_USERS"].update({msg.from_user.id: 1}) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['AFK_USERS'].update({msg.from_user.id: 1}) + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: - if TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] % randint(2, 4) == 0: - if "AFK_REASON" in TEMP_SETTINGS: + if TEMP_SETTINGS['AFK_USERS'][msg.from_user.id] % randint(1, 2) == 0: + if 'AFK_REASON' in TEMP_SETTINGS: reply( msg, get_translation( "afkMessage2", [ - "**", + '**', me.first_name, me.id, - "`", - TEMP_SETTINGS["AFK_REASON"], + '`', + TEMP_SETTINGS['AFK_REASON'], ], ), ) else: reply(msg, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] = ( - TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] + 1 + TEMP_SETTINGS['AFK_USERS'][msg.from_user.id] = ( + TEMP_SETTINGS['AFK_USERS'][msg.from_user.id] + 1 ) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: - TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] = ( - TEMP_SETTINGS["AFK_USERS"][msg.from_user.id] + 1 + TEMP_SETTINGS['AFK_USERS'][msg.from_user.id] = ( + TEMP_SETTINGS['AFK_USERS'][msg.from_user.id] + 1 ) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 raise ContinuePropagation @@ -111,98 +110,98 @@ def afk_on_pm(message): apprv = True else: apprv = True - if apprv and TEMP_SETTINGS["IS_AFK"]: - me = TEMP_SETTINGS["ME"] - if message.from_user.id not in TEMP_SETTINGS["AFK_USERS"]: - if "AFK_REASON" in TEMP_SETTINGS: + if apprv and TEMP_SETTINGS['IS_AFK']: + me = TEMP_SETTINGS['ME'] + if message.from_user.id not in TEMP_SETTINGS['AFK_USERS']: + if 'AFK_REASON' in TEMP_SETTINGS: reply( message, get_translation( "afkMessage2", - ["**", me.first_name, me.id, "`", TEMP_SETTINGS["AFK_REASON"]], + ['**', me.first_name, me.id, '`', TEMP_SETTINGS['AFK_REASON']], ), ) else: reply(message, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS["AFK_USERS"].update({message.from_user.id: 1}) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['AFK_USERS'].update({message.from_user.id: 1}) + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: - if TEMP_SETTINGS["AFK_USERS"][message.from_user.id] % randint(2, 4) == 0: - if "AFK_REASON" in TEMP_SETTINGS: + if TEMP_SETTINGS['AFK_USERS'][message.from_user.id] % randint(1, 2) == 0: + if 'AFK_REASON' in TEMP_SETTINGS: reply( message, get_translation( "afkMessage2", [ - "**", + '**', me.first_name, me.id, - "`", - TEMP_SETTINGS["AFK_REASON"], + '`', + TEMP_SETTINGS['AFK_REASON'], ], ), ) else: reply(message, f"```{choice(AFKSTR)}```") - TEMP_SETTINGS["AFK_USERS"][message.from_user.id] = ( - TEMP_SETTINGS["AFK_USERS"][message.from_user.id] + 1 + TEMP_SETTINGS['AFK_USERS'][message.from_user.id] = ( + TEMP_SETTINGS['AFK_USERS'][message.from_user.id] + 1 ) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 else: - TEMP_SETTINGS["AFK_USERS"][message.from_user.id] = ( - TEMP_SETTINGS["AFK_USERS"][message.from_user.id] + 1 + TEMP_SETTINGS['AFK_USERS'][message.from_user.id] = ( + TEMP_SETTINGS['AFK_USERS'][message.from_user.id] + 1 ) - TEMP_SETTINGS["COUNT_MSG"] = TEMP_SETTINGS["COUNT_MSG"] + 1 + TEMP_SETTINGS['COUNT_MSG'] = TEMP_SETTINGS['COUNT_MSG'] + 1 raise ContinuePropagation -@sedenify(pattern=r"^.afk") +@sedenify(pattern=r'^.afk') def set_afk(message): args = extract_args(message) if len(args) > 0: - TEMP_SETTINGS["AFK_REASON"] = args + TEMP_SETTINGS['AFK_REASON'] = args edit( message, - get_translation("afkStartReason", ["**", "`", TEMP_SETTINGS["AFK_REASON"]]), + get_translation('afkStartReason', ['**', '`', TEMP_SETTINGS['AFK_REASON']]), ) else: edit(message, f'**{get_translation("afkStart")}**') - send_log(get_translation("afkLog")) - TEMP_SETTINGS["IS_AFK"] = True + send_log(get_translation('afkLog')) + TEMP_SETTINGS['IS_AFK'] = True raise StopPropagation @sedenify() def type_afk_is_not_true(message): - if TEMP_SETTINGS["IS_AFK"]: - TEMP_SETTINGS["IS_AFK"] = False + if TEMP_SETTINGS['IS_AFK']: + TEMP_SETTINGS['IS_AFK'] = False reply(message, f'**{get_translation("afkEnd")}**') sleep(2) send_log( get_translation( - "afkMessages", + 'afkMessages', [ - "`", - "**", - str(len(TEMP_SETTINGS["AFK_USERS"])), - str(TEMP_SETTINGS["COUNT_MSG"]), + '`', + '**', + str(len(TEMP_SETTINGS['AFK_USERS'])), + str(TEMP_SETTINGS['COUNT_MSG']), ], ) ) - for i in TEMP_SETTINGS["AFK_USERS"]: + for i in TEMP_SETTINGS['AFK_USERS']: name = app.get_chat(i) name0 = str(name.first_name) send_log( get_translation( - "afkMentionUsers", - ["**", name0, str(i), "`", str(TEMP_SETTINGS["AFK_USERS"][i])], + 'afkMentionUsers', + ['**', name0, str(i), '`', str(TEMP_SETTINGS['AFK_USERS'][i])], ) ) - TEMP_SETTINGS["COUNT_MSG"] = 0 - TEMP_SETTINGS["AFK_USERS"] = {} - if "AFK_REASON" in TEMP_SETTINGS: - del TEMP_SETTINGS["AFK_REASON"] + TEMP_SETTINGS['COUNT_MSG'] = 0 + TEMP_SETTINGS['AFK_USERS'] = {} + if 'AFK_REASON' in TEMP_SETTINGS: + del TEMP_SETTINGS['AFK_REASON'] raise ContinuePropagation -HELP.update({"afk": get_translation("afkInfo")}) +HELP.update({'afk': get_translation('afkInfo')}) diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 44e6bbc..389acf2 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -7,28 +7,28 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import sub +from datetime import datetime from json import loads +from re import sub from urllib.parse import urlencode -from datetime import datetime + from bs4 import BeautifulSoup from requests import get - from sedenbot import HELP, TEMP_SETTINGS -from sedenecem.core import edit, extract_args, sedenify, get_translation +from sedenecem.core import edit, extract_args, get_translation, sedenify -GITHUB = "https://github.com" +GITHUB = 'https://github.com' -@sedenify(pattern="^.magisk$") +@sedenify(pattern='^.magisk$') def magisk(message): magisk_dict = { - "Stable": "https://raw.githubusercontent.com/topjohnwu/" - "magisk-files/master/stable.json", - "Beta": "https://raw.githubusercontent.com/topjohnwu/" - "magisk-files/master/beta.json", - "Canary": "https://raw.githubusercontent.com/topjohnwu/" - "magisk-files/master/canary.json", + 'Stable': 'https://raw.githubusercontent.com/topjohnwu/' + 'magisk-files/master/stable.json', + 'Beta': 'https://raw.githubusercontent.com/topjohnwu/' + 'magisk-files/master/beta.json', + 'Canary': 'https://raw.githubusercontent.com/topjohnwu/' + 'magisk-files/master/canary.json', } releases = f'**{get_translation("magiskReleases")}**\n' for name, release_url in magisk_dict.items(): @@ -40,37 +40,37 @@ def magisk(message): edit(message, releases, preview=False) -@sedenify(pattern="^.phh") +@sedenify(pattern='^.phh') def phh(message): get_phh = get( - "https://api.github.com/repos/phhusson/treble_experimentations/releases/latest" + 'https://api.github.com/repos/phhusson/treble_experimentations/releases/latest' ).json() search = extract_args(message) - releases = "{}\n".format( + releases = '{}\n'.format( get_translation( - "androidPhhHeader", ["`", "{} ".format(search) if len(search) > 0 else ""] + 'androidPhhHeader', ['`', "{} ".format(search) if len(search) > 0 else ""] ) ) count = 0 for i in range(len(get_phh)): try: - name = get_phh["assets"][i]["name"] - if not name.endswith("img.xz"): + name = get_phh['assets'][i]['name'] + if not name.endswith('img.xz'): continue elif search not in name: continue count += 1 - url = get_phh["assets"][i]["browser_download_url"] - releases += f"[{name}]({url})\n" + url = get_phh['assets'][i]['browser_download_url'] + releases += f'[{name}]({url})\n' except IndexError: continue if count < 1: - releases = get_translation("phhError", ["`", "**", search]) + releases = get_translation('phhError', ['`', '**', search]) edit(message, releases, preview=False) -@sedenify(pattern=r"^.device") +@sedenify(pattern=r'^.device') def device(message): textx = message.reply_to_message codename = extract_args(message) @@ -83,55 +83,55 @@ def device(message): return data = loads( get( - "https://raw.githubusercontent.com/androidtrackers/" - "certified-android-devices/master/by_device.json" + 'https://raw.githubusercontent.com/androidtrackers/' + 'certified-android-devices/master/by_device.json' ).text ) results = data.get(codename) if results: - reply = "{}\n".format(get_translation("deviceSearch", ["**", codename])) + reply = "{}\n".format(get_translation('deviceSearch', ['**', codename])) for item in results: reply += get_translation( - "deviceSearchResultChild", - ["**", item["brand"], item["name"], item["model"]], + 'deviceSearchResultChild', + ['**', item['brand'], item['name'], item['model']], ) else: - reply = get_translation("deviceError", ["`", codename]) + reply = get_translation('deviceError', ['`', codename]) edit(message, reply) -@sedenify(pattern=r"^.codename") +@sedenify(pattern=r'^.codename') def codename(message): textx = message.reply_to_message arr = extract_args(message) brand = arr device = arr - if " " in arr: - args = arr.split(" ", 1) + if ' ' in arr: + args = arr.split(' ', 1) brand = args[0].lower() device = args[1].lower() elif textx: - brand = textx.text.split(" ")[0] - device = " ".join(textx.text.split(" ")[1:]) + brand = textx.text.split(' ')[0] + device = ' '.join(textx.text.split(' ')[1:]) else: edit(message, f'`{get_translation("codenameUsage")}`') return data = loads( get( - "https://raw.githubusercontent.com/androidtrackers/" - "certified-android-devices/master/by_brand.json" + 'https://raw.githubusercontent.com/androidtrackers/' + 'certified-android-devices/master/by_brand.json' ).text ) devices_lower = {k.lower(): v for k, v in data.items()} devices = devices_lower.get(brand) if not devices: - reply = get_translation("codenameError", ["`", device]) + reply = get_translation('codenameError', ['`', device]) else: results = [ i for i in devices - if device.lower() in i["name"].lower() - or device.lower() in i["model"].lower() + if device.lower() in i['name'].lower() + or device.lower() in i['model'].lower() ] if results: reply = f'{get_translation("codenameSearch", ["**", brand, device])}\n' @@ -139,43 +139,43 @@ def codename(message): results = results[:8] for item in results: reply += get_translation( - "codenameSearchResultChild", - ["**", item["device"], item["name"], item["model"]], + 'codenameSearchResultChild', + ['**', item['device'], item['name'], item['model']], ) else: - reply = get_translation("codenameError", ["`", device]) + reply = get_translation('codenameError', ['`', device]) edit(message, reply) -@sedenify(pattern=r"^.twrp") +@sedenify(pattern=r'^.twrp') def twrp(message): textx = message.reply_to_message device = extract_args(message) if device: pass elif textx: - device = textx.text.split(" ")[0] + device = textx.text.split(' ')[0] else: edit(message, f'`{get_translation("twrpUsage")}`') return - url = get(f"https://dl.twrp.me/{device}/") + url = get(f'https://dl.twrp.me/{device}/') if url.status_code == 404: - reply = get_translation("twrpError", ["`", device]) + reply = get_translation('twrpError', ['`', device]) edit(message, reply) return - page = BeautifulSoup(url.content, "html.parser") - download = page.find("table").find("tr").find("a") + page = BeautifulSoup(url.content, 'html.parser') + download = page.find('table').find('tr').find('a') dl_link = f"https://dl.twrp.me{download['href']}" dl_file = download.text - size = page.find("span", {"class": "filesize"}).text - date = page.find("em").text.strip() + size = page.find('span', {'class': 'filesize'}).text + date = page.find('em').text.strip() reply = get_translation( - "twrpResult", ["**", "__", device, dl_file, dl_link, size, date] + 'twrpResult', ['**', '__', device, dl_file, dl_link, size, date] ) edit(message, reply) -@sedenify(pattern=r"^.o(range|)f(ox|rp)") +@sedenify(pattern=r'^.o(range|)f(ox|rp)') def ofox(message): if len(args := extract_args(message)) < 1: edit(message, f'`{get_translation("ofrpUsage")}`') @@ -190,12 +190,12 @@ def ofox(message): if len(releases.releases) < 1: edit( message, - get_translation("ofrpNotFound", ["`", args, OFOX_REPO]), + get_translation('ofrpNotFound', ['`', args, OFOX_REPO]), preview=False, ) return - out = "" + out = '' for release in releases.releases: out += f"[{release.version}{' (Beta)' if release.is_beta() else ''}]({release.file_url}) **{release.file_size}**\n" @@ -205,10 +205,10 @@ def ofox(message): edit(message, f'`{get_translation("ofrpError")}`') return - edit(message, f"**OrangeFox Recovery ({args}):**\n{out}") + edit(message, f'**OrangeFox Recovery ({args}):**\n{out}') -@sedenify(pattern=r"^.specs") +@sedenify(pattern=r'^.specs') def specs(message): args = extract_args(message) if len(args) < 1: @@ -227,53 +227,53 @@ def specs(message): req = get( link, headers={ - "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; " - "+http://www.google.com/bot.html)" + 'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' + '+http://www.google.com/bot.html)' }, proxies=proxy, ) - soup = BeautifulSoup(req.text, features="html.parser") + soup = BeautifulSoup(req.text, features='html.parser') - def get_spec(query, key="data-spec", cls="td"): + def get_spec(query, key='data-spec', cls='td'): try: result = soup.find(cls, {key: query.split()}).text.strip() - result = get_translation("specsError2") if len(result) < 1 else result + result = get_translation('specsError2') if len(result) < 1 else result return result except BaseException: - return get_translation("specsError2") - - title = get_spec("specs-phone-name-title", "class", "h1") - launch = get_spec("released-hl", cls="span") - body = sub(", ", "g, ", get_spec("body-hl", cls="span")) - os = get_spec("os-hl", cls="span") - storage = get_spec("internalmemory") - stortyp = get_spec("memoryother") - dispsize = get_spec("displaysize-hl", cls="span") - dispres = get_spec("displayres-hl", cls="div") - bcampx = get_spec("cam1modules") - bcamft = get_spec("cam1features") - bcamvd = get_spec("cam1video") - fcampx = get_spec("cam2modules") - fcamft = get_spec("cam2features") - fcamvd = get_spec("cam2video") - cpuname = get_spec("chipset") - cpuchip = get_spec("cpu") - gpuname = get_spec("gpu") - battery = get_spec("batdescription1") - wlan = get_spec("wlan") - bluetooth = get_spec("bluetooth") - gps = get_spec("gps") - sensors = get_spec("sensors") - sarus = sub(r"\s\s+", ", ", get_spec("sar-us")) - sareu = sub(r"\s\s+", ", ", get_spec("sar-eu")) + return get_translation('specsError2') + + title = get_spec('specs-phone-name-title', 'class', 'h1') + launch = get_spec('released-hl', cls='span') + body = sub(', ', 'g, ', get_spec('body-hl', cls='span')) + os = get_spec('os-hl', cls='span') + storage = get_spec('internalmemory') + stortyp = get_spec('memoryother') + dispsize = get_spec('displaysize-hl', cls='span') + dispres = get_spec('displayres-hl', cls='div') + bcampx = get_spec('cam1modules') + bcamft = get_spec('cam1features') + bcamvd = get_spec('cam1video') + fcampx = get_spec('cam2modules') + fcamft = get_spec('cam2features') + fcamvd = get_spec('cam2video') + cpuname = get_spec('chipset') + cpuchip = get_spec('cpu') + gpuname = get_spec('gpu') + battery = get_spec('batdescription1') + wlan = get_spec('wlan') + bluetooth = get_spec('bluetooth') + gps = get_spec('gps') + sensors = get_spec('sensors') + sarus = sub(r'\s\s+', ', ', get_spec('sar-us')) + sareu = sub(r'\s\s+', ', ', get_spec('sar-eu')) edit( message, get_translation( - "specsResult", + 'specsResult', [ - "**", - "`", + '**', + '`', title, launch, body, @@ -309,35 +309,35 @@ def find_device(query, proxy): raw_query = query.lower() def replace_query(query): - return urlencode({"sSearch": query}) + return urlencode({'sSearch': query}) query = replace_query(raw_query) req = get( - f"https://www.gsmarena.com/res.php3?{query}", + f'https://www.gsmarena.com/res.php3?{query}', headers={ - "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; " - "+http://www.google.com/bot.html)" + 'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' + '+http://www.google.com/bot.html)' }, proxies=proxy, ) - soup = BeautifulSoup(req.text, features="html.parser") + soup = BeautifulSoup(req.text, features='html.parser') - if "Too" in soup.find("title").text: # GSMArena geçici ban atarsa + if 'Too' in soup.find('title').text: # GSMArena geçici ban atarsa return None - res = soup.findAll("div", {"class": ["makers"]}) + res = soup.findAll('div', {'class': ['makers']}) if not res or len(res) < 1: # hiçbir cihaz bulunamazsa return None - res = res[0].findAll("li") + res = res[0].findAll('li') for item in res: - name = str(item.find("span")) - name = sub("(<|</)span>", "", name) + name = str(item.find('span')) + name = sub('(<|</)span>', '', name) if ( - name[name.find(">") + 1 :].lower() == raw_query - or sub("<br(>|/>)", " ", name).lower() == raw_query + name[name.find('>') + 1 :].lower() == raw_query + or sub('<br(>|/>)', ' ', name).lower() == raw_query ): link = f"https://www.gsmarena.com/{item.find('a')['href']}" return link @@ -345,11 +345,11 @@ def replace_query(query): def get_stored_proxy(): - return TEMP_SETTINGS.get("VALID_PROXY_URL", "") + return TEMP_SETTINGS.get('VALID_PROXY_URL', '') def put_stored_proxy(proxy): - TEMP_SETTINGS["VALID_PROXY_URL"] = proxy + TEMP_SETTINGS['VALID_PROXY_URL'] = proxy def _xget_random_proxy(): @@ -361,18 +361,18 @@ def _xget_random_proxy(): return try_valid head = { - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "User-Agent": "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)", - "Referer": "https://www.google.com/search?q=sslproxies", + 'Accept-Encoding': 'gzip, deflate, sdch', + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', + 'Referer': 'https://www.google.com/search?q=sslproxies', } - req = get("https://sslproxies.org/", headers=head) - soup = BeautifulSoup(req.text, "html.parser") - res = soup.find("table", {"id": "proxylisttable"}).find("tbody") - res = res.findAll("tr") + req = get('https://sslproxies.org/', headers=head) + soup = BeautifulSoup(req.text, 'html.parser') + res = soup.find('table', {'id': 'proxylisttable'}).find('tbody') + res = res.findAll('tr') for item in res: - infos = item.findAll("td") + infos = item.findAll('td') ip = infos[0].text port = infos[1].text proxy = (ip, port) @@ -384,10 +384,10 @@ def _xget_random_proxy(): def _try_proxy(proxy): try: - prxy = f"http://{proxy[0]}:{proxy[1]}" + prxy = f'http://{proxy[0]}:{proxy[1]}' req = get( - "https://www.gsmarena.com/", - proxies={"http": prxy, "https": prxy}, + 'https://www.gsmarena.com/', + proxies={'http': prxy, 'https': prxy}, timeout=1, ) if req.status_code == 200: @@ -401,12 +401,12 @@ def get_random_proxy(): proxy = _xget_random_proxy() if not proxy: return None - proxy = f"http://{proxy[0]}:{proxy[1]}" + proxy = f'http://{proxy[0]}:{proxy[1]}' put_stored_proxy(proxy) proxy_dict = { - "https": proxy, - "http": proxy, + 'https': proxy, + 'http': proxy, } return proxy_dict @@ -418,30 +418,30 @@ def __init__(self, json, releases): self.releases = [] return - self.codename = json["codename"] - self.oem = json["oem_name"] - self.model = json["model_name"] - self.maintainer_name = json["maintainer"]["name"] - self.maintainer_username = json["maintainer"]["username"] + self.codename = json['codename'] + self.oem = json['oem_name'] + self.model = json['model_name'] + self.maintainer_name = json['maintainer']['name'] + self.maintainer_username = json['maintainer']['username'] self.releases = releases class OFRPRelease: def __init__(self, json): - self.id = json["_id"] - self.type = json["type"] - self.device = json["device_id"] + self.id = json['_id'] + self.type = json['type'] + self.device = json['device_id'] - date = datetime.utcfromtimestamp(int(json["date"])).strftime( - "%d-%m-%Y %H:%M:%S" + date = datetime.utcfromtimestamp(int(json['date'])).strftime( + '%d-%m-%Y %H:%M:%S' ) self.date = date - self.file_size = "{:,.2f} MB".format(int(json["size"]) / float(1 << 20)) - self.md5 = json["md5"] - self.version = json["version"] + self.file_size = '{:,.2f} MB'.format(int(json['size']) / float(1 << 20)) + self.md5 = json['md5'] + self.version = json['version'] - url = f"https://api.orangefox.download/v3/releases/{self.id}" + url = f'https://api.orangefox.download/v3/releases/{self.id}' res = ofrp_get(url) if not res: @@ -451,23 +451,23 @@ def __init__(self, json): json = loads(res) - self.file_name = json["filename"] - self.file_url = list(json["mirrors"].values())[0] - self.changelog = "\n".join(json["changelog"]) + self.file_name = json['filename'] + self.file_url = list(json['mirrors'].values())[0] + self.changelog = '\n'.join(json['changelog']) def is_beta(self): - return self.type == "beta" + return self.type == 'beta' def ofrp_get(url): try: head = { - "Accept-Language": "en-US,en;q=0.8", - "User-Agent": "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)", - "Referer": "https://orangefox.download/en", + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', + 'Referer': 'https://orangefox.download/en', } req = get(url, headers=head) - if "{" not in req.text: + if '{' not in req.text: raise BaseException return req.text except BaseException: @@ -475,7 +475,7 @@ def ofrp_get(url): def ofrp_get_packages(device): - url = f"https://api.orangefox.download/v3/devices/get?codename={device}" + url = f'https://api.orangefox.download/v3/devices/get?codename={device}' res = ofrp_get(url) if not res: @@ -483,7 +483,7 @@ def ofrp_get_packages(device): json = loads(res) - if "_id" not in json: + if '_id' not in json: return OFRPDeviceInfo(None, None) url = f'https://api.orangefox.download/v3/releases/?device_id={json["_id"]}' @@ -492,10 +492,10 @@ def ofrp_get_packages(device): out = [] json2 = loads(res) - json2 = json2["data"] + json2 = json2['data'] - stables = [x for x in json2 if x["type"] == "stable"] - betas = [x for x in json2 if x["type"] == "beta"] + stables = [x for x in json2 if x['type'] == 'stable'] + betas = [x for x in json2 if x['type'] == 'beta'] if len(stables): stable = [OFRPRelease(x) for x in stables] @@ -508,4 +508,4 @@ def ofrp_get_packages(device): return OFRPDeviceInfo(json, out) -HELP.update({"android": get_translation("androidInfo")}) +HELP.update({'android': get_translation('androidInfo')}) diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index 43969dd..ada400e 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -7,15 +7,20 @@ # All rights reserved. See COPYING, AUTHORS. # +from datetime import datetime from os import path, remove from time import sleep -from datetime import datetime + from PIL import Image, ImageDraw, ImageFont from requests import get - -from sedenbot import HELP, AUTO_PP, TEMP_SETTINGS, LOGS -from sedenecem.core import (extract_args, sedenify, edit, - get_translation, download_media_wc) +from sedenbot import AUTO_PP, HELP, LOGS, TEMP_SETTINGS +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + sedenify, +) # =================== CONSTANT =================== KEY_AUTOPP = 'autopic' @@ -57,7 +62,8 @@ def autopic(client, message): try: profile_photo = client.get_profile_photos('me', limit=1) downloaded_file_name = download_media_wc( - profile_photo[0], downloaded_file_name) + profile_photo[0], downloaded_file_name + ) except BaseException: edit(message, f'`{get_translation("autoppConfig")}`') return @@ -73,7 +79,10 @@ def autopic(client, message): size = drawn_text.multiline_textsize(current_time, font=fnt) drawn_text.text( ((img.width - size[0]) / 2, (img.height - size[1])), - current_time, font=fnt, fill=(255, 255, 255)) + current_time, + font=fnt, + fill=(255, 255, 255), + ) img.save(photo) client.set_profile_photo(photo=photo) remove(photo) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 916681a..85d6bf4 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -7,26 +7,24 @@ # All rights reserved. See COPYING, AUTHORS. # -from dotenv import compat -from sedenbot.modules.admin.helpers import is_admin from time import sleep -from pyrogram.types import ChatPermissions from pyrogram.errors import MessageTooLong, UserAdminInvalid - -from sedenbot import HELP, BRAIN +from pyrogram.types import ChatPermissions +from sedenbot import BRAIN, HELP +from sedenbot.modules.admin.helpers import is_admin from sedenecem.core import ( edit, - sedenify, - send_log, - reply_doc, extract_args, get_translation, + reply_doc, + sedenify, + send_log, ) from sedenecem.sql import mute_sql as sql -@sedenify(pattern="^.ban", compat=False, private=False, admin=True) +@sedenify(pattern='^.ban', compat=False, private=False, admin=True) def ban_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -54,28 +52,28 @@ def ban_user(client, message): if user.id in BRAIN: return edit( message, - get_translation("brainError", ["`", "**", user.first_name, user.id]), + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) try: chat_id = message.chat.id client.kick_chat_member(chat_id, user.id) edit( - message, get_translation("banResult", ["**", user.first_name, user.id, "`"]) + message, get_translation('banResult', ['**', user.first_name, user.id, '`']) ) sleep(1) send_log( get_translation( - "banLog", - ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + 'banLog', + [user.first_name, user.id, message.chat.title, '`', chat_id], ) ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.unban", compat=False, private=False, admin=True) +@sedenify(pattern='^.unban', compat=False, private=False, admin=True) def unban_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -105,14 +103,14 @@ def unban_user(client, message): client.unban_chat_member(chat_id, user.id) edit( message, - get_translation("unbanResult", ["**", user.first_name, user.id, "`"]), + get_translation('unbanResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.kick", compat=False, private=False, admin=True) +@sedenify(pattern='^.kick', compat=False, private=False, admin=True) def kick_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -140,7 +138,7 @@ def kick_user(client, message): if user.id in BRAIN: return edit( message, - get_translation("brainError", ["`", "**", user.first_name, user.id]), + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -149,28 +147,27 @@ def kick_user(client, message): client.unban_chat_member(chat_id, user.id) edit( message, - get_translation("kickResult", ["**", user.first_name, user.id, "`"]), + get_translation('kickResult', ['**', user.first_name, user.id, '`']), ) sleep(1) send_log( get_translation( - "kickLog", + 'kickLog', [ - "**", user.first_name, user.id, message.chat.title, - "`", + '`', message.chat.id, ], ) ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.mute", compat=False, private=False, admin=True) +@sedenify(pattern='^.mute', compat=False, private=False, admin=True) def mute_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -198,7 +195,7 @@ def mute_user(client, message): if user.id in BRAIN: return edit( message, - get_translation("brainError", ["`", "**", user.first_name, user.id]), + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -208,21 +205,21 @@ def mute_user(client, message): sql.mute(chat_id, user.id) edit( message, - get_translation("muteResult", ["**", user.first_name, user.id, "`"]), + get_translation('muteResult', ['**', user.first_name, user.id, '`']), ) sleep(1) send_log( get_translation( - "muteLog", - ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + 'muteLog', + [user.first_name, user.id, message.chat.title, '`', chat_id], ) ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.unmute", compat=False, private=False, admin=True) +@sedenify(pattern='^.unmute', compat=False, private=False, admin=True) def unmute_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -253,14 +250,14 @@ def unmute_user(client, message): client.unban_chat_member(chat_id, user.id) edit( message, - get_translation("unmuteResult", ["**", user.first_name, user.id, "`"]), + get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.promote", admin=True, private=False, compat=False) +@sedenify(pattern='^.promote', admin=True, private=False, compat=False) def promote_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -273,14 +270,14 @@ def promote_user(client, message): rank = args except Exception: return edit(message, f'`{get_translation("banFailUser")}`') - elif " " not in args: + elif ' ' not in args: try: user = client.get_users(args) except Exception: return edit(message, f'`{get_translation("banFailUser")}`') elif args: try: - arr = args.split(" ", 1) + arr = args.split(' ', 1) user = client.get_users(arr[0]) rank = arr[1] except Exception: @@ -306,21 +303,21 @@ def promote_user(client, message): client.set_administrator_title(chat_id, user.id, rank) edit( message, - get_translation("promoteResult", ["**", user.first_name, user.id, "`"]), + get_translation('promoteResult', ['**', user.first_name, user.id, '`']), ) sleep(1) send_log( get_translation( - "promoteLog", - ["**", user.first_name, user.id, message.chat.title, "`", chat_id], + 'promoteLog', + [user.first_name, user.id, message.chat.title, '`', chat_id], ) ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.demote", compat=False, private=False, admin=True) +@sedenify(pattern='^.demote', compat=False, private=False, admin=True) def demote_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -359,14 +356,14 @@ def demote_user(client, message): ) edit( message, - get_translation("demoteResult", ["**", user.first_name, user.id, "`"]), + get_translation('demoteResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.pin$", compat=False, private=False, admin=True) +@sedenify(pattern='^.pin$', compat=False, private=False, admin=True) def pin_message(client, message): reply = message.reply_to_message @@ -379,13 +376,13 @@ def pin_message(client, message): client.pin_chat_message(chat_id, message_id) edit(message, f'`{get_translation("pinResult")}`') sleep(1) - send_log(get_translation("pinLog", ["**", message.chat.title, "`", chat_id])) + send_log(get_translation('pinLog', [message.chat.title, '`', chat_id])) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return -@sedenify(pattern="^.unpin$", compat=False, private=False, admin=True) +@sedenify(pattern='^.unpin$', compat=False, private=False, admin=True) def unpin_message(client, message): reply = message.reply_to_message chat_id = message.chat.id @@ -393,44 +390,44 @@ def unpin_message(client, message): try: client.unpin_chat_message(chat_id, reply.message_id) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return else: try: client.unpin_all_chat_messages(chat_id) except Exception as e: - edit(message, get_translation("banError", ["`", "**", e])) + edit(message, get_translation('banError', ['`', '**', e])) return message.delete() -@sedenify(pattern="^.(admins|bots|user(s|sdel))$", compat=False, private=False) +@sedenify(pattern='^.(admins|bots|user(s|sdel))$', compat=False, private=False) def get_users(client, message): - args = message.text.split(" ", 1) - users = args[0][1:5] == "user" - showdel = users and args[0][-3:] == "del" - bots = not users and args[0][1:5] == "bots" - admins = not bots and args[0][1:7] == "admins" + args = message.text.split(' ', 1) + users = args[0][1:5] == 'user' + showdel = users and args[0][-3:] == 'del' + bots = not users and args[0][1:5] == 'bots' + admins = not bots and args[0][1:7] == 'admins' - out = "" + out = '' if users: out = get_translation( - "userlist", + 'userlist', [ - "**", + '**', f'{get_translation("deleted") if showdel else ""}', - "`", + '`', message.chat.title, ], ) - filtr = "all" + filtr = 'all' elif admins: - out = get_translation("adminlist", ["**", "`", message.chat.title]) - filtr = "administrators" + out = get_translation('adminlist', ['**', '`', message.chat.title]) + filtr = 'administrators' elif bots: - out = get_translation("botlist", ["**", "`", message.chat.title]) - filtr = "bots" + out = get_translation('botlist', ['**', '`', message.chat.title]) + filtr = 'bots' try: chat_id = message.chat.id @@ -439,7 +436,7 @@ def get_users(client, message): if not i.user.is_deleted and showdel: continue name = f'[{get_translation("deletedAcc") if i.user.is_deleted else i.user.first_name}](tg://user?id={i.user.id}) | `{i.user.id}`' - out += f"\n`•` **{name}**" + out += f'\n`•` **{name}**' except Exception as e: out += f'\n{get_translation("banError", ["`", "**", e])}' @@ -447,18 +444,18 @@ def get_users(client, message): edit(message, out) except MessageTooLong: edit(message, f'`{get_translation("outputTooLarge")}`') - file = open("userslist.txt", "w+") + file = open('userslist.txt', 'w+') file.write(out) file.close() reply_doc( message, - "userslist.txt", + 'userslist.txt', caption=get_translation( - "userlist", + 'userlist', [ - "**", + '**', f'{get_translation("deleted") if showdel else ""}', - "`", + '`', message.chat.title, ], ), @@ -467,21 +464,21 @@ def get_users(client, message): ) -@sedenify(pattern="^.zombies", private=False, compat=False) +@sedenify(pattern='^.zombies', private=False, admin=True, compat=False) def zombie_accounts(client, message): args = extract_args(message).lower() chat_id = message.chat.id count = 0 msg = f'`{get_translation("zombiesNoAccount")}`' - if args != "clean": + if args != 'clean': edit(message, f'`{get_translation("zombiesFind")}`') for i in client.iter_chat_members(chat_id): if i.user.is_deleted: count += 1 sleep(1) if count > 0: - msg = get_translation("zombiesFound", ["**", "`", count]) + msg = get_translation('zombiesFound', ['**', '`', count]) return edit(message, msg) if not is_admin(message): @@ -505,17 +502,17 @@ def zombie_accounts(client, message): count += 1 if count > 0: - msg = get_translation("zombiesResult", ["**", "`", count]) + msg = get_translation('zombiesResult', ['**', '`', count]) if users > 0: - msg = get_translation("zombiesResult2", ["**", "`", count, users]) + msg = get_translation('zombiesResult2', ['**', '`', count, users]) edit(message, msg) sleep(2) message.delete() send_log( - get_translation("zombiesLog", ["**", "`", count, message.chat.title, chat_id]) + get_translation('zombiesLog', ['**', '`', count, message.chat.title, chat_id]) ) @@ -537,4 +534,4 @@ def mute_check(client, message): message.continue_propagation() -HELP.update({"admin": get_translation("adminInfo")}) +HELP.update({'admin': get_translation('adminInfo')}) diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/blacklist.py index 4f1677b..094ccc6 100644 --- a/sedenbot/modules/blacklist.py +++ b/sedenbot/modules/blacklist.py @@ -8,17 +8,25 @@ # from io import BytesIO -from re import escape, search, IGNORECASE +from re import IGNORECASE, escape, search from sedenbot import HELP, LOGS -from sedenecem.core import (edit, reply, send_log, reply_doc, - extract_args, sedenify, get_translation) +from sedenecem.core import ( + edit, + extract_args, + get_translation, + reply, + reply_doc, + sedenify, + send_log, +) def blacklist_init(): try: global sql from importlib import import_module + sql = import_module('sedenecem.sql.blacklist_sql') except Exception as e: sql = None @@ -57,8 +65,7 @@ def blacklist(message): message.delete() msg_removed = True except Exception: - reply( - message, f'`{get_translation("blacklistPermission")}`') + reply(message, f'`{get_translation("blacklistPermission")}`') sql.rm_from_blacklist(message.chat.id, snip.lower()) break @@ -75,8 +82,9 @@ def addblacklist(message): if len(text) < 1: edit(message, f'`{get_translation("blacklistText")}`') return - to_blacklist = list(set(trigger.strip() - for trigger in text.split("\n") if trigger.strip())) + to_blacklist = list( + set(trigger.strip() for trigger in text.split("\n") if trigger.strip()) + ) for trigger in to_blacklist: sql.add_to_blacklist(message.chat.id, trigger.lower()) edit(message, get_translation('blacklistAddSuccess', ['**', '`', text])) @@ -99,8 +107,9 @@ def showblacklist(message): if len(OUT_STR) > 4096: with BytesIO(str.encode(OUT_STR)) as out_file: out_file.name = 'blacklist.text' - reply_doc(message, out_file, - caption=f'**{get_translation("blacklistChats")}**') + reply_doc( + message, out_file, caption=f'**{get_translation("blacklistChats")}**' + ) message.delete() else: edit(message, OUT_STR) @@ -115,8 +124,9 @@ def rmblacklist(message): if len(text) < 1: edit(message, f'`{get_translation("blacklistText")}`') return - to_unblacklist = list(set(trigger.strip() - for trigger in text.split("\n") if trigger.strip())) + to_unblacklist = list( + set(trigger.strip() for trigger in text.split("\n") if trigger.strip()) + ) successful = 0 for trigger in to_unblacklist: if sql.rm_from_blacklist(message.chat.id, trigger.lower()): diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat.py index 7f598be..377e50e 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat.py @@ -8,15 +8,16 @@ # from time import sleep + from sedenbot import HELP, LOGS -from sedenecem.core import (edit, sedenify, extract_args, - send_log, get_translation) +from sedenecem.core import edit, extract_args, get_translation, sedenify, send_log def chat_init(): try: global sql from importlib import import_module + sql = import_module('sedenecem.sql.keep_read_sql') except Exception as e: sql = None diff --git a/sedenbot/modules/colors.py b/sedenbot/modules/colors.py index ba1947b..6dbf69e 100644 --- a/sedenbot/modules/colors.py +++ b/sedenbot/modules/colors.py @@ -8,10 +8,8 @@ # from PIL import Image, ImageColor - from sedenbot import HELP -from sedenecem.core import (edit, reply_img, extract_args, - sedenify, get_translation) +from sedenecem.core import edit, extract_args, get_translation, reply_img, sedenify @sedenify(pattern='^.color') @@ -32,7 +30,8 @@ def color(message): 'sedencik.png', caption=input_str, delete_file=True, - delete_orig=True) + delete_orig=True, + ) else: edit(message, f'`{get_translation("colorsUsage")}`') diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py index 5d66b03..362c411 100644 --- a/sedenbot/modules/covid19.py +++ b/sedenbot/modules/covid19.py @@ -1,55 +1,47 @@ -# Copyright (C) 2020 TeamDerUntergang. +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> # -# SedenUserBot is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. # -# SedenUserBot is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. +# All rights reserved. See COPYING, AUTHORS. # -from requests import get from json import loads -from bs4 import BeautifulSoup from re import sub +from bs4 import BeautifulSoup +from requests import get from sedenbot import HELP -from sedenecem.core import edit, sedenify, get_translation - -# Copyright (c) @frknkrc44 | 2020 +from sedenecem.core import edit, get_translation, sedenify -@sedenify(pattern="^.covid(|19)$") +@sedenify(pattern='^.covid(|19)$') def covid(message): + '''Copyright (c) @frknkrc44 | 2020''' try: req = get( - "https://covid19.saglik.gov.tr/", + 'https://covid19.saglik.gov.tr/', headers={ - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Language": "tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3", - "Cache-Control": "max-age=0", - "Connection": "keep-alive", - "Referer": "https://covid19.saglik.gov.tr/", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36", + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Accept-Language': 'tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3', + 'Cache-Control': 'max-age=0', + 'Connection': 'keep-alive', + 'Referer': 'https://covid19.saglik.gov.tr/', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36', }, ) - soup = BeautifulSoup(req.text, "html.parser") - scripts = soup.find_all("script") + soup = BeautifulSoup(req.text, 'html.parser') + scripts = soup.find_all('script') for script in scripts: turejq = str(script) - if "var sondurumjson" in turejq: + if 'var sondurumjson' in turejq: result = loads( sub( - "(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s)", - "", + '(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s)', + '', turejq, ) ) @@ -62,7 +54,7 @@ def covid(message): result = result[0] def del_dots(res): - return res.replace(".", "") + return res.replace('.', '') sonuclar = ( f'**{get_translation("covidData")}**\n' @@ -85,4 +77,4 @@ def del_dots(res): edit(message, sonuclar) -HELP.update({"covid19": get_translation("covidInfo")}) +HELP.update({'covid19': get_translation('covidInfo')}) diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index 9915adc..4954884 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -9,11 +9,17 @@ from os import remove from random import randint, uniform -from PIL import Image, ImageEnhance, ImageOps +from PIL import Image, ImageEnhance, ImageOps from sedenbot import HELP -from sedenecem.core import (edit, reply_img, sedenify, parse_cmd, - download_media_wc, get_translation) +from sedenecem.core import ( + download_media_wc, + edit, + get_translation, + parse_cmd, + reply_img, + sedenify, +) @sedenify(pattern='^.(deepf|f)ry') @@ -45,8 +51,10 @@ def deepfry(message): edit(message, f'`{get_translation("deepfryError")}`') return else: - edit(message, get_translation('deepfryNoPic', - ['`', f'{"f" if fry else "deepf"}ry'])) + edit( + message, + get_translation('deepfryNoPic', ['`', f'{"f" if fry else "deepf"}ry']), + ) return # Download Media @@ -56,8 +64,7 @@ def deepfry(message): remove(image_file) # Apply effect to media - edit(message, get_translation( - 'deepfryApply', ['`', f'{"" if fry else "deep"}'])) + edit(message, get_translation('deepfryApply', ['`', f'{"" if fry else "deep"}'])) for _ in range(frycount): image = deepfry_media(image, fry) @@ -73,33 +80,33 @@ def deepfry_media(img: Image, fry: bool) -> Image: if fry: colors = ( (randint(50, 200), randint(40, 170), randint(40, 190)), - (randint(190, 255), randint(170, 240), randint(180, 250)) + (randint(190, 255), randint(170, 240), randint(180, 250)), ) - # Resim formatı ayarla + # Set image format img = img.copy().convert('RGB') width, height = img.width, img.height - temp_num = uniform(.8, .9) if fry else .75 - img = img.resize((int(width ** temp_num), - int(height ** temp_num)), - resample=Image.LANCZOS) + temp_num = uniform(0.8, 0.9) if fry else 0.75 + img = img.resize( + (int(width ** temp_num), int(height ** temp_num)), resample=Image.LANCZOS + ) - temp_num = uniform(.85, .95) if fry else .88 - img = img.resize((int(width ** temp_num), - int(height ** temp_num)), - resample=Image.BILINEAR) + temp_num = uniform(0.85, 0.95) if fry else 0.88 + img = img.resize( + (int(width ** temp_num), int(height ** temp_num)), resample=Image.BILINEAR + ) - temp_num = uniform(.89, .98) if fry else .9 - img = img.resize((int(width ** temp_num), - int(height ** temp_num)), - resample=Image.BICUBIC) + temp_num = uniform(0.89, 0.98) if fry else 0.9 + img = img.resize( + (int(width ** temp_num), int(height ** temp_num)), resample=Image.BICUBIC + ) img = img.resize((width, height), resample=Image.BICUBIC) temp_num = randint(3, 7) if fry else 4 img = ImageOps.posterize(img, temp_num) - # Renk yerleşimi oluştur + # Create a color scheme overlay = img.split()[0] temp_num = uniform(1.0, 2.0) if fry else 2 @@ -109,13 +116,11 @@ def deepfry_media(img: Image, fry: bool) -> Image: overlay = ImageEnhance.Brightness(overlay).enhance(temp_num) overlay = ImageOps.colorize( - overlay, - colors[0] if fry else (254, 0, 2), - colors[1] if fry else (255, 255, 15) + overlay, colors[0] if fry else (254, 0, 2), colors[1] if fry else (255, 255, 15) ) - # Kırmızı ve sarıyı ana görüntüye yerleştir ve keskinleştir - temp_num = uniform(0.1, 0.4) if fry else .75 + # Place red and yellow in image and sharpen + temp_num = uniform(0.1, 0.4) if fry else 0.75 img = Image.blend(img, overlay, temp_num) temp_num = randint(5, 300) if fry else 100 @@ -134,8 +139,11 @@ def check_media(reply_message): data = True elif reply_message.document: name = reply_message.document.file_name - if name and '.' in name and name[name.find( - '.') + 1:] in ['png', 'jpg', 'jpeg', 'webp']: + if ( + name + and '.' in name + and name[name.find('.') + 1 :] in ['png', 'jpg', 'jpeg', 'webp'] + ): data = True return data diff --git a/sedenbot/modules/deezer.py b/sedenbot/modules/deezer.py index 3484204..6499752 100644 --- a/sedenbot/modules/deezer.py +++ b/sedenbot/modules/deezer.py @@ -8,10 +8,8 @@ # from deethon import Session - -from sedenbot import HELP, DEEZER_TOKEN -from sedenecem.core import (sedenify, extract_args, edit, - get_translation, reply_audio) +from sedenbot import DEEZER_TOKEN, HELP +from sedenecem.core import edit, extract_args, get_translation, reply_audio, sedenify @sedenify(pattern='^.deezer') diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index 74f3f67..4d4fe62 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -7,15 +7,14 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import findall, sub from json import JSONDecodeError +from re import findall, sub from urllib.parse import unquote, urlparse -from bs4 import BeautifulSoup -from requests import get, Session +from bs4 import BeautifulSoup +from requests import Session, get from sedenbot import HELP -from sedenecem.core import (edit, extract_args, sedenify, - get_webdriver, get_translation) +from sedenecem.core import edit, extract_args, get_translation, get_webdriver, sedenify @sedenify(pattern=r'^.direct') @@ -38,7 +37,7 @@ def check(url, items, starts=False): return url.startswith(items) if starts else items in url for item in items: - if (url.startswith(item) if starts else item in url): + if url.startswith(item) if starts else item in url: return True return False @@ -84,8 +83,7 @@ def zippy_share(link: str) -> str: size = font[4].text button = driver.find_element_by_xpath('//a[contains(@id, "dlbutton")]') link = button.get_attribute('href') - reply += '{}\n'.format(get_translation('directZippyLink', - [name, size, link])) + reply += '{}\n'.format(get_translation('directZippyLink', [name, size, link])) driver.quit() return reply @@ -118,21 +116,24 @@ def sourceforge(link: str) -> str: file_path = findall(r'files(.*)/download', link)[0] reply = f"Mirrors for __{file_path.split('/')[-1]}__\n" project = findall(r'projects?/(.*?)/files', link)[0] - mirrors = f'https://sourceforge.net/settings/mirror_choices?' \ + mirrors = ( + f'https://sourceforge.net/settings/mirror_choices?' f'projectname={project}&filename={file_path}' + ) page = BeautifulSoup(get(mirrors).content, 'html.parser') info = page.find('ul', {'id': 'mirrorList'}).findAll('li') for mirror in info[1:]: name = findall(r'\((.*)\)', mirror.text.strip())[0] - dl_url = f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' + dl_url = ( + f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' + ) reply += f'[{name}]({dl_url}) ' return reply def osdn(link: str) -> str: osdn_link = 'https://osdn.net' - page = BeautifulSoup( - get(link, allow_redirects=True).content, 'html.parser') + page = BeautifulSoup(get(link, allow_redirects=True).content, 'html.parser') info = page.find('a', {'class': 'mirror_link'}) link = unquote(osdn_link + info['href']) reply = f"Mirrors for __{link.split('/')[-1]}__\n" @@ -175,11 +176,7 @@ def androidfilehost(link: str) -> str: 'authority': 'androidfilehost.com', 'x-requested-with': 'XMLHttpRequest', } - data = { - 'submit': 'submit', - 'action': 'getdownloadmirrors', - 'fid': f'{fid}' - } + data = {'submit': 'submit', 'action': 'getdownloadmirrors', 'fid': f'{fid}'} mirrors = None reply = f'URL: {link}\n' error = f'`{get_translation("mirrorError")}`\n' @@ -188,7 +185,8 @@ def androidfilehost(link: str) -> str: 'https://androidfilehost.com/libs/otf/mirrors.otf.php', headers=headers, data=data, - cookies=res.cookies) + cookies=res.cookies, + ) mirrors = req.json()['MIRRORS'] except (JSONDecodeError, TypeError): reply += error diff --git a/sedenbot/modules/dogbin.py b/sedenbot/modules/dogbin.py index 36e32f8..cd1ba30 100644 --- a/sedenbot/modules/dogbin.py +++ b/sedenbot/modules/dogbin.py @@ -8,10 +8,10 @@ # from os import remove -from requests import get, post, exceptions -from sedenbot import HELP, DOWNLOAD_DIRECTORY -from sedenecem.core import edit, extract_args, sedenify, get_translation +from requests import exceptions, get, post +from sedenbot import DOWNLOAD_DIRECTORY, HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify DOGBIN_URL = 'https://del.dog/' @@ -29,7 +29,7 @@ def paste(client, message): if match: dogbin = match elif reply_id: - dogbin = (message.reply_to_message) + dogbin = message.reply_to_message if dogbin.media: downloaded_file_name = client.download_media( dogbin, @@ -55,11 +55,10 @@ def paste(client, message): if response['isUrl']: reply_text = get_translation( - 'dogbinPasteResult2', [ - '`', dogbin_final_url, f'{DOGBIN_URL}v/{key}']) + 'dogbinPasteResult2', ['`', dogbin_final_url, f'{DOGBIN_URL}v/{key}'] + ) else: - reply_text = get_translation( - 'dogbinPasteResult', ['`', dogbin_final_url]) + reply_text = get_translation('dogbinPasteResult', ['`', dogbin_final_url]) else: reply_text = f'`{get_translation("dogbinReach")}`' @@ -79,11 +78,11 @@ def getpaste(message): format_view = f'{DOGBIN_URL}v/' if dogbin.startswith(format_view): - dogbin = dogbin[len(format_view):] + dogbin = dogbin[len(format_view) :] elif dogbin.startswith(format_normal): - dogbin = dogbin[len(format_normal):] + dogbin = dogbin[len(format_normal) :] elif dogbin.startswith('del.dog/'): - dogbin = dogbin[len('del.dog/'):] + dogbin = dogbin[len('del.dog/') :] else: edit(message, f'`{get_translation("dogbinUrlError")}`') return @@ -99,8 +98,7 @@ def getpaste(message): edit(message, get_translation('dogbinTimeOut', [str(TimeoutErr)])) return except exceptions.TooManyRedirects as RedirectsErr: - edit(message, get_translation( - 'dogbinTooManyRedirects', [str(RedirectsErr)])) + edit(message, get_translation('dogbinTooManyRedirects', [str(RedirectsErr)])) return reply_text = get_translation('dogbinResult', ['`', resp.text]) diff --git a/sedenbot/modules/ecem.py b/sedenbot/modules/ecem.py index 662a3af..feee00b 100644 --- a/sedenbot/modules/ecem.py +++ b/sedenbot/modules/ecem.py @@ -10,216 +10,371 @@ from random import choice from sedenbot import HELP -from sedenecem.core import edit, sedenify, get_translation +from sedenecem.core import edit, get_translation, sedenify + # ================= CONSTANT ================= ECEM_STRINGS = [ "Çocukluk aşkımsın", "Erkek arkadaşlarımın arkadaşlarının adlarını bilmem normal bir şey", "Şu an acele işim var, daha sonra çekilsek olmaz mı? Zaten yine geleceğim.", - "Yok kızmadım", "Bunun farkına vardıysan benim için artık problem yok", + "Yok kızmadım", + "Bunun farkına vardıysan benim için artık problem yok", "Bundan sonra artık hayatında Ecem diye biri yok!", "Yağmurlu havada sweatini veren arkadaşlarınız olsun 👅", "Yalnız çiçek açar mısın? Yalnız, çiçek.", - "Affetmiyorum, annemi çağıracağım. O gereğini yapacak.", "Saçımla oynama", + "Affetmiyorum, annemi çağıracağım. O gereğini yapacak.", + "Saçımla oynama", "Saçımla oynamamanı söylediğim halde oynuyorsun. Arkamdan git lütfen.", "Ben gelmiyorum bedene falan, kenarda ölmeyi bekleyeceğim", "Düşman değiliz sadece onun olduğu yerde rahat edemem", "Git arkamdan, istemiyorum seni", "Murat Boz şarkıları beni hiç eğlendirmiyor", - "Tövbe tövbe kapatın şunu çarpılacağız", "Üç kızım olsun istiyorum", + "Tövbe tövbe kapatın şunu çarpılacağız", + "Üç kızım olsun istiyorum", "Eğer onunla evlenirsem nikah şahidim sen ol", - "Ehehehe çok komik, hadi defolun gidin", "Gidip onunla konuşsana?", + "Ehehehe çok komik, hadi defolun gidin", + "Gidip onunla konuşsana?", "Ne oldu sana? Anlatmak ister misin?", "Lütfen anlat işte. Çok mu özel ki?", - "Hadi gel erkeklerin yanına geçelim", "İyi misin?", "Teşekkür ederim 😊", + "Hadi gel erkeklerin yanına geçelim", + "İyi misin?", + "Teşekkür ederim 😊", "Daha sonra yine geleceğim, o zaman çekiliriz, olmaz mı?", "Grubu dağıtıyoruz gençler", "Grubu dağıtıyoruz gençler??? Ne ara dedim onu ya ben?", - "Allah belanı vermesin", "Hadi yönetici sensin, dağıt grubu", - "İyi akşamlar", "Her şeyi ben söylüyorum, biraz da siz söyleyin", + "Allah belanı vermesin", + "Hadi yönetici sensin, dağıt grubu", + "İyi akşamlar", + "Her şeyi ben söylüyorum, biraz da siz söyleyin", "Ben onu esprisine demiştim diyette değilim", "Sizde var mı bilmiyorum sorim dedim birkaç kişiye daha sordum, çıktı alabilme şansın var mı?", - "Üçgende çıkmış sorular, 3. site", "Eğer olmadıysa gerek kalmadı", - "Yok yani, ben buldum birinden", "Evet evet hallettim ben sağol", + "Üçgende çıkmış sorular, 3. site", + "Eğer olmadıysa gerek kalmadı", + "Yok yani, ben buldum birinden", + "Evet evet hallettim ben sağol", "Atim sen de kontrol et bir, o mu diye", - "Neyse sen çıkarttığını da getirirsin olmadı", "Kim ne dedi sana?", + "Neyse sen çıkarttığını da getirirsin olmadı", + "Kim ne dedi sana?", "Onlar benim kardeşim gibi olduğu için sahipleniyorlar", "Kötü bir durum yok", "Bir anda yapınca rahatsız oldum ama sonra sana zaten sorun yok dedim", - "Onlar da biliyor, ondan tepki vermiştir", "Sorun yok yani", "Rica ederim", + "Onlar da biliyor, ondan tepki vermiştir", + "Sorun yok yani", + "Rica ederim", "Teşekkür ederim kusura bakma telefonum bozuktu yeni görebildim 😊", "Evet. Yalan borcum mu var size? Evet, yalan borcum mu var? Yalan borcum mu var?", - "Ne malsın ya", "Testleri bitirmeyin Allah aşkına", - "Sen mesajı silsen de bildirimde gözüküyor", "O ne demişti ki?", + "Ne malsın ya", + "Testleri bitirmeyin Allah aşkına", + "Sen mesajı silsen de bildirimde gözüküyor", + "O ne demişti ki?", "Ben birine bir şey yazmştım, sonra sildim. Ne yazdın dedi, söylemedim. Sonra yazdığım şeyi bildirimden açıp bana yazdı.", "Mzkddkslslldldldldld", "Toplu olarak yapıyoruz değil mi, hiç hoş olmayan şeyler çıkmasın sonra.", - "Lan sen konuşma", "Abi yalan değil ki, yetişmiyor", - "Ya hep mi korktuğum başıma gelir ya?", "Bende şans olsa zaten", + "Lan sen konuşma", + "Abi yalan değil ki, yetişmiyor", + "Ya hep mi korktuğum başıma gelir ya?", + "Bende şans olsa zaten", "Ben oyuna falan katılamam", "Tövbe, hep olmaması gereken sınıfları söylüyorsun", - "Neyse ben konuşmuyorum sizinle zaten", "Ne uzattınız ya, değişmiş işte", - "Sen sus, seninle konuşan yok", "Çok özledim 😔 ♥️", + "Neyse ben konuşmuyorum sizinle zaten", + "Ne uzattınız ya, değişmiş işte", + "Sen sus, seninle konuşan yok", + "Çok özledim 😔 ♥️", "Saygı, minnet ve özlemle", - "Canım, dayım, her şeyim yolun açık olsun askerim ♥️", "#NoFilter", - "Yorgunum ve ağrılar", "Sıkıldım ya, konuşun. Söz valla terslemeyeceğim.", + "Canım, dayım, her şeyim yolun açık olsun askerim ♥️", + "#NoFilter", + "Yorgunum ve ağrılar", + "Sıkıldım ya, konuşun. Söz valla terslemeyeceğim.", "Çok kaptırmış kendini o", "Okul mal o zaman, o kadar yanlışla 11. olduysam", - "Kime diyorsunuz, ben bir şey anlamadım", "Sen hani hastaydın Lan", + "Kime diyorsunuz, ben bir şey anlamadım", + "Sen hani hastaydın Lan", "Sen ne diyon ya sksödödödöföfçgçgçhçhş", "Hiç bu kadar güldüğümü hatırlamıyorum", "Kaç dakika uğraştın, gerizekalı ya", "Lan sus sende sabahtan beri Ecem Ecem dmdkdmdmdmföfögö", - "Evde gerizekalı gibi gülüyorum susun artık", "Tövbe yarabbim", - "Üstüme gitmeyin şu saatlerde benim", "Sakn ol raki", - "5 saattir anlamaya çalışıyor", "Yeter, gidin yatın", "Sus lan mal", - "Gahahaha diye gülenin dediğine bak", "Yarın hiç hoş olmayacaksın", + "Evde gerizekalı gibi gülüyorum susun artık", + "Tövbe yarabbim", + "Üstüme gitmeyin şu saatlerde benim", + "Sakn ol raki", + "5 saattir anlamaya çalışıyor", + "Yeter, gidin yatın", + "Sus lan mal", + "Gahahaha diye gülenin dediğine bak", + "Yarın hiç hoş olmayacaksın", "Sinirliyim zaten, uğraşmayın benimle sjdkdkkdmfgm", "Bu arada yüzümü telefon gözükmesin diye öyle tutmuyordum sebebini bilmiyorum djjd", "Doğum günü kızıyım ben şşş. Nasıl bir değişiklik bekliyordunuz dkdkdk", - "İnternetten baksana", "Müzik dersi hangi gün? Hocaya söz vermiştim.", + "İnternetten baksana", + "Müzik dersi hangi gün? Hocaya söz vermiştim.", "Hem arkamdan kaşar diyorsun, hem de kardeşim diyorsun", - "Böyle böyle yürüyor ya ahahahahahahaha", "Her an'ımda", "BFF♥️", + "Böyle böyle yürüyor ya ahahahahahahaha", + "Her an'ımda", + "BFF♥️", "Yine, yeni, yeniden ♥️", "Buraya sığdıramayacağım milyon tane iyi anımız var, hep de olsun.", "İyi ki doğdun kız kardeşim, seni çok seviyorum ♥️", "Best gün ilan ediyorum ♥️", - "Beraber uyuyoruz ama yine de sen bilirsin sjskskksksks️", "Özledik be️", - "Ula 😔", "Konum yeterli", "Yazdan kalmalarla avunmaya devam", + "Beraber uyuyoruz ama yine de sen bilirsin sjskskksksks️", + "Özledik be️", + "Ula 😔", + "Konum yeterli", + "Yazdan kalmalarla avunmaya devam", "Kanka ben zaten tetikteyim ağlamak için yapma bak yanarız️", "Ben de çok özledim nolacak şimdi", "Kajsjskskskskdksksdsjjsksks istemiyom bırak beni️", "O olsaydı iki güldürür kendime gelirdim kafayı yicem dayanamıyorum️", - "Hemde nasılll", "😔 her seferinde düşürür", "Yaz gelsin artıkkkkk", - "YERİM SENİ", "Sen niye beni sinirlendiriyorsun 😠😠", "Sensin 1.55", + "Hemde nasılll", + "😔 her seferinde düşürür", + "Yaz gelsin artıkkkkk", + "YERİM SENİ", + "Sen niye beni sinirlendiriyorsun 😠😠", + "Sensin 1.55", "Bak aferin nasıl biliyor", - "Gevezelik yapma sjsjkskskskskkslslsksksksklsks", "Güldür güldür", - "Allah allah çok özledim bir şaşırdım", "Ne demek 2 yıl geçmiş", - "Her şeyi anladım da Çin ne sjdjdkmdmxmxmmxmxkc", "Her yere atacam", + "Gevezelik yapma sjsjkskskskskkslslsksksksklsks", + "Güldür güldür", + "Allah allah çok özledim bir şaşırdım", + "Ne demek 2 yıl geçmiş", + "Her şeyi anladım da Çin ne sjdjdkmdmxmxmmxmxkc", + "Her yere atacam", "Işık bulursam 37478383 tane fotoğraf çekilirim mutlaka", - "Aşkım olduğunu söylemiş miydim ??", "Ya salak mısın sjdjdkdkkdkdkdkdkd", + "Aşkım olduğunu söylemiş miydim ??", + "Ya salak mısın sjdjdkdkkdkdkdkdkd", "Bana bu kadar kilo aldıran hayat size neler yapmaz - '2018", - "Hatırla çabuk", "Ayyyyyyy 😳 ♥️", + "Hatırla çabuk", + "Ayyyyyyy 😳 ♥️", "Biz bunu hangi kafayla çekilmişiz asksksksllslsldkd", "Asıl kesmeseydim o zaman küserdik sjskkdkdksksmdk", "Olay benim kilomdan buraya nasıl geldi", - "Kanka beni bir yıldır görmüyorsun ya hani", "Salın artık bizi", "Çatalla", - "Diyete başlıyorum konu kapanmıştır", "Bakılır neden olmasınn", + "Kanka beni bir yıldır görmüyorsun ya hani", + "Salın artık bizi", + "Çatalla", + "Diyete başlıyorum konu kapanmıştır", + "Bakılır neden olmasınn", "Main storyden vazgeçilmiştir", "Bilen bilir mutlu olduğum dönemler - işte anca 2018 -", - "Yorum yapmıyorum artıkkk", "Az dalgası dönmedi şu aynanın 👅", "Hadi bay", - "Aşırı aşırı aşırı özledim", "Hikayede kalmasın", - "Bulduğu her yerde fotoğraf çekmeyen de ne bilim", "Nasıl orada havalar", + "Yorum yapmıyorum artıkkk", + "Az dalgası dönmedi şu aynanın 👅", + "Hadi bay", + "Aşırı aşırı aşırı özledim", + "Hikayede kalmasın", + "Bulduğu her yerde fotoğraf çekmeyen de ne bilim", + "Nasıl orada havalar", "Sana sahip olduğum için çok şanslıyım", - "En acilinden bu günlere geri dönebilir miyiz", "Buralar artık benim", - "Seni ve uzun saçlarımı özledim 😔", "Sonunda kauvştuk ♥️", - "Sizi bile özledim 😔", "Allahım yine çok mutluyum", - "1 yıl önce mutluymuşum", "Yaaaaaa 😔 ♥️", "HER ŞEYİİİM ❤️💜🧡🖤💗💙💖💕💝💛💘♥💓", + "En acilinden bu günlere geri dönebilir miyiz", + "Buralar artık benim", + "Seni ve uzun saçlarımı özledim 😔", + "Sonunda kauvştuk ♥️", + "Sizi bile özledim 😔", + "Allahım yine çok mutluyum", + "1 yıl önce mutluymuşum", + "Yaaaaaa 😔 ♥️", + "HER ŞEYİİİM ❤️💜🧡🖤💗💙💖💕💝💛💘♥💓", "Hikayede kalmasın serisinden devam", - "Abi ben niye her fotoğrafta farklı çıkıyorum", "Benim küçük sevgilim ♥️", + "Abi ben niye her fotoğrafta farklı çıkıyorum", + "Benim küçük sevgilim ♥️", "Göz altı morluklarım ve ben bunları hak etmedik", "Sjskdksksjdksl gergin olmadığım tek bir saniye yok", "Gözlerimden yorgunluk akmadığı bir gün bile yok", "Yaz geri gelsin ve biz her gün içelim", "Köpeğin yılışıklığı yüzünden kavga ettiğimiz günlerden biri djjdjdjd", - "Bu da senin için skskkdkdkdlskskd", "Ne demiş kumarda kazanan", + "Bu da senin için skskkdkdkdlskskd", + "Ne demiş kumarda kazanan", "SJSKKDKSKSKSKS DÜNYANIN EN DELİ ARKADAŞLARI BENDE iyi ki varsınız ♥️♥️♥️♥️", - "Canlarım asla normal duramazlar ♥️♥️♥️♥️", "Anasının kuzusuuuu", - "Barbie değil harbi", "Özledim be bu zamanları", + "Canlarım asla normal duramazlar ♥️♥️♥️♥️", + "Anasının kuzusuuuu", + "Barbie değil harbi", + "Özledim be bu zamanları", "9. sınıf, canım İzmir 😔 ♥️", - "377383843. Deneme sanırım neysss bugünlük yeterr", "Çokçokçok özledimm", - "Post olmaya hak kazandı", "Birthday baby ♥️♥️", + "377383843. Deneme sanırım neysss bugünlük yeterr", + "Çokçokçok özledimm", + "Post olmaya hak kazandı", + "Birthday baby ♥️♥️", "Daha haklı bir tweet görmedim", "Bu dediğime kendim bile inanamıyorum ama çok özlediiim ♥️♥️♥️", - "Sanırım eski saçımı özledim", "10 yıl da geçse aynı espriye devamke", - "Başladık yine", "Sosyal medyaya sjskskskdkkdkdkdk", "Bir ara ben de", - "Yeter laaan", "Sjskjdkdkskskskskkskks", - "Ben de nerde kaldın diye merak ettim", "Ararsın bu günleri", "GN", "gn", - "Dünyanın en tatlı varlığı", "Ya hahahahahahahahaha o benim tek aşkım", - "Hadi İzmire", "Buyrun Seden hanım", - "Arkada yeni farketmem djskskskskskkssksk", "Okulun güzel yanı", - "Kes önce benim mesajlarıma bak sen", "Sonunda kavuştuk", - "Kuzeniz çünkü 👅😘♥", "Şşş", "Abartmaya bayılıyorsun", "İg", - "BARIŞALIM ARTIK", "Aşkım ♥♥♥", "Geceden kalan", - "Oooo kanka büyük laf soktun o nası laf sokmak", "Canımmsınn♥", - "Djdkdkkd çektirenler utansın", "Bebeğiiiiim♥♥♥♥♥", "Goodnighttttt♥♥♥♥♥", - "Aşk hayatımın özeti", "Bağışıklık yapınca böyle oluyor", "Aşkımmm♥♥♥♥♥", - "İyi gecelerrr", "Tecrübe konuşuyor", "Ben de 😔😔♥♥", - "Sevdaaaa çiçeğiim♥♥♥", "Bu da senin için skskkdkdkdlskskd", - "Ksnkskzksls ♥♥♥♥", "Hasta ve yorgun", "Ksjsksksksksks", "Canlarımmm♥♥♥♥", - "Msmsksks", "Bir konserimiz daha yok mu ama", "Aşkımaşkımm♥♥", "Aynen", + "Sanırım eski saçımı özledim", + "10 yıl da geçse aynı espriye devamke", + "Başladık yine", + "Sosyal medyaya sjskskskdkkdkdkdk", + "Bir ara ben de", + "Yeter laaan", + "Sjskjdkdkskskskskkskks", + "Ben de nerde kaldın diye merak ettim", + "Ararsın bu günleri", + "GN", + "gn", + "Dünyanın en tatlı varlığı", + "Ya hahahahahahahahaha o benim tek aşkım", + "Hadi İzmire", + "Buyrun Seden hanım", + "Arkada yeni farketmem djskskskskskkssksk", + "Okulun güzel yanı", + "Kes önce benim mesajlarıma bak sen", + "Sonunda kavuştuk", + "Kuzeniz çünkü 👅😘♥", + "Şşş", + "Abartmaya bayılıyorsun", + "İg", + "BARIŞALIM ARTIK", + "Aşkım ♥♥♥", + "Geceden kalan", + "Oooo kanka büyük laf soktun o nası laf sokmak", + "Canımmsınn♥", + "Djdkdkkd çektirenler utansın", + "Bebeğiiiiim♥♥♥♥♥", + "Goodnighttttt♥♥♥♥♥", + "Aşk hayatımın özeti", + "Bağışıklık yapınca böyle oluyor", + "Aşkımmm♥♥♥♥♥", + "İyi gecelerrr", + "Tecrübe konuşuyor", + "Ben de 😔😔♥♥", + "Sevdaaaa çiçeğiim♥♥♥", + "Bu da senin için skskkdkdkdlskskd", + "Ksnkskzksls ♥♥♥♥", + "Hasta ve yorgun", + "Ksjsksksksksks", + "Canlarımmm♥♥♥♥", + "Msmsksks", + "Bir konserimiz daha yok mu ama", + "Aşkımaşkımm♥♥", + "Aynen", "Ya defol git djdkdmfmgöögmdmdmdmd", "Lan tamam bin pişman ettiniz ya sjskdkkskskslsmskldmdlddk", - "Seri üzgün sjkdkdkdksls", "Bir oyun kazandık onu da çok gördünüz", + "Seri üzgün sjkdkdkdksls", + "Bir oyun kazandık onu da çok gördünüz", "Benim sayemde", "Bütün maçları bana sorup oynuyon sonra bir de benim privimde artisli yapıyorsun ", - "Ksksksksksksksksks", "SHJSJSKDKDKSKDK", + "Ksksksksksksksksks", + "SHJSJSKDKDKSKDK", "Yeter uyuyun hadi dersiniz var sabah", "kdkdkdkdkdkdmdmdmdm lan sınıfta öksürmekten canım çıktı bu kadarı da fazla sjskkfksks", "Tamam", "Sjkdkdkdkdlskdkxkdkdkdld şela en son bir kus istersen demişti ama yine de sen bilirsin", - "SJDKFKLGLDKDKDLSLDKDKDKDLDLDLLD", "Toplu igg", "Hatırım kalır", - "Bir dahakine kafamı kırmayın ama", "NSSNMDMDMDMSMSMDM", - "Nsnsmsmdmdmdmskkdms", "İyi kiii♥♥♥♥", + "SJDKFKLGLDKDKDLSLDKDKDKDLDLDLLD", + "Toplu igg", + "Hatırım kalır", + "Bir dahakine kafamı kırmayın ama", + "NSSNMDMDMDMSMSMDM", + "Nsnsmsmdmdmdmskkdms", + "İyi kiii♥♥♥♥", "Senin sesin yüzünden sesi kapalı attım haykırmışsın arkadaş gibi arkadaş kskskskskkdmsksmsmsmmdmddk", "Dağa taşa yazacan yakında bir olsaydık şu sınavı skskdkdkksksks", "Düşünsene yarın hoca sayıları değiştiriyor", "Kalırım sjjdjfjdkskskdkdkdkkdksks", "Olay beraber okumak değilmiş abi beraber geçirdiğimiz ortammış", - "Skskskksks sadece bunlar var elimdee ♥♥", "Kordonun dili olsa da konuşsa", - "Bende serisi var sjsjskdmmsksks", "Abartttttt", - "Babuş açı iyi bir de biz de zayıftık bir zamanlar", "Tabi koçç", - "Yuh 2 sene önceydi o", "Aslan 2 ay sonra barıştık biz onun üzerinden", - "Djdjdkkdkdkdkd çünkü o Seden", "Uğraşmaaa", "Yayaya♥♥♥", + "Skskskksks sadece bunlar var elimdee ♥♥", + "Kordonun dili olsa da konuşsa", + "Bende serisi var sjsjskdmmsksks", + "Abartttttt", + "Babuş açı iyi bir de biz de zayıftık bir zamanlar", + "Tabi koçç", + "Yuh 2 sene önceydi o", + "Aslan 2 ay sonra barıştık biz onun üzerinden", + "Djdjdkkdkdkdkd çünkü o Seden", + "Uğraşmaaa", + "Yayaya♥♥♥", "Birthday baby♥♥ -acil fotoğraf @sedenogen", - "Sjsjdjjsksksks özlediğime değil ÇOK özlediğime", "Yaaaaa", - "Deme bak gider boyatırım", "Sen kesinlikle benim ilerideki çocuğumsun", - "Ayyyyyyy♥♥", "Bu fotoğrafta çöktüğümü fark etmişimdir", - "Current mood: DAY OFF!", "Sıkıntıdan patlamama ramak kaldı", + "Sjsjdjjsksksks özlediğime değil ÇOK özlediğime", + "Yaaaaa", + "Deme bak gider boyatırım", + "Sen kesinlikle benim ilerideki çocuğumsun", + "Ayyyyyyy♥♥", + "Bu fotoğrafta çöktüğümü fark etmişimdir", + "Current mood: DAY OFF!", + "Sıkıntıdan patlamama ramak kaldı", "Bir bihter ziyagil değildik ama biz de çok acı çektik sjjsjsjskd", - "Efektten bıkana kadar devam", "Şov başlasın dedi", - "Bir pijamamı bir de seni♥", "Yeni yıla evde tek başına girmek mi 0/10", - "Seri yorgun", "10/10", "Black is my favorite color", - "Çok şükür bugün de yorgunluktan öldük", "Best couple", "İggg♥", - "Definitely Tired", "Good night", "Saç kestirmek net pişmanlıktır", - "Güne puanım 0", "Teyzoşuuuum♥", "Aşşkııım♥♥♥", "Sensin en güzelllll♥♥♥", - "Bebeğiiim♥", "Bir kere ya bir kere güzel bir şey at", "Canımm♥", - "Aşkımmm♥", "Ben de seni çokk seviyorum birtanem♥", - "Emoji yanlış oldu sanırım 👅 ♥", "Teşekkürler ♥", "Birtanemm♥", - "Hem de çokk♥", "En özell♥", "Balll♥", "Seviyorum seniii♥", - "Psikolojik tedavi için dm", "Çözüyormuş gibi çek knk", + "Efektten bıkana kadar devam", + "Şov başlasın dedi", + "Bir pijamamı bir de seni♥", + "Yeni yıla evde tek başına girmek mi 0/10", + "Seri yorgun", + "10/10", + "Black is my favorite color", + "Çok şükür bugün de yorgunluktan öldük", + "Best couple", + "İggg♥", + "Definitely Tired", + "Good night", + "Saç kestirmek net pişmanlıktır", + "Güne puanım 0", + "Teyzoşuuuum♥", + "Aşşkııım♥♥♥", + "Sensin en güzelllll♥♥♥", + "Bebeğiiim♥", + "Bir kere ya bir kere güzel bir şey at", + "Canımm♥", + "Aşkımmm♥", + "Ben de seni çokk seviyorum birtanem♥", + "Emoji yanlış oldu sanırım 👅 ♥", + "Teşekkürler ♥", + "Birtanemm♥", + "Hem de çokk♥", + "En özell♥", + "Balll♥", + "Seviyorum seniii♥", + "Psikolojik tedavi için dm", + "Çözüyormuş gibi çek knk", "Yalan söyleme dakika sayıyordun bitsin diye", "Seninle arkada marş söyleyip çerez yemeği özledim", "Gözümü galatasaray marşıyla açıyordum sjsjdkdkdkkdkd", - "Aferin böyle yola gel sjskkfskdkfdkkdkddk", "Gn priv ailemm♥️♥️♥️", - "Uyumuyorum çaktırma sjskkskdkdkd", "Kara gözlüm ölesim var", - "Bu bağlantıyı kimse anlamayacak sjsksksksksk", "Yapma yanarız", - "skamaksksksksksks", "inşallahh bence de görelim artık", - "hem de nasılllll", "Konumumuz belli", + "Aferin böyle yola gel sjskkfskdkfdkkdkddk", + "Gn priv ailemm♥️♥️♥️", + "Uyumuyorum çaktırma sjskkskdkdkd", + "Kara gözlüm ölesim var", + "Bu bağlantıyı kimse anlamayacak sjsksksksksk", + "Yapma yanarız", + "skamaksksksksksks", + "inşallahh bence de görelim artık", + "hem de nasılllll", + "Konumumuz belli", "Herkes böyle bacı bulamaz şanslıyım tabi 👅 ♥️♥️♥️♥️", - "En değerlilerim misiniz nesinizz????", "😳♥️♥️", - "Pardon da neyin var acabaaa", "Bebekkkk gibisin aşkımm", - "Şşş sus yoksa inanırım", "İyi geceler canlarım️♥♥♥", - "Sizi seveni üzün, düzene uyun", "Tanıyamadım", "Hangi sınıf", - "Okuldan kimse takip etmiyor?", "12 dahil takip ettiğim kişiler var", - "Ama seninle hiçbiri takipleşmiyor", "Kavuştukkkk♥", "Kuzucuğumla💘", - "Bu özlemin tarifi yok♥", "Mood.", "Sondaki gülüşe düşmeyen de ne bilim", - "Herkes bu kadar bencil olmak zorunda mı?", "❔", "Bekle dedi gitti 👅♥", - "Bu da burda kalsın", "SENİİİN👅", "Aşşşşşkkıım", "💗💗", - "Ah Seden'im... küçük civcivim", "💓💓💓💓", - "Özlemini tişörtünle gideriyorum.", "Sus ya sen çok farklısın sanki", - "Seden'e derdimi anlatıyorumdur. Seden:", "Canımın içiiiii 💙", "😘😘", + "En değerlilerim misiniz nesinizz????", + "😳♥️♥️", + "Pardon da neyin var acabaaa", + "Bebekkkk gibisin aşkımm", + "Şşş sus yoksa inanırım", + "İyi geceler canlarım️♥♥♥", + "Sizi seveni üzün, düzene uyun", + "Tanıyamadım", + "Hangi sınıf", + "Okuldan kimse takip etmiyor?", + "12 dahil takip ettiğim kişiler var", + "Ama seninle hiçbiri takipleşmiyor", + "Kavuştukkkk♥", + "Kuzucuğumla💘", + "Bu özlemin tarifi yok♥", + "Mood.", + "Sondaki gülüşe düşmeyen de ne bilim", + "Herkes bu kadar bencil olmak zorunda mı?", + "❔", + "Bekle dedi gitti 👅♥", + "Bu da burda kalsın", + "SENİİİN👅", + "Aşşşşşkkıım", + "💗💗", + "Ah Seden'im... küçük civcivim", + "💓💓💓💓", + "Özlemini tişörtünle gideriyorum.", + "Sus ya sen çok farklısın sanki", + "Seden'e derdimi anlatıyorumdur. Seden:", + "Canımın içiiiii 💙", + "😘😘", "Anlık sinir krizleri geçirildi", "Çevrimiçi olup olmadığımızı test ediyoruzdur #whatsapp yaktın bizi", - "Djjdkdkmdöödööd", "Ben almadım Seden almış", + "Djjdkdkmdöödööd", + "Ben almadım Seden almış", "Hocam lütfen bir sonraki dersimize olsun", "Biraz da şerefsiz arkadaşlarımızı paylaşalım ♥️♥️♥️♥️", - "sadece Doğum günümde yazıyorsunuz", "Yalansa yalan de djjdjdkdkksks", - "👅♥️♥️♥️", "SJSJJDJDKSKSKSKKSKSKDK yok artık hala mı", + "sadece Doğum günümde yazıyorsunuz", + "Yalansa yalan de djjdjdkdkksks", + "👅♥️♥️♥️", + "SJSJJDJDKSKSKSKKSKSKDK yok artık hala mı", "Bütün okul anladı @CiyanogenOneTeams ona yâr olmayacağımı anlamadı Jsjsjdkskskskskskl", "KSKDKDKDKDKSKKSKDKDKKD", "LAN HATIRLATMA sjjdjdjdkdkdkdkdk bir fotoğraf çekilelim mi NE SJSKSKSKSK", - "Yemin ederim daha fazla sevenini görmedim jdjdkdkdkd"] + "Yemin ederim daha fazla sevenini görmedim jdjdkdkdkd", +] # ================= CONSTANT ================= '''Copyright (c) @Sedenogen | 2020''' diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index cef1818..192f067 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -11,8 +11,15 @@ from subprocess import Popen from sedenbot import HELP -from sedenecem.core import (edit, sedenify, download_media_wc, reply_voice, - extract_args, reply_doc, get_translation) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply_doc, + reply_voice, + sedenify, +) @sedenify(pattern='^.earrape') @@ -25,40 +32,53 @@ def earrape(message): util = args[0].lower() if util == 'mp4': - if not(reply.video or reply.video_note or ( - reply.document and 'video' in reply.document.mime_type)): + if not ( + reply.video + or reply.video_note + or (reply.document and 'video' in reply.document.mime_type) + ): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyEarrape")}`') media = download_media_wc(reply, earrape) - process = Popen(['ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp4']) + process = Popen( + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'acrusher=.1:1:64:0:log', + f'{media}.mp4', + ] + ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_doc( - message, - f'{media}.mp4', - delete_after_send=True, - delete_orig=True) + reply_doc(message, f'{media}.mp4', delete_after_send=True, delete_orig=True) remove(media) elif util == 'mp3': - if not(reply.video or reply.video_note or ( - reply.audio or reply.voice or ( - reply.document and 'video' in reply.document.mime_type))): + if not ( + reply.video + or reply.video_note + or ( + reply.audio + or reply.voice + or (reply.document and 'video' in reply.document.mime_type) + ) + ): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyEarrape")}`') media = download_media_wc(reply, earrape) - process = Popen(['ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp3']) + process = Popen( + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'acrusher=.1:1:64:0:log', + f'{media}.mp3', + ] + ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3', delete_orig=True) @@ -77,18 +97,25 @@ def nightcore(message): if path.isfile(nightcore): remove(nightcore) - if not(reply.audio or reply.voice or ( - reply.document and 'audio' in reply.document.mime_type)): + if not ( + reply.audio + or reply.voice + or (reply.document and 'audio' in reply.document.mime_type) + ): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyNightcore")}`') media = download_media_wc(reply, file_name=nightcore) - process = Popen(['ffmpeg', - '-i', - f'{media}', - '-af', - 'asetrate=44100*1.16,aresample=44100,atempo=1', - f'{media}.mp3']) + process = Popen( + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'asetrate=44100*1.16,aresample=44100,atempo=1', + f'{media}.mp3', + ] + ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') @@ -105,16 +132,25 @@ def slowedtoperfection(message): if path.isfile(slowedtoperfection): remove(slowedtoperfection) - if not(reply.audio or reply.voice or ( - reply.document and 'audio' in reply.document.mime_type)): + if not ( + reply.audio + or reply.voice + or (reply.document and 'audio' in reply.document.mime_type) + ): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applySlowedtoperfection")}`') media = download_media_wc(reply, file_name=slowedtoperfection) process = Popen( - ['ffmpeg', '-i', f'{media}', '-af', - 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', - f'{media}.mp3']) + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', + f'{media}.mp3', + ] + ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_voice(message, f'{media}.mp3') diff --git a/sedenbot/modules/env.py b/sedenbot/modules/env.py index 2832d53..3f84233 100644 --- a/sedenbot/modules/env.py +++ b/sedenbot/modules/env.py @@ -8,14 +8,21 @@ # from time import sleep -from heroku3 import from_key -from dotenv import dotenv_values +from dotenv import dotenv_values +from heroku3 import from_key +from sedenbot import ( + ENV_RESTRICTED_KEYS, + HELP, + HEROKU_APPNAME, + HEROKU_KEY, + environ, + reload_env, + set_local_env, + unset_local_env, +) from sedenbot.modules.horeke import restart -from sedenbot import (HELP, HEROKU_KEY, HEROKU_APPNAME, environ, - set_local_env, unset_local_env, reload_env, - ENV_RESTRICTED_KEYS) -from sedenecem.core import edit, sedenify, extract_args, get_translation +from sedenecem.core import edit, extract_args, get_translation, sedenify @sedenify(pattern='^.env', compat=False) @@ -24,8 +31,7 @@ def manage_env(client, message): if action[0] == 'list': pass - elif len(action) < 2 or action[0] not in [ - 'get', 'set', 'rem', 'copy', 'move']: + elif len(action) < 2 or action[0] not in ['get', 'set', 'rem', 'copy', 'move']: edit(message, f"`{get_translation('wrongCommand')}`") return @@ -39,7 +45,8 @@ def manage_env(client, message): if not HEROKU_APPNAME: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) for app in heroku_applications: if app.name == HEROKU_APPNAME: heroku_app = app @@ -48,7 +55,8 @@ def manage_env(client, message): if heroku_app is None: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) return reload_env() @@ -56,8 +64,11 @@ def manage_env(client, message): if action[0] == 'set': items = action[1].split(' ', 1) - if len(items) < 2 or len( - items[1]) < 1 or items[0].upper() in ENV_RESTRICTED_KEYS: + if ( + len(items) < 2 + or len(items[1]) < 1 + or items[0].upper() in ENV_RESTRICTED_KEYS + ): edit(message, f"`{get_translation('wrongCommand')}`") return @@ -87,16 +98,10 @@ def manage_env(client, message): elif not heroku_mode and (value := environ.get(items[0], None)): pass else: - edit( - message, get_translation( - 'envNotFound', [ - '`', '**', items[0]])) + edit(message, get_translation('envNotFound', ['`', '**', items[0]])) return - edit( - message, get_translation( - 'envGetValue', [ - '`', '**', items[0], value])) + edit(message, get_translation('envGetValue', ['`', '**', items[0], value])) elif action[0] == 'rem': items = action[1].split(' ', 1) @@ -111,10 +116,7 @@ def manage_env(client, message): elif not heroku_mode and (value := environ.get(items[0], None)): unset_local_env(items[0]) else: - edit( - message, get_translation( - 'envNotFound', [ - '`', '**', items[0]])) + edit(message, get_translation('envNotFound', ['`', '**', items[0]])) return edit(message, get_translation('envRemSuccess', ['`', '**', items[0]])) @@ -123,8 +125,13 @@ def manage_env(client, message): elif action[0] in ['copy', 'move']: items = action[1].split(' ', 1) - if len(items) < 2 or len(items[0]) < 1 or items[0].upper() in ENV_RESTRICTED_KEYS or items[1].upper( - ) in ENV_RESTRICTED_KEYS or items[0].upper() == items[1].upper(): + if ( + len(items) < 2 + or len(items[0]) < 1 + or items[0].upper() in ENV_RESTRICTED_KEYS + or items[1].upper() in ENV_RESTRICTED_KEYS + or items[0].upper() == items[1].upper() + ): edit(message, f"`{get_translation('wrongCommand')}`") return @@ -136,10 +143,7 @@ def manage_env(client, message): elif not heroku_mode and (value := environ.get(items[0], None)): pass else: - edit( - message, get_translation( - 'envNotFound', [ - '`', '**', items[0]])) + edit(message, get_translation('envNotFound', ['`', '**', items[0]])) return if heroku_mode: @@ -153,17 +157,16 @@ def manage_env(client, message): elif not heroku_mode and (value := environ.get(items[0], None)): unset_local_env(items[0]) edit( - message, get_translation( - 'envMoveSuccess', [ - '`', '**', items[0], items[1]])) + message, + get_translation('envMoveSuccess', ['`', '**', items[0], items[1]]), + ) sleep(2) restart(client, message) return edit( - message, get_translation( - 'envCopySuccess', [ - '`', '**', items[0], items[1]])) + message, get_translation('envCopySuccess', ['`', '**', items[0], items[1]]) + ) sleep(2) restart(client, message) elif action[0] == 'list': @@ -175,8 +178,7 @@ def manage_env(client, message): out += f'%1•%1 %2{i.replace("%", "½")}%2\n' else: keys = dotenv_values('config.env').keys() - keys = sorted([x for x in keys if x.upper() - not in ENV_RESTRICTED_KEYS]) + keys = sorted([x for x in keys if x.upper() not in ENV_RESTRICTED_KEYS]) for i in keys: out += f'%1•%1 %2{i.replace("%", "½")}%2\n' diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 3e07ef0..d54c9e6 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -7,13 +7,13 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import sub, DOTALL from functools import reduce -from requests import get -from bs4 import BeautifulSoup +from re import DOTALL, sub +from bs4 import BeautifulSoup +from requests import get from sedenbot import HELP -from sedenecem.core import edit, extract_args, sedenify, get_translation +from sedenecem.core import edit, extract_args, get_translation, sedenify @sedenify(pattern='^.ezanvakti') @@ -36,26 +36,20 @@ def ezanvakti(message): res1 = result.body.findAll('div', {'class': ['body-content']}) res1 = res1[0].findAll('script') res1 = sub( - r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1[0]), - flags=DOTALL) + r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1[0]), flags=DOTALL + ) res1 = sub('\n\n', '\n', res1)[:-1].split('\n') def get_val(st): - return [i.split('=')[1].replace('"', '').strip() - for i in st[:-1].split(';')] + return [i.split('=')[1].replace('"', '').strip() for i in st[:-1].split(';')] res2 = get_val(res1[1]) res3 = get_val(res1[2]) vakitler = get_translation( 'ezanvaktiShowInfo', - ['**', '`', res2[1], - res3[0], - res3[1], - res3[2], - res3[3], - res3[4], - res3[5]]) + ['**', '`', res2[1], res3[0], res3[1], res3[2], res3[3], res3[4], res3[5]], + ) edit(message, vakitler) @@ -79,33 +73,93 @@ def find_loc(konum): sehirler = [ - '01 Adana 9146', '02 Adiyaman 9158', '03 Afyonkarahisar 9167', - '04 Agri 9185', '05 Amasya 9198', '06 Ankara 9206', '07 Antalya 9225', - '08 Artvin 9246', '09 Aydin 9252', '10 Balikesir 9270', '11 Bilecik 9297', - '12 Bingol 9303', '13 Bitlis 9311', '14 Bolu 9315', '15 Burdur 9327', - '16 Bursa 9335', '17 Canakkale 9352', '18 Cankiri 9359', '19 Corum 9370', - '20 Denizli 9392', '21 Diyarbakir 9402', '22 Edirne 9419', - '23 Elazig 9432', '24 Erzincan 9440', '25 Erzurum 9451', - '26 Eskisehir 9470', '27 Gaziantep 9479', '28 Giresun 9494', - '29 Gumushane 9501', '30 Hakkari 9507', '31 Hatay 20089', - '32 Isparta 9528', '33 Mersin 9737', '34 Istanbul 9541', '35 Izmir 9560', - '36 Kars 9594', '37 Kastamonu 9609', '38 Kayseri 9620', - '39 Kirklareli 9638', '40 Kirsehir 9646', '41 Kocaeli 9654', - '42 Konya 9676', '43 Kutahya 9689', '44 Malatya 9703', '45 Manisa 9716', - '46 Kahramanmaras 9577', '47 Mardin 9726', '48 Mugla 9747', '49 Mus 9755', - '50 Nevsehir 9760', '51 Nigde 9766', '52 Ordu 9782', '53 Rize 9799', - '54 Sakarya 9807', '55 Samsun 9819', '56 Siirt 9839', '57 Sinop 9847', - '58 Sivas 9868', '59 Tekirdag 9879', '60 Tokat 9887', '61 Trabzon 9905', - '62 Tunceli 9914', '63 Sanliurfa 9831', '64 Usak 9919', '65 Van 9930', - '66 Yozgat 9949', '67 Zonguldak 9955', '68 Aksaray 9193', - '69 Bayburt 9295', '70 Karaman 9587', '71 Kirikkale 9635', - '72 Batman 9288', '73 Sirnak 9854', '74 Bartin 9285', '75 Ardahan 9238', - '76 Igdir 9522', '77 Yalova 9935', '78 Karabuk 9581', '79 Kilis 9629', - '80 Osmaniye 9788', '81 Duzce 9414'] + '01 Adana 9146', + '02 Adiyaman 9158', + '03 Afyonkarahisar 9167', + '04 Agri 9185', + '05 Amasya 9198', + '06 Ankara 9206', + '07 Antalya 9225', + '08 Artvin 9246', + '09 Aydin 9252', + '10 Balikesir 9270', + '11 Bilecik 9297', + '12 Bingol 9303', + '13 Bitlis 9311', + '14 Bolu 9315', + '15 Burdur 9327', + '16 Bursa 9335', + '17 Canakkale 9352', + '18 Cankiri 9359', + '19 Corum 9370', + '20 Denizli 9392', + '21 Diyarbakir 9402', + '22 Edirne 9419', + '23 Elazig 9432', + '24 Erzincan 9440', + '25 Erzurum 9451', + '26 Eskisehir 9470', + '27 Gaziantep 9479', + '28 Giresun 9494', + '29 Gumushane 9501', + '30 Hakkari 9507', + '31 Hatay 20089', + '32 Isparta 9528', + '33 Mersin 9737', + '34 Istanbul 9541', + '35 Izmir 9560', + '36 Kars 9594', + '37 Kastamonu 9609', + '38 Kayseri 9620', + '39 Kirklareli 9638', + '40 Kirsehir 9646', + '41 Kocaeli 9654', + '42 Konya 9676', + '43 Kutahya 9689', + '44 Malatya 9703', + '45 Manisa 9716', + '46 Kahramanmaras 9577', + '47 Mardin 9726', + '48 Mugla 9747', + '49 Mus 9755', + '50 Nevsehir 9760', + '51 Nigde 9766', + '52 Ordu 9782', + '53 Rize 9799', + '54 Sakarya 9807', + '55 Samsun 9819', + '56 Siirt 9839', + '57 Sinop 9847', + '58 Sivas 9868', + '59 Tekirdag 9879', + '60 Tokat 9887', + '61 Trabzon 9905', + '62 Tunceli 9914', + '63 Sanliurfa 9831', + '64 Usak 9919', + '65 Van 9930', + '66 Yozgat 9949', + '67 Zonguldak 9955', + '68 Aksaray 9193', + '69 Bayburt 9295', + '70 Karaman 9587', + '71 Kirikkale 9635', + '72 Batman 9288', + '73 Sirnak 9854', + '74 Bartin 9285', + '75 Ardahan 9238', + '76 Igdir 9522', + '77 Yalova 9935', + '78 Karabuk 9581', + '79 Kilis 9629', + '80 Osmaniye 9788', + '81 Duzce 9414', +] -HELP.update({ - "ezanvakti": - ".ezanvakti <şehir> \ +HELP.update( + { + "ezanvakti": ".ezanvakti <şehir> \ \nKullanım: Belirtilen şehir için namaz vakitlerini gösterir. \ \nÖrnek: .ezanvakti istanbul veya .ezanvakti 34" -}) + } +) diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index 0f7f4e2..95af984 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -7,18 +7,27 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import fullmatch, IGNORECASE - -from sedenbot import HELP, LOGS, LOG_ID -from sedenecem.core import (extract_args, sedenify, edit, get_messages, - reply_msg, reply, forward, send_log, - get_translation) +from re import IGNORECASE, fullmatch + +from sedenbot import HELP, LOG_ID, LOGS +from sedenecem.core import ( + edit, + extract_args, + forward, + get_messages, + get_translation, + reply, + reply_msg, + sedenify, + send_log, +) def filters_init(): try: global sql from importlib import import_module + sql = import_module('sedenecem.sql.filters_sql') except Exception as e: sql = None @@ -93,12 +102,10 @@ def add_filter(message): string = None msg_o = forward(msg, LOG_ID) if not msg_o: - edit( - message, f'`{get_translation("filterError")}`') + edit(message, f'`{get_translation("filterError")}`') return msg_id = msg_o.message_id - send_log(get_translation( - 'filterLog', ['`', message.chat.id, keyword])) + send_log(get_translation('filterLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/gban.py b/sedenbot/modules/gban.py deleted file mode 100644 index 9f8a0cd..0000000 --- a/sedenbot/modules/gban.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from time import sleep - -from sedenbot import BRAIN -from sedenecem.sql import gban_sql as sql -from sedenecem.core import (edit, sedenify, send_log, - extract_args, get_translation) - - -@sedenify(pattern='^.gban', compat=False) -def gban_user(client, message): - args = extract_args(message) - reply = message.reply_to_message - edit(message, f'`{get_translation("banProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return - - try: - replied_user = reply.from_user - if replied_user.is_self: - return edit(message, f'`{get_translation("cannotBanMyself")}`') - except BaseException: - pass - - if user.id in BRAIN: - return edit( - message, get_translation( - 'brainError', [ - '`', '**', user.first_name, user.id])) - - try: - if sql.is_gbanned(user.id): - return edit(message, f'`{get_translation("alreadyBanned")}`') - sql.gban(user.id) - edit( - message, get_translation( - 'gbanResult', [ - '**', user.first_name, user.id, '`'])) - sleep(1) - send_log( - get_translation( - 'gbanLog', - ['**', user.first_name, user.id, '`'])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return - - -@sedenify(pattern='^.ungban', compat=False) -def ungban_user(client, message): - args = extract_args(message) - reply = message.reply_to_message - edit(message, f'`{get_translation("unbanProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return - - try: - replied_user = reply.from_user - if replied_user.is_self: - return edit(message, f'`{get_translation("cannotUnbanMyself")}`') - except BaseException: - pass - - try: - if not sql.is_gbanned(user.id): - return edit(message, f'`{get_translation("alreadyUnbanned")}`') - chat_id = message.chat.id - sql.ungban(user.id) - client.unban_chat_member(chat_id, user.id) - edit( - message, get_translation( - 'unbanResult', [ - '**', user.first_name, user.id, '`'])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return - - -@sedenify(pattern='^.listgban$') -def gbanlist(message): - users = sql.gbanned_users() - if not users: - return edit(message, f'`{get_translation("listEmpty")}`') - gban_list = f'**{get_translation("gbannedUsers")}**\n' - count = 0 - for i in users: - count += 1 - gban_list += f'**{count} -** `{i.sender}`\n' - return edit(message, gban_list) - - -@sedenify(incoming=True, outgoing=False, compat=False) -def gban_check(client, message): - gbanned = sql.is_gbanned(message.from_user.id) - - if gbanned: - try: - user_id = message.from_user.id - chat_id = message.chat.id - client.kick_chat_member(chat_id, user_id) - except BaseException as e: - send_log(get_translation('banError', ['`', '**', e])) - - message.continue_propagation() diff --git a/sedenbot/modules/git.py b/sedenbot/modules/git.py index 9954c51..3ec42a4 100644 --- a/sedenbot/modules/git.py +++ b/sedenbot/modules/git.py @@ -8,10 +8,10 @@ # from json import loads -from requests import get +from requests import get from sedenbot import HELP -from sedenecem.core import sedenify, edit, extract_args, get_translation +from sedenecem.core import edit, extract_args, get_translation, sedenify @sedenify(pattern='^.github') @@ -81,25 +81,33 @@ def get_repos(): out += f'{i}\n' return out - edit(message, f'**{get_translation("gitUserInfo", [login])}**\n\n' + - format_info(get_translation("gitUser"), user_id) + - format_info(get_translation("gitAccount"), acc_type) + - format_info(get_translation("gitName"), name) + - format_info(get_translation("gitCompany"), company) + - format_info(get_translation("gitWebsite"), blog) + - format_info(get_translation("gitLocation"), location) + - format_info(get_translation("gitMail"), email) + - format_info(get_translation("gitBio"), bio) + - format_info(get_translation("gitTwitter"), twitter) + - format_info(get_translation("gitTotalRepo"), repo_count) + - format_info(get_translation("gitTotalGist"), gist_count) + - ((format_info(get_translation("gitFollowers"), followers) + - format_info(get_translation("gitFollowing"), following)) - if acc_type == 'User' - else '') + - format_info(get_translation("gitCreationDate"), created) + - format_info(get_translation("gitDateOfUpdate"), updated) + - f'\n{get_translation("gitRepoList")}\n{get_repos()}', preview=False) + edit( + message, + f'**{get_translation("gitUserInfo", [login])}**\n\n' + + format_info(get_translation("gitUser"), user_id) + + format_info(get_translation("gitAccount"), acc_type) + + format_info(get_translation("gitName"), name) + + format_info(get_translation("gitCompany"), company) + + format_info(get_translation("gitWebsite"), blog) + + format_info(get_translation("gitLocation"), location) + + format_info(get_translation("gitMail"), email) + + format_info(get_translation("gitBio"), bio) + + format_info(get_translation("gitTwitter"), twitter) + + format_info(get_translation("gitTotalRepo"), repo_count) + + format_info(get_translation("gitTotalGist"), gist_count) + + ( + ( + format_info(get_translation("gitFollowers"), followers) + + format_info(get_translation("gitFollowing"), following) + ) + if acc_type == 'User' + else '' + ) + + format_info(get_translation("gitCreationDate"), created) + + format_info(get_translation("gitDateOfUpdate"), updated) + + f'\n{get_translation("gitRepoList")}\n{get_repos()}', + preview=False, + ) HELP.update({'git': get_translation('gitInfo')}) diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py new file mode 100644 index 0000000..b1a716c --- /dev/null +++ b/sedenbot/modules/globals.py @@ -0,0 +1,259 @@ +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from time import sleep + +from pyrogram.types import ChatPermissions +from sedenbot import BRAIN, HELP, LOGS +from sedenecem.core import edit, extract_args, get_translation, sedenify, send_log + + +def globals_init(): + try: + global sql, sql2 + from importlib import import_module + + sql = import_module('sedenecem.sql.gban_sql') + sql2 = import_module('sedenecem.sql.gmute_sql') + except Exception as e: + sql = None + sql2 = None + LOGS.warn(get_translation('globalsSqlLog')) + raise e + + +globals_init() + + +@sedenify(pattern='^.gban', compat=False) +def gban_user(client, message): + args = extract_args(message) + reply = message.reply_to_message + edit(message, f'`{get_translation("banProcess")}`') + if args: + try: + user = client.get_users(args) + except Exception: + edit(message, f'`{get_translation("banFailUser")}`') + return + elif reply: + user_id = reply.from_user.id + user = client.get_users(user_id) + else: + edit(message, f'`{get_translation("banFailUser")}`') + return + + try: + replied_user = reply.from_user + if replied_user.is_self: + return edit(message, f'`{get_translation("cannotBanMyself")}`') + except BaseException: + pass + + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), + ) + + try: + if sql.is_gbanned(user.id): + return edit(message, f'`{get_translation("alreadyBanned")}`') + sql.gban(user.id) + edit( + message, + get_translation('gbanResult', ['**', user.first_name, user.id, '`']), + ) + sleep(1) + send_log(get_translation('gbanLog', [user.first_name, user.id])) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return + + +@sedenify(pattern='^.ungban', compat=False) +def ungban_user(client, message): + args = extract_args(message) + reply = message.reply_to_message + edit(message, f'`{get_translation("unbanProcess")}`') + if args: + try: + user = client.get_users(args) + except Exception: + edit(message, f'`{get_translation("banFailUser")}`') + return + elif reply: + user_id = reply.from_user.id + user = client.get_users(user_id) + else: + edit(message, f'`{get_translation("banFailUser")}`') + return + + try: + replied_user = reply.from_user + if replied_user.is_self: + return edit(message, f'`{get_translation("cannotUnbanMyself")}`') + except BaseException: + pass + + try: + if not sql.is_gbanned(user.id): + return edit(message, f'`{get_translation("alreadyUnbanned")}`') + chat_id = message.chat.id + sql.ungban(user.id) + client.unban_chat_member(chat_id, user.id) + edit( + message, + get_translation('unbanResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return + + +@sedenify(pattern='^.listgban$') +def gbanlist(message): + users = sql.gbanned_users() + if not users: + return edit(message, f'`{get_translation("listEmpty")}`') + gban_list = f'**{get_translation("gbannedUsers")}**\n' + count = 0 + for i in users: + count += 1 + gban_list += f'**{count} -** `{i.sender}`\n' + return edit(message, gban_list) + + +@sedenify(incoming=True, outgoing=False, compat=False) +def gban_check(client, message): + if sql.is_gbanned(message.from_user.id): + try: + user_id = message.from_user.id + chat_id = message.chat.id + client.kick_chat_member(chat_id, user_id) + except BaseException as e: + send_log(get_translation('banError', ['`', '**', e])) + + message.continue_propagation() + + +@sedenify(pattern='^.gmute', compat=False) +def gmute_user(client, message): + args = extract_args(message) + reply = message.reply_to_message + edit(message, f'`{get_translation("muteProcess")}`') + if len(args): + try: + user = client.get_users(args) + except Exception: + edit(message, f'`{get_translation("banFailUser")}`') + return + elif reply: + user_id = reply.from_user.id + user = client.get_users(user_id) + else: + edit(message, f'`{get_translation("banFailUser")}`') + return + + try: + replied_user = reply.from_user + if replied_user.is_self: + return edit(message, f'`{get_translation("cannotMuteMyself")}`') + except BaseException: + pass + + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), + ) + + try: + if sql2.is_gmuted(user.id): + return edit(message, f'`{get_translation("alreadyMuted")}`') + sql2.gmute(user.id) + edit( + message, + get_translation('gmuteResult', ['**', user.first_name, user.id, '`']), + ) + sleep(1) + send_log(get_translation('gmuteLog', [user.first_name, user.id])) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return + + +@sedenify(pattern='^.ungmute', compat=False) +def ungmute_user(client, message): + args = extract_args(message) + reply = message.reply_to_message + edit(message, f'`{get_translation("unmuteProcess")}`') + if len(args): + try: + user = client.get_users(args) + except Exception: + edit(message, f'`{get_translation("banFailUser")}`') + return + elif reply: + user_id = reply.from_user.id + user = client.get_users(user_id) + else: + edit(message, f'`{get_translation("banFailUser")}`') + return + + try: + replied_user = reply.from_user + if replied_user.is_self: + return edit(message, f'`{get_translation("cannotUnmuteMyself")}`') + except BaseException: + pass + + try: + if not sql2.is_gmuted(user.id): + return edit(message, f'`{get_translation("alreadyUnmuted")}`') + sql2.ungmute(user.id) + edit( + message, + get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return + + +@sedenify(pattern='^.listgmute$') +def gmutelist(message): + users = sql2.gmuted_users() + if not users: + return edit(message, f'`{get_translation("listEmpty")}`') + gmute_list = f'**{get_translation("gmutedUsers")}**\n' + count = 0 + for i in users: + count += 1 + gmute_list += f'**{count} -** `{i.sender}`\n' + return edit(message, gmute_list) + + +@sedenify(incoming=True, outgoing=False, compat=False) +def gmute_check(client, message): + if sql2.is_gmuted(message.from_user.id): + sleep(0.1) + message.delete() + + try: + user_id = message.from_user.id + chat_id = message.chat.id + client.restrict_chat_member(chat_id, user_id, ChatPermissions()) + except BaseException: + pass + + message.continue_propagation() + + +HELP.update({'globals': get_translation('globalsInfo')}) diff --git a/sedenbot/modules/gmute.py b/sedenbot/modules/gmute.py deleted file mode 100644 index d149853..0000000 --- a/sedenbot/modules/gmute.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from time import sleep - -from pyrogram.types import ChatPermissions - -from sedenbot import BRAIN -from sedenecem.sql import gmute_sql as sql -from sedenecem.core import (edit, sedenify, send_log, - extract_args, get_translation) - - -@sedenify(pattern='^.gmute', compat=False) -def gmute_user(client, message): - args = extract_args(message) - reply = message.reply_to_message - edit(message, f'`{get_translation("muteProcess")}`') - if len(args): - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return - - try: - replied_user = reply.from_user - if replied_user.is_self: - return edit(message, f'`{get_translation("cannotMuteMyself")}`') - except BaseException: - pass - - if user.id in BRAIN: - return edit( - message, get_translation( - 'brainError', [ - '`', '**', user.first_name, user.id])) - - try: - if sql.is_gmuted(user.id): - return edit(message, f'`{get_translation("alreadyMuted")}`') - sql.gmute(user.id) - edit( - message, get_translation( - 'gmuteResult', [ - '**', user.first_name, user.id, '`'])) - sleep(1) - send_log(get_translation('gmuteLog', ['**', user.first_name, user.id])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return - - -@sedenify(pattern='^.ungmute', compat=False) -def ungmute_user(client, message): - args = extract_args(message) - reply = message.reply_to_message - edit(message, f'`{get_translation("unmuteProcess")}`') - if len(args): - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return - - try: - replied_user = reply.from_user - if replied_user.is_self: - return edit(message, f'`{get_translation("cannotUnmuteMyself")}`') - except BaseException: - pass - - try: - if not sql.is_gmuted(user.id): - return edit(message, f'`{get_translation("alreadyUnmuted")}`') - sql.ungmute(user.id) - edit( - message, get_translation( - 'unmuteResult', [ - '**', user.first_name, user.id, '`'])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return - - -@sedenify(pattern='^.listgmute$') -def gmutelist(message): - users = sql.gmuted_users() - if not users: - return edit(message, f'`{get_translation("listEmpty")}`') - gmute_list = f'**{get_translation("gmutedUsers")}**\n' - count = 0 - for i in users: - count += 1 - gmute_list += f'**{count} -** `{i.sender}`\n' - return edit(message, gmute_list) - - -@sedenify(incoming=True, outgoing=False, compat=False) -def gmute_check(client, message): - gmuted = sql.is_gmuted(message.from_user.id) - - if gmuted: - sleep(0.1) - message.delete() - - try: - user_id = message.from_user.id - chat_id = message.chat.id - client.restrict_chat_member(chat_id, user_id, ChatPermissions()) - except BaseException: - pass - - message.continue_propagation() diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index cb8048c..86b92a2 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -7,15 +7,14 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import execl, getpid -from sys import executable, argv from math import floor -from requests import get -from heroku3 import from_key +from os import execl, getpid +from sys import argv, executable -from sedenbot import HELP, HEROKU_KEY, HEROKU_APPNAME -from sedenecem.core import (edit, sedenify, get_translation, - send_log, reply_doc) +from heroku3 import from_key +from requests import get +from sedenbot import HELP, HEROKU_APPNAME, HEROKU_KEY +from sedenecem.core import edit, get_translation, reply_doc, sedenify, send_log @sedenify(pattern='^.(quo|ko)ta$') @@ -32,7 +31,8 @@ def dyno(message): if not HEROKU_APPNAME: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) for app in heroku_applications: if app.name == HEROKU_APPNAME: @@ -42,7 +42,8 @@ def dyno(message): if heroku_app is None: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) return acc_id = heroku.account().id @@ -54,8 +55,8 @@ def dyno(message): } req = get( - f'https://api.heroku.com/accounts/{acc_id}/actions/get-quota', - headers=headers) + f'https://api.heroku.com/accounts/{acc_id}/actions/get-quota', headers=headers + ) if req.status_code != 200: edit(message, f"`{get_translation('covidError')}`") @@ -97,18 +98,21 @@ def get_app_quota(): message, get_translation( 'herokuQuotaInfo', - ['`', '**', - get_translation( - 'herokuQuotaInHM', [acc_total_hrs, acc_total_min]), - get_translation( - 'herokuQuotaInHM', [acc_used_hrs, acc_used_min]), - acc_quota_percent, - get_translation( - 'herokuQuotaInHM', [acc_remaining_hrs, acc_remaining_min]), - acc_quota_rem_percent, - get_translation( - 'herokuQuotaInHM', [app_quota_hrs, app_quota_min]), - app_quota_percent])) + [ + '`', + '**', + get_translation('herokuQuotaInHM', [acc_total_hrs, acc_total_min]), + get_translation('herokuQuotaInHM', [acc_used_hrs, acc_used_min]), + acc_quota_percent, + get_translation( + 'herokuQuotaInHM', [acc_remaining_hrs, acc_remaining_min] + ), + acc_quota_rem_percent, + get_translation('herokuQuotaInHM', [app_quota_hrs, app_quota_min]), + app_quota_percent, + ], + ), + ) @sedenify(pattern='^.(restart|yb)$') @@ -145,7 +149,8 @@ def std_ret(): if not HEROKU_APPNAME: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) std_ret() return @@ -157,7 +162,8 @@ def std_ret(): if heroku_app is None: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) std_ret() return @@ -175,6 +181,7 @@ def shutdown(message): def std_off(): try: from subprocess import getoutput + getoutput(f'kill -7 {getpid()}') except Exception: pass @@ -189,7 +196,8 @@ def std_off(): if not HEROKU_APPNAME: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) std_off() return @@ -201,7 +209,8 @@ def std_off(): if heroku_app is None: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) std_off() return @@ -223,7 +232,8 @@ def dyno_logs(message): if not HEROKU_APPNAME: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) for app in heroku_applications: if app.name == HEROKU_APPNAME: @@ -233,7 +243,8 @@ def dyno_logs(message): if heroku_app is None: edit( message, - f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`') + f'`{get_translation("updateHerokuVariables", ["HEROKU_APPNAME "])}`', + ) return filename = 'seden_heroku_log.txt' diff --git a/sedenbot/modules/lastfm.py b/sedenbot/modules/lastfm.py index dc077be..00909d5 100644 --- a/sedenbot/modules/lastfm.py +++ b/sedenbot/modules/lastfm.py @@ -9,11 +9,10 @@ from re import sub from urllib.parse import quote -from pylast import User, LastFMNetwork, md5 +from pylast import LastFMNetwork, User, md5 from sedenbot import HELP, environ -from sedenecem.core import sedenify, edit, get_translation - +from sedenecem.core import edit, get_translation, sedenify # =================== CONSTANT =================== LASTFM_API = environ.get('LASTFM_API', None) @@ -22,10 +21,12 @@ LASTFM_PASSWORD_PLAIN = environ.get('LASTFM_PASSWORD', None) LASTFM_PASS = md5(LASTFM_PASSWORD_PLAIN) if LASTFM_API and LASTFM_SECRET and LASTFM_USERNAME and LASTFM_PASS: - lastfm = LastFMNetwork(api_key=LASTFM_API, - api_secret=LASTFM_SECRET, - username=LASTFM_USERNAME, - password_hash=LASTFM_PASS) + lastfm = LastFMNetwork( + api_key=LASTFM_API, + api_secret=LASTFM_SECRET, + username=LASTFM_USERNAME, + password_hash=LASTFM_PASS, + ) else: lastfm = None # ================================================ @@ -36,9 +37,8 @@ def last_fm(message): edit(message, f'`{get_translation("processing")}`') if not lastfm: return edit( - message, get_translation( - 'lastfmApiMissing', [ - '**', '`']), preview=False) + message, get_translation('lastfmApiMissing', ['**', '`']), preview=False + ) playing = User(LASTFM_USERNAME, lastfm).get_now_playing() username = f'https://www.last.fm/user/{LASTFM_USERNAME}' @@ -47,14 +47,13 @@ def last_fm(message): rectrack = quote(f'{playing}') rectrack = sub('^', 'https://open.spotify.com/search/', rectrack) output = get_translation( - 'lastfmProcess', [ - LASTFM_USERNAME, username, '__', playing, rectrack, '`', tags]) + 'lastfmProcess', + [LASTFM_USERNAME, username, '__', playing, rectrack, '`', tags], + ) else: recent = User(LASTFM_USERNAME, lastfm).get_recent_tracks(limit=5) playing = User(LASTFM_USERNAME, lastfm).get_now_playing() - output = get_translation( - 'lastfmProcess2', [ - LASTFM_USERNAME, username, '__']) + output = get_translation('lastfmProcess2', [LASTFM_USERNAME, username, '__']) for i, track in enumerate(recent): printable = artist_and_song(track) tags = gettags(track) diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/locks.py index 63d9d4b..c63ee4e 100644 --- a/sedenbot/modules/locks.py +++ b/sedenbot/modules/locks.py @@ -8,9 +8,8 @@ # from pyrogram.types import ChatPermissions - from sedenbot import HELP -from sedenecem.core import edit, sedenify, get_translation, parse_cmd +from sedenecem.core import edit, get_translation, parse_cmd, sedenify @sedenify(pattern=r'^.(un|)lock', compat=False, private=False, admin=True) @@ -81,8 +80,9 @@ def lock(client, message): else: if not kilit: edit( - message, get_translation( - 'locksUnlockNoArgs' if unlock else 'locksLockNoArgs')) + message, + get_translation('locksUnlockNoArgs' if unlock else 'locksLockNoArgs'), + ) return else: edit(message, get_translation('lockError', ['`', kilit])) @@ -96,31 +96,35 @@ def lock(client, message): gif = get_on_none(gif, kilitle.permissions.can_send_animations) gamee = get_on_none(gamee, kilitle.permissions.can_send_games) ainline = get_on_none(ainline, kilitle.permissions.can_use_inline_bots) - webprev = get_on_none( - webprev, kilitle.permissions.can_add_web_page_previews) + webprev = get_on_none(webprev, kilitle.permissions.can_add_web_page_previews) gpoll = get_on_none(gpoll, kilitle.permissions.can_send_polls) adduser = get_on_none(adduser, kilitle.permissions.can_invite_users) cpin = get_on_none(cpin, kilitle.permissions.can_pin_messages) changeinfo = get_on_none(changeinfo, kilitle.permissions.can_change_info) try: - client.set_chat_permissions(message.chat.id, ChatPermissions( - can_send_messages=msg, - can_send_media_messages=media, - can_send_stickers=sticker, - can_send_animations=gif, - can_send_games=gamee, - can_use_inline_bots=ainline, - can_add_web_page_previews=webprev, - can_send_polls=gpoll, - can_change_info=changeinfo, - can_invite_users=adduser, - can_pin_messages=cpin - )) + client.set_chat_permissions( + message.chat.id, + ChatPermissions( + can_send_messages=msg, + can_send_media_messages=media, + can_send_stickers=sticker, + can_send_animations=gif, + can_send_games=gamee, + can_use_inline_bots=ainline, + can_add_web_page_previews=webprev, + can_send_polls=gpoll, + can_change_info=changeinfo, + can_invite_users=adduser, + can_pin_messages=cpin, + ), + ) edit( - message, get_translation( - 'locksUnlockSuccess' if unlock else 'locksLockSuccess', [ - '`', kullanim])) + message, + get_translation( + 'locksUnlockSuccess' if unlock else 'locksLockSuccess', ['`', kullanim] + ), + ) except BaseException as e: edit(message, get_translation('lockPerm', ['`', '**', str(e)])) return diff --git a/sedenbot/modules/lydia.py b/sedenbot/modules/lydia.py index 35a1003..3903c1b 100644 --- a/sedenbot/modules/lydia.py +++ b/sedenbot/modules/lydia.py @@ -8,25 +8,11 @@ # from time import sleep -from coffeehouse.lydia import LydiaAI -from coffeehouse.api import API +from coffeehouse.api import API +from coffeehouse.lydia import LydiaAI from sedenbot import HELP, LOGS, LYDIA_APIKEY -from sedenecem.core import sedenify, edit, reply, get_translation - - -def lydia_init(): - try: - global sql - from importlib import import_module - sql = import_module('sedenecem.sql.lydia_sql') - except Exception as e: - sql = None - LOGS.warn(get_translation('lydiaSqlLog')) - raise e - - -lydia_init() +from sedenecem.core import edit, get_translation, reply, sedenify ACC_LYDIA = {} @@ -40,9 +26,8 @@ def lydia_init(): def repcf(message): if not LYDIA_APIKEY: return edit( - message, get_translation( - 'lydiaMissingApi', [ - '**', '`']), preview=False) + message, get_translation('lydiaMissingApi', ['**', '`']), preview=False + ) edit(message, f'`{get_translation("processing")}`') try: session = lydia.create_session() @@ -58,9 +43,8 @@ def repcf(message): def addcf(message): if not LYDIA_APIKEY: return edit( - message, get_translation( - 'lydiaMissingApi', [ - '**', '`']), preview=False) + message, get_translation('lydiaMissingApi', ['**', '`']), preview=False + ) edit(message, f'`{get_translation("processing")}`') sleep(3) reply_msg = message.reply_to_message @@ -73,8 +57,9 @@ def addcf(message): message, get_translation( 'lydiaResult2', - ['**', '`', str(reply_msg.from_user.id), - str(message.chat.id)])) + ['**', '`', str(reply_msg.from_user.id), str(message.chat.id)], + ), + ) else: edit(message, f'`{get_translation("lydiaError2")}`') @@ -83,9 +68,8 @@ def addcf(message): def remcf(message): if not LYDIA_APIKEY: return edit( - message, get_translation( - 'lydiaMissingApi', [ - '**', '`']), preview=False) + message, get_translation('lydiaMissingApi', ['**', '`']), preview=False + ) edit(message, f'`{get_translation("processing")}`') sleep(3) reply_msg = message.reply_to_message @@ -95,16 +79,14 @@ def remcf(message): message, get_translation( 'lydiaResult3', - ['**', '`', str(reply_msg.from_user.id), - str(message.chat.id)])) + ['**', '`', str(reply_msg.from_user.id), str(message.chat.id)], + ), + ) except Exception: edit(message, f'`{get_translation("lydiaError3")}`') -@sedenify(incoming=True, - outgoing=False, - disable_edited=True, - disable_notify=True) +@sedenify(incoming=True, outgoing=False, disable_edited=True, disable_notify=True) def user(message): try: session = ACC_LYDIA[message.chat.id & message.from_user.id] diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/lyrics.py index ac213af..e4de102 100644 --- a/sedenbot/modules/lyrics.py +++ b/sedenbot/modules/lyrics.py @@ -8,10 +8,8 @@ # from lyricsgenius import Genius - -from sedenbot import HELP, GENIUS_TOKEN -from sedenecem.core import (edit, reply_doc, extract_args, - sedenify, get_translation) +from sedenbot import GENIUS_TOKEN, HELP +from sedenecem.core import edit, extract_args, get_translation, reply_doc, sedenify @sedenify(pattern='^.lyrics') @@ -52,12 +50,15 @@ def lyrics(message): if len(songs.lyrics) > 4096: edit(message, f'`{get_translation("lyricsOutput")}`') with open('lyrics.txt', 'w+') as f: - f.write(get_translation('lyricsQuery', [ - '', '', artist, song, songs.lyrics])) + f.write( + get_translation('lyricsQuery', ['', '', artist, song, songs.lyrics]) + ) reply_doc(message, 'lyrics.txt', delete_after_send=True) else: - edit(message, get_translation('lyricsQuery', [ - '**', '`', artist, song, songs.lyrics])) + edit( + message, + get_translation('lyricsQuery', ['**', '`', artist, song, songs.lyrics]), + ) return diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index 1e5f851..e491903 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -7,83 +7,398 @@ # All rights reserved. See COPYING, AUTHORS. # +from random import choice, getrandbits, randint +from re import sub from time import sleep -from re import sub -from random import choice, getrandbits, randint from cowpy import cow from requests import get - from sedenbot import HELP -from sedenecem.core import (edit, extract_args, sedenify, - get_translation, parse_cmd) +from sedenecem.core import edit, extract_args, get_translation, parse_cmd, sedenify + # ================= CONSTANT ================= -ZALGS = [['̖', ' ̗', ' ̘', ' ̙', ' ̜', ' ̝', ' ̞', ' ̟', ' ̠', ' ̤', ' ̥', - ' ̦', ' ̩', ' ̪', ' ̫', ' ̬', ' ̭', ' ̮', ' ̯', ' ̰', ' ̱', ' ̲', - ' ̳', ' ̹', ' ̺', ' ̻', ' ̼', ' ͅ', ' ͇', ' ͈', ' ͉', ' ͍', ' ͎', - ' ͓', ' ͔', ' ͕', ' ͖', ' ͙', ' ͚', ' '], - [' ̍', ' ̎', ' ̄', ' ̅', ' ̿', ' ̑', ' ̆', ' ̐', ' ͒', ' ͗', ' ͑', - ' ̇', ' ̈', ' ̊', ' ͂', ' ̓', ' ̈́', ' ͊', ' ͋', ' ͌', ' ̃', ' ̂', - ' ̌', ' ͐', ' ́', ' ̋', ' ̏', ' ̽', ' ̉', ' ͣ', ' ͤ', ' ͥ', ' ͦ', - ' ͧ', ' ͨ', ' ͩ', ' ͪ', ' ͫ', ' ͬ', ' ͭ', ' ͮ', ' ͯ', ' ̾', ' ͛', - ' ͆', ' ̚'], - [' ̕', ' ̛', ' ̀', ' ́', ' ͘', ' ̡', ' ̢', ' ̧', ' ̨', ' ̴', ' ̵', - ' ̶', ' ͜', ' ͝', ' ͞', ' ͟', ' ͠', ' ͢', ' ̸', ' ̷', ' ͡']] - -EMOJIS = ['😂', '😂', '👌', '✌', '💞', '👍', '👌', '💯', '🎶', '👀', - '😂', '👓', '👏', '👐', '🍕', '💥', '🍴', '💦', '💦', - '🍑', '🍆', '😩', '😏', '👉👌', '👀', '👅', '😩', '🚰', - '♿'] - -UWUS = ['(・`ω´・)', ';;w;;', 'owo', 'UwU', '>w<', '^w^', r'\(^o\) (/o^)/', - '( ^ _ ^)∠☆', '(ô_ô)', '~:o', ';-;', '(*^*)', '(>_', '(♥_♥)', - '*(^O^)*', '((+_+))'] - -REACTS = ['ʘ‿ʘ', 'ヾ(-_- )ゞ', '(っ˘ڡ˘ς)', '(´ж`ς)', '( ಠ ʖ̯ ಠ)', '(° ͜ʖ͡°)╭∩╮', - '(ᵟຶ︵ ᵟຶ)', '(งツ)ว', 'ʚ(•`', '(っ▀¯▀)つ', '(◠﹏◠)', '( ͡ಠ ʖ̯ ͡ಠ)', - '( ఠ ͟ʖ ఠ)', '(∩`-´)⊃━☆゚.*・。゚', '(⊃。•́‿•̀。)⊃', '(._.)', '{•̃_•̃}', - '(ᵔᴥᵔ)', '♨_♨', '⥀.⥀', 'ح˚௰˚づ ', '(҂◡_◡)', '(っ•́。•́)♪♬', - '◖ᵔᴥᵔ◗ ♪ ♫ ', '(☞゚ヮ゚)☞', '[¬º-°]¬', '(Ծ‸ Ծ)', '(•̀ᴗ•́)و ̑̑', - 'ヾ(´〇`)ノ♪♪♪', "(ง'̀-'́)ง", 'ლ(•́•́ლ)', 'ʕ •́؈•̀ ₎', '♪♪ ヽ(ˇ∀ˇ )ゞ', - 'щ(゚Д゚щ)', '( ˇ෴ˇ )', '눈_눈', '(๑•́ ₃ •̀๑) ', '( ˘ ³˘)♥ ', - 'ԅ(≖‿≖ԅ)', '♥‿♥', '◔_◔', '⁽⁽ଘ( ˊᵕˋ )ଓ⁾⁾', - '乁( ◔ ౪◔)「 ┑( ̄Д  ̄)┍', '( ఠൠఠ )ノ', '٩(๏_๏)۶', '┌(ㆆ㉨ㆆ)ʃ', - 'ఠ_ఠ', '(づ。◕‿‿◕。)づ', '(ノಠ ∩ಠ)ノ彡( \\o°o)\\', '“ヽ(´▽`)ノ”', - '༼ ༎ຶ ෴ ༎ຶ༽', '。゚( ゚இ‸இ゚)゚。', '(づ ̄ ³ ̄)づ', '(⊙.☉)7', 'ᕕ( ᐛ )ᕗ', - 't(-_-t)', '(ಥ⌣ಥ)', 'ヽ༼ ಠ益ಠ ༽ノ', '༼∵༽ ༼⍨༽ ༼⍢༽ ༼⍤༽', 'ミ●﹏☉ミ', - '(⊙_◎)', '¿ⓧ_ⓧﮌ', 'ಠ_ಠ', '(´・_・`)', 'ᕦ(ò_óˇ)ᕤ', '⊙﹏⊙', - '(╯°□°)╯︵ ┻━┻', r'¯\_(⊙︿⊙)_/¯', '٩◔̯◔۶', '°‿‿°', 'ᕙ(⇀‸↼‶)ᕗ', - '⊂(◉‿◉)つ', 'V•ᴥ•V', 'q(❂‿❂)p', 'ಥ_ಥ', 'ฅ^•ﻌ•^ฅ', 'ಥ﹏ಥ', - '( ^_^)o自自o(^_^ )', 'ಠ‿ಠ', 'ヽ(´▽`)/', 'ᵒᴥᵒ#', '( ͡° ͜ʖ ͡°)', - '┬─┬ ノ( ゜-゜ノ)', 'ヽ(´ー`)ノ', '☜(⌒▽⌒)☞', 'ε=ε=ε=┌(;*´Д`)ノ', '(╬ ಠ益ಠ)', - '┬─┬⃰͡ (ᵔᵕᵔ͜ )', '┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻', r'¯\_(ツ)_/¯', 'ʕᵔᴥᵔʔ', - '(`・ω・´)', 'ʕ•ᴥ•ʔ', 'ლ(`ー´ლ)', 'ʕʘ̅͜ʘ̅ʔ', '( ゚Д゚)', r'¯\(°_o)/¯', - '(。◕‿◕。)'] +ZALGS = [ + [ + '̖', + ' ̗', + ' ̘', + ' ̙', + ' ̜', + ' ̝', + ' ̞', + ' ̟', + ' ̠', + ' ̤', + ' ̥', + ' ̦', + ' ̩', + ' ̪', + ' ̫', + ' ̬', + ' ̭', + ' ̮', + ' ̯', + ' ̰', + ' ̱', + ' ̲', + ' ̳', + ' ̹', + ' ̺', + ' ̻', + ' ̼', + ' ͅ', + ' ͇', + ' ͈', + ' ͉', + ' ͍', + ' ͎', + ' ͓', + ' ͔', + ' ͕', + ' ͖', + ' ͙', + ' ͚', + ' ', + ], + [ + ' ̍', + ' ̎', + ' ̄', + ' ̅', + ' ̿', + ' ̑', + ' ̆', + ' ̐', + ' ͒', + ' ͗', + ' ͑', + ' ̇', + ' ̈', + ' ̊', + ' ͂', + ' ̓', + ' ̈́', + ' ͊', + ' ͋', + ' ͌', + ' ̃', + ' ̂', + ' ̌', + ' ͐', + ' ́', + ' ̋', + ' ̏', + ' ̽', + ' ̉', + ' ͣ', + ' ͤ', + ' ͥ', + ' ͦ', + ' ͧ', + ' ͨ', + ' ͩ', + ' ͪ', + ' ͫ', + ' ͬ', + ' ͭ', + ' ͮ', + ' ͯ', + ' ̾', + ' ͛', + ' ͆', + ' ̚', + ], + [ + ' ̕', + ' ̛', + ' ̀', + ' ́', + ' ͘', + ' ̡', + ' ̢', + ' ̧', + ' ̨', + ' ̴', + ' ̵', + ' ̶', + ' ͜', + ' ͝', + ' ͞', + ' ͟', + ' ͠', + ' ͢', + ' ̸', + ' ̷', + ' ͡', + ], +] + +EMOJIS = [ + '😂', + '😂', + '👌', + '✌', + '💞', + '👍', + '👌', + '💯', + '🎶', + '👀', + '😂', + '👓', + '👏', + '👐', + '🍕', + '💥', + '🍴', + '💦', + '💦', + '🍑', + '🍆', + '😩', + '😏', + '👉👌', + '👀', + '👅', + '😩', + '🚰', + '♿', +] + +UWUS = [ + '(・`ω´・)', + ';;w;;', + 'owo', + 'UwU', + '>w<', + '^w^', + r'\(^o\) (/o^)/', + '( ^ _ ^)∠☆', + '(ô_ô)', + '~:o', + ';-;', + '(*^*)', + '(>_', + '(♥_♥)', + '*(^O^)*', + '((+_+))', +] + +REACTS = [ + 'ʘ‿ʘ', + 'ヾ(-_- )ゞ', + '(っ˘ڡ˘ς)', + '(´ж`ς)', + '( ಠ ʖ̯ ಠ)', + '(° ͜ʖ͡°)╭∩╮', + '(ᵟຶ︵ ᵟຶ)', + '(งツ)ว', + 'ʚ(•`', + '(っ▀¯▀)つ', + '(◠﹏◠)', + '( ͡ಠ ʖ̯ ͡ಠ)', + '( ఠ ͟ʖ ఠ)', + '(∩`-´)⊃━☆゚.*・。゚', + '(⊃。•́‿•̀。)⊃', + '(._.)', + '{•̃_•̃}', + '(ᵔᴥᵔ)', + '♨_♨', + '⥀.⥀', + 'ح˚௰˚づ ', + '(҂◡_◡)', + '(っ•́。•́)♪♬', + '◖ᵔᴥᵔ◗ ♪ ♫ ', + '(☞゚ヮ゚)☞', + '[¬º-°]¬', + '(Ծ‸ Ծ)', + '(•̀ᴗ•́)و ̑̑', + 'ヾ(´〇`)ノ♪♪♪', + "(ง'̀-'́)ง", + 'ლ(•́•́ლ)', + 'ʕ •́؈•̀ ₎', + '♪♪ ヽ(ˇ∀ˇ )ゞ', + 'щ(゚Д゚щ)', + '( ˇ෴ˇ )', + '눈_눈', + '(๑•́ ₃ •̀๑) ', + '( ˘ ³˘)♥ ', + 'ԅ(≖‿≖ԅ)', + '♥‿♥', + '◔_◔', + '⁽⁽ଘ( ˊᵕˋ )ଓ⁾⁾', + '乁( ◔ ౪◔)「 ┑( ̄Д  ̄)┍', + '( ఠൠఠ )ノ', + '٩(๏_๏)۶', + '┌(ㆆ㉨ㆆ)ʃ', + 'ఠ_ఠ', + '(づ。◕‿‿◕。)づ', + '(ノಠ ∩ಠ)ノ彡( \\o°o)\\', + '“ヽ(´▽`)ノ”', + '༼ ༎ຶ ෴ ༎ຶ༽', + '。゚( ゚இ‸இ゚)゚。', + '(づ ̄ ³ ̄)づ', + '(⊙.☉)7', + 'ᕕ( ᐛ )ᕗ', + 't(-_-t)', + '(ಥ⌣ಥ)', + 'ヽ༼ ಠ益ಠ ༽ノ', + '༼∵༽ ༼⍨༽ ༼⍢༽ ༼⍤༽', + 'ミ●﹏☉ミ', + '(⊙_◎)', + '¿ⓧ_ⓧﮌ', + 'ಠ_ಠ', + '(´・_・`)', + 'ᕦ(ò_óˇ)ᕤ', + '⊙﹏⊙', + '(╯°□°)╯︵ ┻━┻', + r'¯\_(⊙︿⊙)_/¯', + '٩◔̯◔۶', + '°‿‿°', + 'ᕙ(⇀‸↼‶)ᕗ', + '⊂(◉‿◉)つ', + 'V•ᴥ•V', + 'q(❂‿❂)p', + 'ಥ_ಥ', + 'ฅ^•ﻌ•^ฅ', + 'ಥ﹏ಥ', + '( ^_^)o自自o(^_^ )', + 'ಠ‿ಠ', + 'ヽ(´▽`)/', + 'ᵒᴥᵒ#', + '( ͡° ͜ʖ ͡°)', + '┬─┬ ノ( ゜-゜ノ)', + 'ヽ(´ー`)ノ', + '☜(⌒▽⌒)☞', + 'ε=ε=ε=┌(;*´Д`)ノ', + '(╬ ಠ益ಠ)', + '┬─┬⃰͡ (ᵔᵕᵔ͜ )', + '┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻', + r'¯\_(ツ)_/¯', + 'ʕᵔᴥᵔʔ', + '(`・ω・´)', + 'ʕ•ᴥ•ʔ', + 'ლ(`ー´ლ)', + 'ʕʘ̅͜ʘ̅ʔ', + '( ゚Д゚)', + r'¯\(°_o)/¯', + '(。◕‿◕。)', +] RUNS = [get_translation(f'runstr{i+1}') for i in range(0, 48)] -SHGS = ['┐(´д`)┌', '┐(´~`)┌', '┐(´ー`)┌', '┐( ̄ヘ ̄)┌', '╮(╯∀╰)╭', '╮(╯_╰)╭', - '┐(´д`)┌', '┐(´∀`)┌', 'ʅ(́◡◝)ʃ', '┐(゚~゚)┌', "┐('д')┌", '┐(‘~`;)┌', - 'ヘ(´-`;)ヘ', '┐( -“-)┌', 'ʅ(´◔౪◔)ʃ', 'ヽ(゜~゜o)ノ', 'ヽ(~~~ )ノ', - '┐(~ー~;)┌', '┐(-。ー;)┌', r'¯\_(ツ)_/¯', r'¯\_(⊙_ʖ⊙)_/¯', - r'¯\_༼ ಥ ‿ ಥ ༽_/¯', '乁( ⁰͡ Ĺ̯ ⁰͡ ) ㄏ'] - -CRYS = ['أ‿أ', '╥﹏╥', '(;﹏;)', '(ToT)', '(┳Д┳)', '(ಥ﹏ಥ)', '(;へ:)', - '(T_T)', '(πーπ)', '(T▽T)', '(⋟﹏⋞)', '(iДi)', '(´Д⊂ヽ', - '(;Д;)', '(>﹏<)', '(TдT)', '(つ﹏⊂)', '༼☯﹏☯༽', '(ノ﹏ヽ)', '(ノAヽ)', - '(╥_╥)', '(T⌓T)', '(༎ຶ⌑༎ຶ)', '(☍﹏⁰)。', '(ಥ_ʖಥ)', '(つд⊂)', '(≖͞_≖̥)', - '(இ﹏இ`。)', '༼ಢ_ಢ༽', '༼ ༎ຶ ෴ ༎ຶ༽'] - -XDA_STRINGS = ['sur', 'Sir', 'bro', 'yes', 'no', 'bolte', 'bolit', - 'bholit', 'volit', 'mustah', 'fap', 'lit', 'lmao', - 'iz', 'jiosim', 'ijo', 'nut', 'workz', 'workang', - 'flashabl zip', 'bateri', 'bacup', 'bad englis', - 'sar', 'treble wen', 'gsi', 'fox bag', 'bag fox', - 'fine', 'bast room', 'fax', 'trable', 'kenzo', - 'plz make room', 'andreid pai', 'when', 'port', - 'mtk', 'send moni', 'bad rom', 'dot', 'rr', 'linage', - 'arrows', 'kernal', 'meme12', 'bruh', 'imail', - 'email', 'plaka', 'evox'] +SHGS = [ + '┐(´д`)┌', + '┐(´~`)┌', + '┐(´ー`)┌', + '┐( ̄ヘ ̄)┌', + '╮(╯∀╰)╭', + '╮(╯_╰)╭', + '┐(´д`)┌', + '┐(´∀`)┌', + 'ʅ(́◡◝)ʃ', + '┐(゚~゚)┌', + "┐('д')┌", + '┐(‘~`;)┌', + 'ヘ(´-`;)ヘ', + '┐( -“-)┌', + 'ʅ(´◔౪◔)ʃ', + 'ヽ(゜~゜o)ノ', + 'ヽ(~~~ )ノ', + '┐(~ー~;)┌', + '┐(-。ー;)┌', + r'¯\_(ツ)_/¯', + r'¯\_(⊙_ʖ⊙)_/¯', + r'¯\_༼ ಥ ‿ ಥ ༽_/¯', + '乁( ⁰͡ Ĺ̯ ⁰͡ ) ㄏ', +] + +CRYS = [ + 'أ‿أ', + '╥﹏╥', + '(;﹏;)', + '(ToT)', + '(┳Д┳)', + '(ಥ﹏ಥ)', + '(;へ:)', + '(T_T)', + '(πーπ)', + '(T▽T)', + '(⋟﹏⋞)', + '(iДi)', + '(´Д⊂ヽ', + '(;Д;)', + '(>﹏<)', + '(TдT)', + '(つ﹏⊂)', + '༼☯﹏☯༽', + '(ノ﹏ヽ)', + '(ノAヽ)', + '(╥_╥)', + '(T⌓T)', + '(༎ຶ⌑༎ຶ)', + '(☍﹏⁰)。', + '(ಥ_ʖಥ)', + '(つд⊂)', + '(≖͞_≖̥)', + '(இ﹏இ`。)', + '༼ಢ_ಢ༽', + '༼ ༎ຶ ෴ ༎ຶ༽', +] + +XDA_STRINGS = [ + 'sur', + 'Sir', + 'bro', + 'yes', + 'no', + 'bolte', + 'bolit', + 'bholit', + 'volit', + 'mustah', + 'fap', + 'lit', + 'lmao', + 'iz', + 'jiosim', + 'ijo', + 'nut', + 'workz', + 'workang', + 'flashabl zip', + 'bateri', + 'bacup', + 'bad englis', + 'sar', + 'treble wen', + 'gsi', + 'fox bag', + 'bag fox', + 'fine', + 'bast room', + 'fax', + 'trable', + 'kenzo', + 'plz make room', + 'andreid pai', + 'when', + 'port', + 'mtk', + 'send moni', + 'bad rom', + 'dot', + 'rr', + 'linage', + 'arrows', + 'kernal', + 'meme12', + 'bruh', + 'imail', + 'email', + 'plaka', + 'evox', +] # ================= CONSTANT ================= @@ -91,7 +406,7 @@ def cowsay(message): ext = message.text.split(' ', 1) arg = parse_cmd(ext[0]) - arg = arg[:arg.find('say')] + arg = arg[: arg.find('say')] textx = message.reply_to_message if textx and textx.text: text = textx.text @@ -192,8 +507,7 @@ def stretch(message): return count = randint(3, 10) - reply_text = sub(r'([aeiouAEIOUaeiouAEIOUаеиоуюяыэё])', (r'\1' * count), - stretch) + reply_text = sub(r'([aeiouAEIOUaeiouAEIOUаеиоуюяыэё])', (r'\1' * count), stretch) edit(message, reply_text) @@ -301,8 +615,10 @@ def lfy(message): lfy_url = f'http://lmgtfy.com/?s=g&iie=1&q={query_encoded}' payload = {'format': 'json', 'url': lfy_url} r = get('http://is.gd/create.php', params=payload) - edit(message, f'`{get_translation("lfyResult")}`' - f"\n[{query}]({r.json()['shorturl']})") + edit( + message, + f'`{get_translation("lfyResult")}`' f"\n[{query}]({r.json()['shorturl']})", + ) @sedenify(pattern=r'.scam', compat=False) @@ -319,7 +635,8 @@ def scam(client, message): 'record_video_note', 'upload_video_note', 'choose_contact', - 'playing'] + 'playing', + ] input_str = extract_args(message) args = input_str.split() if len(args) == 0: @@ -390,23 +707,26 @@ def oof(message): @sedenify(pattern='^.10iq$') def iqless(message): - edit(message, - 'DÜÜÜT DÜÜÜTT AÇ YOLU AÇÇ HADİ ASLAN PARÇASI YOLU AÇ \n' - 'HADİ BAK ENGELLİ BEKLİYO BURDA HADİ DÜÜÜTTT ♿️ BAK \n' - 'SİNİRLENDİ ARKADAŞ HADİ YOLU AÇ HADİİ DÜÜÜT DÜÜTT BİİİPP \n' - 'HADİ BE HIZLI OLL DÜÜÜTT BİİİPPP ♿️♿️ BAK HIZLANDI ENGELLİ \n' - 'KARDEŞİMİZ SERİ KÖZ GETİR SERİ DÜÜÜTT DÜÜÜT DÜÜÜÜTTTTT \n' - 'BİİİİPPP BİİİİİPPP DÜÜÜTTT ♿️♿️♿️♿️ BAK ARTIYO SAYILARI \n' - 'AÇTIN MI YOLU AÇMADIN PÜÜÜÜ REZİİİLL DÜÜÜÜTTT ♿️♿️♿️ \n' - '♿️♿️♿️ BAK KALABALIKLASTI BAK DELI GELIYOR DELIRDI DELI \n' - 'AC YOLU DUTDUTDURURURUDUTTT♿️♿️♿️♿️♿️♿️♿️♿️♿️ \n' - '♿️♿️♿️♿️♿️KAFAYI YEDI BUNLAR AC LAAAAN YOLU') + edit( + message, + 'DÜÜÜT DÜÜÜTT AÇ YOLU AÇÇ HADİ ASLAN PARÇASI YOLU AÇ \n' + 'HADİ BAK ENGELLİ BEKLİYO BURDA HADİ DÜÜÜTTT ♿️ BAK \n' + 'SİNİRLENDİ ARKADAŞ HADİ YOLU AÇ HADİİ DÜÜÜT DÜÜTT BİİİPP \n' + 'HADİ BE HIZLI OLL DÜÜÜTT BİİİPPP ♿️♿️ BAK HIZLANDI ENGELLİ \n' + 'KARDEŞİMİZ SERİ KÖZ GETİR SERİ DÜÜÜTT DÜÜÜT DÜÜÜÜTTTTT \n' + 'BİİİİPPP BİİİİİPPP DÜÜÜTTT ♿️♿️♿️♿️ BAK ARTIYO SAYILARI \n' + 'AÇTIN MI YOLU AÇMADIN PÜÜÜÜ REZİİİLL DÜÜÜÜTTT ♿️♿️♿️ \n' + '♿️♿️♿️ BAK KALABALIKLASTI BAK DELI GELIYOR DELIRDI DELI \n' + 'AC YOLU DUTDUTDURURURUDUTTT♿️♿️♿️♿️♿️♿️♿️♿️♿️ \n' + '♿️♿️♿️♿️♿️KAFAYI YEDI BUNLAR AC LAAAAN YOLU', + ) @sedenify(pattern='^.mizah$') def mizahshow(message): edit( - message, '⚠️⚠️⚠️MmMmMmMizahh Şoww😨😨😨😨😱😱😱😱😱 \n' + message, + '⚠️⚠️⚠️MmMmMmMizahh Şoww😨😨😨😨😱😱😱😱😱 \n' '😱😱⚠️⚠️ 😂😂😂😂😂😂😂😂😂😂😂😂😂😂😱😵 \n' '😂😂👍👍👍👍👍👍👍👍👍👍👍👍👍 MiZah \n' 'ŞeLaLesNdEn b1r yUdm aLdım✔️✔️✔️✔️ \n' @@ -426,32 +746,35 @@ def mizahshow(message): 'AJXJAJXJJAJXJWJFWJJFWIIFIWICIWIFIWICJAXJWJFJEICIIEICIEIFIWICJSXJJS \n' 'CJEIVIAJXBWJCJIQICIWJX💯💯💯💯💯💯😂😂😂😂😂😂😂 \n' '😂⚠️😂😂😂😂😂😂⚠️⚠️⚠️😂😂😂😂♿️♿️♿️😅😅 \n' - '😅😂👏💯⚠️👏♿️🚨') + '😅😂👏💯⚠️👏♿️🚨', + ) @sedenify(pattern='^.h$') def h(message): - edit(message, - '⠀⠀⠀⠀⠀⠀⠀⢀⠀⠂⠂⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠠⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠄⠈⠐⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⡐⠀⠀⠀⠀⠀⠀⠀⢡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠊⠀⠀⢸⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠀⠀⠀⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⢣⠀⠀⠀\n' - '⠀⠀⠀⢸⠀⠀⠀⡜⠀⡆⠀⠀⠀⢀⣲⠀⠀⠀⠀⠀⠀⠴⠀⠀⡇⠀⠀⡀⠀⠀\n' - '⠀⠀⠀⡜⠀⠀⠁⠀⠀⠘⠀⠀⠀⠀⠀⢘⣄⠀⠀⠀⡜⣀⠀⢠⠉⠀⠀⢠⠀⠀\n' - '⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢠⠀⠈⠛⠛⠒⡀⠀⡇⠀⡄⠀⠈⠀⠀\n' - '⠀⠀⠀⢒⠀⠀⡱⠀⠀⠀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠇⠀⠈⠀⠀⠀⠀\n' - '⠀⠀⠀⢸⠀⢠⠀⠀⠀⢸⠀⠀⠀⠀⢀⠀⠙⠁⠀⠁⣉⠊⠀⡆⠀⠀⠈⠀⡅⠀\n' - '⠀⠀⠀⠀⡀⠈⠀⠀⠀⠃⠀⠀⠀⠀⡌⠈⠀⠑⠃⠋⠀⠀⠀⡇⠀⠀⠀⠀⢠⠀\n' - '⠀⠀⠀⠀⠘⠀⠈⡀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⡀⠈⠀\n' - '⠀⠀⠀⠀⠀⣂⢀⢸⠀⢱⢀⣤⢀⠀⠃⠀⠀⠀⠀⢂⠀⠀⠂⠂⠀⠀⠀⣘⡈⡀\n' - '⠀⠀⠀⠀⠀⠀⠠⠹⠓⢸⠀⠀⢀⠓⠀⠀⠀⠀⠀⡞⢀⠀⢀⠀⠀⠀⠐⢹⠂⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠈⠛⠇⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠂⠁⠀⠀⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠀⡌⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⡠⠀⠀⠀⠀⠀⠀⡆⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠊⠠⠂⠉⢤⣀⠀⠀⠀⠀⢠⠀⠐⠣⠠⢤⠀⠀⠀⠀⠀⠀⠀\n' - '⠀⠀⠀⠀⠀⠀⠀⠁⠂⠤⠼⠓⠓⠒⠀⠀⠀⠈⠂⠀⠀⠀⠂⠚⠁⠀⠀⠀⠀⠀') + edit( + message, + '⠀⠀⠀⠀⠀⠀⠀⢀⠀⠂⠂⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠠⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠄⠈⠐⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⡐⠀⠀⠀⠀⠀⠀⠀⢡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠊⠀⠀⢸⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠀⠀⠀⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⢣⠀⠀⠀\n' + '⠀⠀⠀⢸⠀⠀⠀⡜⠀⡆⠀⠀⠀⢀⣲⠀⠀⠀⠀⠀⠀⠴⠀⠀⡇⠀⠀⡀⠀⠀\n' + '⠀⠀⠀⡜⠀⠀⠁⠀⠀⠘⠀⠀⠀⠀⠀⢘⣄⠀⠀⠀⡜⣀⠀⢠⠉⠀⠀⢠⠀⠀\n' + '⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢠⠀⠈⠛⠛⠒⡀⠀⡇⠀⡄⠀⠈⠀⠀\n' + '⠀⠀⠀⢒⠀⠀⡱⠀⠀⠀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠇⠀⠈⠀⠀⠀⠀\n' + '⠀⠀⠀⢸⠀⢠⠀⠀⠀⢸⠀⠀⠀⠀⢀⠀⠙⠁⠀⠁⣉⠊⠀⡆⠀⠀⠈⠀⡅⠀\n' + '⠀⠀⠀⠀⡀⠈⠀⠀⠀⠃⠀⠀⠀⠀⡌⠈⠀⠑⠃⠋⠀⠀⠀⡇⠀⠀⠀⠀⢠⠀\n' + '⠀⠀⠀⠀⠘⠀⠈⡀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⡀⠈⠀\n' + '⠀⠀⠀⠀⠀⣂⢀⢸⠀⢱⢀⣤⢀⠀⠃⠀⠀⠀⠀⢂⠀⠀⠂⠂⠀⠀⠀⣘⡈⡀\n' + '⠀⠀⠀⠀⠀⠀⠠⠹⠓⢸⠀⠀⢀⠓⠀⠀⠀⠀⠀⡞⢀⠀⢀⠀⠀⠀⠐⢹⠂⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠈⠛⠇⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠂⠁⠀⠀⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠀⡌⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⡠⠀⠀⠀⠀⠀⠀⡆⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠊⠠⠂⠉⢤⣀⠀⠀⠀⠀⢠⠀⠐⠣⠠⢤⠀⠀⠀⠀⠀⠀⠀\n' + '⠀⠀⠀⠀⠀⠀⠀⠁⠂⠤⠼⠓⠓⠒⠀⠀⠀⠈⠂⠀⠀⠀⠂⠚⠁⠀⠀⠀⠀⠀', + ) @sedenify(pattern='^.react$') @@ -483,9 +806,11 @@ def xda(message): @sedenify(pattern='^.f (.*)') def payf(message): paytext = extract_args(message) - pay = f'{paytext * 8}\n{paytext * 8}\n{paytext * 2}\n{paytext * 2}' \ - f'\n{paytext * 2}\n{paytext * 6}\n{paytext * 6}\n{paytext * 2}' \ - f'\n{paytext * 2}\n{paytext * 2}\n{paytext * 2}\n{paytext * 2}' + pay = ( + f'{paytext * 8}\n{paytext * 8}\n{paytext * 2}\n{paytext * 2}' + f'\n{paytext * 2}\n{paytext * 6}\n{paytext * 6}\n{paytext * 2}' + f'\n{paytext * 2}\n{paytext * 2}\n{paytext * 2}\n{paytext * 2}' + ) edit(message, pay) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index c01ce2c..e5cb6b2 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -7,17 +7,24 @@ # All rights reserved. See COPYING, AUTHORS. # -from random import choice from os import remove +from random import choice +from subprocess import PIPE +from subprocess import run as runapp -from subprocess import PIPE, run as runapp -from requests import post -from pybase64 import b64encode, b64decode from image_to_ascii import ImageToAscii - +from pybase64 import b64decode, b64encode +from requests import post from sedenbot import HELP, SUPPORT_GROUP -from sedenecem.core import (edit, reply, reply_doc, sedenify, extract_args, - download_media_wc, get_translation) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply, + reply_doc, + sedenify, +) @sedenify(pattern='^.random') @@ -28,15 +35,12 @@ def random(message): edit(message, f'`{get_translation("randomUsage")}`') return - edit(message, get_translation( - 'randomResult', ['**', '`', items, choice(args)])) + edit(message, get_translation('randomResult', ['**', '`', items, choice(args)])) @sedenify(pattern='^.chatid$', private=False) def chatid(message): - edit( - message, - get_translation('chatidResult', ['`', str(message.chat.id)])) + edit(message, get_translation('chatidResult', ['`', str(message.chat.id)])) @sedenify(pattern='^.id$') @@ -55,10 +59,7 @@ def userid(message): name = f'**@{reply.forward_from.username}**' else: name = f'**[{reply.forward_from.first_name}](tg://user?id={reply.forward_from.id})**' - edit( - message, get_translation( - 'useridResult', [ - '**', name, '`', user_id])) + edit(message, get_translation('useridResult', ['**', name, '`', user_id])) else: edit(message, f'`{get_translation("wrongCommand")}`') @@ -71,8 +72,7 @@ def kickme(client, message): @sedenify(pattern='^.support$') def support(message): - edit(message, get_translation('supportResult', [SUPPORT_GROUP]), - preview=False) + edit(message, get_translation('supportResult', [SUPPORT_GROUP]), preview=False) @sedenify(pattern='^.founder') @@ -86,16 +86,17 @@ def readme(message): message, '[Seden README.md](https://github.com/TeamDerUntergang/' 'Telegram-SedenUserBot/blob/seden/README.md)', - preview=False) + preview=False, + ) @sedenify(pattern='^.repo$') def repo(message): edit( message, - '[Seden Repo](https://github.com/TeamDerUntergang/' - 'Telegram-SedenUserBot)', - preview=False) + '[Seden Repo](https://github.com/TeamDerUntergang/' 'Telegram-SedenUserBot)', + preview=False, + ) @sedenify(pattern='^.repeat') @@ -169,20 +170,20 @@ def hash(message): sha512 = sha512.stdout.decode() def rem_filename(st): - return st[:st.find(' ')] - - ans = (f'Text: `{hashtxt_}`' - f'\nMD5: `{rem_filename(md5)}`' - f'\nSHA1: `{rem_filename(sha1)}`' - f'\nSHA256: `{rem_filename(sha256)}`' - f'\nSHA512: `{rem_filename(sha512)}`') + return st[: st.find(' ')] + + ans = ( + f'Text: `{hashtxt_}`' + f'\nMD5: `{rem_filename(md5)}`' + f'\nSHA1: `{rem_filename(sha1)}`' + f'\nSHA256: `{rem_filename(sha256)}`' + f'\nSHA512: `{rem_filename(sha512)}`' + ) if len(ans) > 4096: hashfile = open('hash.txt', 'w+') hashfile.write(ans) hashfile.close() - reply_doc(message, - 'hash.txt', - caption=f'`{get_translation("outputTooLarge")}`') + reply_doc(message, 'hash.txt', caption=f'`{get_translation("outputTooLarge")}`') runapp(['rm', 'hash.txt'], stdout=PIPE) message.delete() else: @@ -218,7 +219,8 @@ def birakmamseni(message): 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': f'{url}', - 'X-Requested-With': 'XMLHttpRequest'} + 'X-Requested-With': 'XMLHttpRequest', + } try: response = post(url=url + path, headers=headers) @@ -239,8 +241,11 @@ def img_to_ascii(message): edit(message, f'`{get_translation("wrongCommand")}`') return - if not(reply.photo or (reply.sticker and not reply.sticker.is_animated) or ( - reply.document and 'image' in reply.document.mime_type)): + if not ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ): edit(message, f'`{get_translation("wrongMedia")}`') else: media = download_media_wc(reply, file_name='ascii.png') diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index 8228974..7b8aace 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -7,15 +7,24 @@ # All rights reserved. See COPYING, AUTHORS. # -from sedenbot import HELP, LOGS, LOG_ID -from sedenecem.core import (extract_args, sedenify, edit, get_messages, - reply_msg, forward, send_log, get_translation) +from sedenbot import HELP, LOG_ID, LOGS +from sedenecem.core import ( + edit, + extract_args, + forward, + get_messages, + get_translation, + reply_msg, + sedenify, + send_log, +) def notes_init(): try: global sql from importlib import import_module + sql = import_module('sedenecem.sql.notes_sql') except Exception as e: sql = None @@ -68,12 +77,10 @@ def save_note(message): string = None msg_o = forward(msg, LOG_ID) if not msg_o: - edit( - message, f'`{get_translation("noteError")}`') + edit(message, f'`{get_translation("noteError")}`') return msg_id = msg_o.message_id - send_log(get_translation( - 'notesLog', ['`', message.chat.id, keyword])) + send_log(get_translation('notesLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/ocr.py index 941ce89..81127e9 100644 --- a/sedenbot/modules/ocr.py +++ b/sedenbot/modules/ocr.py @@ -8,17 +8,19 @@ # from os import remove -from requests import post +from requests import post from sedenbot import HELP, OCR_APIKEY -from sedenecem.core import (edit, extract_args, sedenify, - get_translation, download_media_wc) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + sedenify, +) -def ocr_file(filename, - language='eng', - overlay=False, - api_key=OCR_APIKEY): +def ocr_file(filename, language='eng', overlay=False, api_key=OCR_APIKEY): payload = { 'isOverlayRequired': overlay, @@ -38,9 +40,10 @@ def ocr_file(filename, def ocr(message): if not OCR_APIKEY: return edit( - message, get_translation( - 'ocrApiMissing', [ - '**', 'OCR Space', '`']), preview=False) + message, + get_translation('ocrApiMissing', ['**', 'OCR Space', '`']), + preview=False, + ) match = extract_args(message) reply = message.reply_to_message diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 7630c9c..1fcf068 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -7,23 +7,22 @@ # All rights reserved. See COPYING, AUTHORS. # -from sqlalchemy.exc import IntegrityError - -from sedenbot.modules.chat import is_muted from sedenbot import ( - PM_COUNT, HELP, - PM_AUTO_BAN, LOGS, + PM_AUTO_BAN, + PM_COUNT, PM_LAST_MSG, - PM_UNAPPROVED, PM_MSG_COUNT, + PM_UNAPPROVED, TEMP_SETTINGS, ) -from sedenecem.core import sedenify, send_log, edit, reply, get_translation +from sedenbot.modules.chat import is_muted +from sedenecem.core import edit, get_translation, reply, sedenify, send_log +from sqlalchemy.exc import IntegrityError # ========================= CONSTANTS ============================ -UNAPPROVED_MSG = PM_UNAPPROVED or get_translation("pmpermitMessage", ["`"]) +UNAPPROVED_MSG = PM_UNAPPROVED or get_translation('pmpermitMessage', ['`']) # ================================================================= @@ -32,10 +31,10 @@ def pmpermit_init(): global sql from importlib import import_module - sql = import_module("sedenecem.sql.pm_permit_sql") + sql = import_module('sedenecem.sql.pm_permit_sql') except Exception as e: sql = None - LOGS.warn(get_translation("pmpermitSqlLog")) + LOGS.warn(get_translation('pmpermitSqlLog')) raise e @@ -61,7 +60,7 @@ def permitpm(client, message): if auto_accept(client, message): return - self_user = TEMP_SETTINGS["ME"] + self_user = TEMP_SETTINGS['ME'] if message.chat.id not in [self_user.id, 777000]: try: from sedenecem.sql.pm_permit_sql import is_approved @@ -106,7 +105,7 @@ def permitpm(client, message): send_log( get_translation( - "pmpermitLog", [message.chat.first_name, message.chat.id] + 'pmpermitLog', [message.chat.first_name, message.chat.id] ) ) @@ -114,7 +113,7 @@ def permitpm(client, message): def auto_accept(client, message): - self_user = TEMP_SETTINGS["ME"] + self_user = TEMP_SETTINGS['ME'] if message.chat.id not in [self_user.id, 777000]: try: from sedenecem.sql.pm_permit_sql import approve, is_approved @@ -142,7 +141,7 @@ def auto_accept(client, message): for message in _find_unapproved_msg(client, chat.id): message.delete() send_log( - get_translation("pmAutoAccept", [chat.first_name, chat.id]) + get_translation('pmAutoAccept', [chat.first_name, chat.id]) ) return True except BaseException: @@ -151,7 +150,7 @@ def auto_accept(client, message): return False -@sedenify(outgoing=True, pattern="^.notifoff$") +@sedenify(outgoing=True, pattern='^.notifoff$') def notifoff(message): try: from sedenecem.sql.keep_read_sql import kread @@ -163,7 +162,7 @@ def notifoff(message): edit(message, f'`{get_translation("pmNotifOff")}`') -@sedenify(outgoing=True, pattern="^.notifon$") +@sedenify(outgoing=True, pattern='^.notifon$') def notifon(message): try: from sedenecem.sql.keep_read_sql import unkread @@ -175,7 +174,7 @@ def notifon(message): edit(message, f'`{get_translation("pmNotifOn")}`') -@sedenify(outgoing=True, pattern="^.approve$", compat=False) +@sedenify(outgoing=True, pattern='^.approve$', compat=False) def approvepm(client, message): try: from sedenecem.sql.pm_permit_sql import approve @@ -194,7 +193,7 @@ def approvepm(client, message): uid = replied_user.id else: aname = message.chat - if not aname.type == "private": + if not aname.type == 'private': edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -208,9 +207,9 @@ def approvepm(client, message): edit(message, f'`{get_translation("pmApproveError2")}`') return - edit(message, get_translation("pmApproveSuccess", [name0, uid, "`"])) + edit(message, get_translation('pmApproveSuccess', [name0, uid, '`'])) - send_log(get_translation("pmApproveLog", [name0, uid])) + send_log(get_translation('pmApproveLog', [name0, uid])) @sedenify(outgoing=True, pattern="^.disapprove$") @@ -232,7 +231,7 @@ def disapprovepm(message): uid = replied_user.id else: aname = message.chat - if not aname.type == "private": + if not aname.type == 'private': edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -240,18 +239,18 @@ def disapprovepm(message): dissprove(uid) - edit(message, get_translation("pmDisapprove", [name0, uid, "`"])) + edit(message, get_translation('pmDisapprove', [name0, uid, '`'])) - send_log(get_translation("pmDisapprove", [name0, uid, "`"])) + send_log(get_translation('pmDisapprove', [name0, uid, '`'])) def _find_unapproved_msg(client, chat_id): try: return client.search_messages( - chat_id, from_user="me", limit=10, query=UNAPPROVED_MSG + chat_id, from_user='me', limit=10, query=UNAPPROVED_MSG ) except BaseException: return [] -HELP.update({"pmpermit": get_translation("pmpermitInfo")}) +HELP.update({'pmpermit': get_translation('pmpermitInfo')}) diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 7bfa241..12b3fc9 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -11,13 +11,18 @@ from time import sleep from PIL import Image - from pyrogram.errors import UsernameOccupied -from pyrogram.raw.functions import channels, account - +from pyrogram.raw.functions import account, channels from sedenbot import HELP, TEMP_SETTINGS -from sedenecem.core import (edit, extract_args, sedenify, send_log, - get_translation, download_media_wc) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + sedenify, + send_log, +) + # ====================== CONSTANT =============================== INVALID_MEDIA = get_translation('mediaInvalid') PP_CHANGED = get_translation('ppChanged') @@ -52,8 +57,7 @@ def name(client, message): firstname = namesplit[0] lastname = namesplit[1] - client.send(account.UpdateProfile( - first_name=firstname, last_name=lastname)) + client.send(account.UpdateProfile(first_name=firstname, last_name=lastname)) edit(message, f'`{NAME_OK}`') @@ -61,8 +65,11 @@ def name(client, message): def set_profilepic(client, message): reply = message.reply_to_message photo = None - if (reply and reply.media and (reply.photo or ( - reply.document and 'image' in reply.document.mime_type))): + if ( + reply + and reply.media + and (reply.photo or (reply.document and 'image' in reply.document.mime_type)) + ): photo = download_media_wc(reply, 'profile_photo.jpg') else: edit(message, f'`{INVALID_MEDIA}`') @@ -142,6 +149,7 @@ def blockpm(client, message): try: from sedenecem.sql.pm_permit_sql import dissprove + dissprove(uid) except BaseException: pass @@ -230,7 +238,9 @@ def user_stats(client, message): message, get_translation( 'statsResult', - ['**', '`', chats, channels, groups, sgroups, bots, pms, unread])) + ['**', '`', chats, channels, groups, sgroups, bots, pms, unread], + ), + ) HELP.update({'profile': get_translation('profileInfo')}) diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index fe2527a..45eb2f9 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -8,11 +8,17 @@ # from time import sleep -from pyrogram.errors import FloodWait +from pyrogram.errors import FloodWait from sedenbot import HELP -from sedenecem.core import (extract_args, sedenify, edit, - send_log, reply, get_translation) +from sedenecem.core import ( + edit, + extract_args, + get_translation, + reply, + sedenify, + send_log, +) @sedenify(pattern='^.purge$', compat=False, admin=True) @@ -20,9 +26,8 @@ def purge(client, message): msg = message.reply_to_message if msg: itermsg = client.iter_history( - message.chat.id, - offset_id=msg.message_id, - reverse=True) + message.chat.id, offset_id=msg.message_id, reverse=True + ) else: edit(message, f'`{get_translation("purgeUsage")}`') return @@ -39,10 +44,7 @@ def purge(client, message): edit(message, get_translation('purgeError', ['`', '**', e])) return - done = reply( - message, get_translation( - 'purgeResult', [ - '**', '`', str(count)])) + done = reply(message, get_translation('purgeResult', ['**', '`', str(count)])) send_log(get_translation('purgeLog', ['**', '`', str(count)])) sleep(2) done.delete() @@ -62,10 +64,7 @@ def purgeme(client, message): i = i + 1 message.delete() - smsg = reply( - message, get_translation( - 'purgeResult', [ - '**', '`', str(count)])) + smsg = reply(message, get_translation('purgeResult', ['**', '`', str(count)])) send_log(get_translation('purgeLog', ['**', '`', str(count)])) sleep(2) i = 1 diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index f1493a1..f3e1214 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -8,14 +8,20 @@ # from os import remove -from urllib3 import PoolManager -from bs4 import BeautifulSoup + from barcode import get from barcode.writer import ImageWriter - +from bs4 import BeautifulSoup from sedenbot import HELP -from sedenecem.core import (extract_args, sedenify, edit, reply_doc, - download_media_wc, get_translation) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply_doc, + sedenify, +) +from urllib3 import PoolManager from qrcode import QRCode, constants @@ -26,8 +32,11 @@ def parseqr(message): if not reply: return edit(message, f'`{get_translation("wrongCommand")}`') - if not(reply.photo or reply.sticker or ( - reply.document and 'image' in reply.document.mime_type)): + if not ( + reply.photo + or reply.sticker + or (reply.document and 'image' in reply.document.mime_type) + ): edit(message, f'`{get_translation("wrongCommand")}`') return @@ -39,8 +48,7 @@ def parseqr(message): try: http = PoolManager() - t_response = http.request( - 'POST', 'https://zxing.org/w/decode', fields=files) + t_response = http.request('POST', 'https://zxing.org/w/decode', fields=files) t_response = t_response.data http.clear() dw.close() @@ -120,10 +128,8 @@ def makeqr(message): try: qr = QRCode( - version=1, - error_correction=constants.ERROR_CORRECT_L, - box_size=10, - border=4) + version=1, error_correction=constants.ERROR_CORRECT_L, box_size=10, border=4 + ) qr.add_data(qrmsg) qr.make(fit=True) img = qr.make_image(fill_color='black', back_color='white') diff --git a/sedenbot/modules/quotly.py b/sedenbot/modules/quotly.py index 98b88ab..d0f733d 100644 --- a/sedenbot/modules/quotly.py +++ b/sedenbot/modules/quotly.py @@ -8,10 +8,10 @@ # from time import sleep -from pyrogram.errors import YouBlockedUser +from pyrogram.errors import YouBlockedUser from sedenbot import HELP -from sedenecem.core import sedenify, edit, get_translation, PyroConversation +from sedenecem.core import PyroConversation, edit, get_translation, sedenify @sedenify(pattern='^.q$', compat=False) diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index bb0c3d0..1f6fb07 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -8,25 +8,25 @@ # from os import path, remove -from removebg import RemoveBg -from sedenbot import HELP, RBG_APIKEY, DOWNLOAD_DIRECTORY -from sedenecem.core import (sedenify, edit, reply_doc, - get_translation, download_media_wc) +from removebg import RemoveBg +from sedenbot import DOWNLOAD_DIRECTORY, HELP, RBG_APIKEY +from sedenecem.core import download_media_wc, edit, get_translation, reply_doc, sedenify @sedenify(pattern='^.rbg$') def rbg(message): if not RBG_APIKEY: return edit( - message, get_translation( - 'rbgApiMissing', [ - '**', 'Remove.BG', '`']), preview=False) + message, + get_translation('rbgApiMissing', ['**', 'Remove.BG', '`']), + preview=False, + ) reply = message.reply_to_message if reply and ( - reply.photo or ( - reply.document and 'image' in reply.document.mime_type)): + reply.photo or (reply.document and 'image' in reply.document.mime_type) + ): edit(message, f'`{get_translation("processing")}`') else: edit(message, f'`{get_translation("rbgUsage")}`') @@ -42,8 +42,7 @@ def rbg(message): remove_bg = RemoveBg(RBG_APIKEY, f'{get_translation("rbgLog")}') remove_bg.remove_background_from_img_file(IMG_PATH) rbg_img = IMG_PATH + '_no_bg.png' - reply_doc(reply, rbg_img, - caption=get_translation('rbgResult')) + reply_doc(reply, rbg_img, caption=get_translation('rbgResult')) message.delete() except Exception as e: return edit(message, get_translation('banError', ['`', '**', e])) diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index 4428057..c212164 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -8,17 +8,22 @@ # from os import path, remove -from re import findall, I, M -from urllib import request, parse -from requests import post, get -from PIL import Image -from bs4 import BeautifulSoup +from re import I, M, findall +from urllib import parse, request +from bs4 import BeautifulSoup +from PIL import Image from pyrogram.types import InputMediaPhoto - +from requests import get, post from sedenbot import HELP -from sedenecem.core import (sedenify, edit, reply_doc, extract_args, - download_media_wc, get_translation) +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply_doc, + sedenify, +) opener = request.build_opener() useragent = 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.70 Mobile Safari/537.36' @@ -50,13 +55,8 @@ def reverse(message): image.close() # https://stackoverflow.com/questions/23270175/google-reverse-image-search-using-post-request#28792943 searchUrl = 'https://www.google.com/searchbyimage/upload' - multipart = { - 'encoded_image': (photo, open(photo, 'rb')), - 'image_content': '' - } - response = post(searchUrl, - files=multipart, - allow_redirects=False) + multipart = {'encoded_image': (photo, open(photo, 'rb')), 'image_content': ''} + response = post(searchUrl, files=multipart, allow_redirects=False) fetchUrl = response.headers['Location'] if response != 400: @@ -66,14 +66,12 @@ def reverse(message): return remove(photo) - match = ParseSauce(fetchUrl + - '&preferences?hl=en&fg=1#languages') + match = ParseSauce(fetchUrl + '&preferences?hl=en&fg=1#languages') guess = match['best_guess'] imgspage = match['similar_images'] if guess and imgspage: - edit(message, get_translation( - "reverseResult", [guess, fetchUrl, '`'])) + edit(message, get_translation("reverseResult", [guess, fetchUrl, '`'])) else: edit(message, f'`{get_translation("reverseError2")}`') return @@ -93,8 +91,7 @@ def reverse(message): file.close() yeet.append(InputMediaPhoto(n)) reply_doc(message, yeet) - edit(message, get_translation( - "reverseResult", [guess, fetchUrl, imgspage])) + edit(message, get_translation("reverseResult", [guess, fetchUrl, imgspage])) def ParseSauce(googleurl): @@ -106,8 +103,9 @@ def ParseSauce(googleurl): try: for similar_image in soup.findAll('input', {'class': 'gLFyf'}): - url = 'https://www.google.com/search?tbm=isch&q=' + \ - parse.quote_plus(similar_image.get('value')) + url = 'https://www.google.com/search?tbm=isch&q=' + parse.quote_plus( + similar_image.get('value') + ) results['similar_images'] = url except BaseException: pass diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index 0b3aa85..cc9f440 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -13,10 +13,8 @@ from textwrap import wrap from PIL import Image, ImageChops, ImageDraw, ImageFont - from sedenbot import HELP -from sedenecem.core import (extract_args, sedenify, edit, - send_sticker, get_translation) +from sedenecem.core import edit, extract_args, get_translation, sedenify, send_sticker @sedenify(pattern='^.rgb', compat=False) @@ -52,8 +50,9 @@ def sticklet(client, message): font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) width, height = draw.multiline_textsize(sticktext, font=font) - draw.multiline_text(((512 - width) / 2, (512 - height) / 2), - sticktext, font=font, fill=(R, G, B)) + draw.multiline_text( + ((512 - width) / 2, (512 - height) / 2), sticktext, font=font, fill=(R, G, B) + ) image_stream = BytesIO() image_stream.name = 'image.webp' diff --git a/sedenbot/modules/sangmata.py b/sedenbot/modules/sangmata.py index 0de7828..32b90a2 100644 --- a/sedenbot/modules/sangmata.py +++ b/sedenbot/modules/sangmata.py @@ -8,9 +8,8 @@ # from pyrogram.errors import YouBlockedUser - from sedenbot import HELP -from sedenecem.core import sedenify, edit, get_translation, PyroConversation +from sedenecem.core import PyroConversation, edit, get_translation, sedenify @sedenify(pattern='^.sangmata$', compat=False) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index bb83798..f90a323 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -7,29 +7,36 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove, path -from time import sleep -from re import findall, sub -from urllib.parse import quote_plus -from urllib.error import HTTPError from mimetypes import guess_type +from os import path, remove +from re import findall, sub +from time import sleep from traceback import format_exc -from urbandict import define -from wikipedia import set_lang, summary -from wikipedia.exceptions import DisambiguationError, PageError -from gtts import gTTS -from gtts.lang import tts_langs -from googletrans import LANGUAGES, Translator -from emoji import get_emoji_regexp -from requests import get -from bs4 import BeautifulSoup +from urllib.error import HTTPError +from urllib.parse import quote_plus +from bs4 import BeautifulSoup from currency_converter import CurrencyConverter +from emoji import get_emoji_regexp +from googletrans import LANGUAGES, Translator +from gtts import gTTS +from gtts.lang import tts_langs from pyrogram.types import InputMediaPhoto - +from requests import get from sedenbot import HELP, SEDEN_LANG -from sedenecem.core import (sedenify, edit, send_log, reply_doc, reply_voice, - extract_args, get_webdriver, get_translation) +from sedenecem.core import ( + edit, + extract_args, + get_translation, + get_webdriver, + reply_doc, + reply_voice, + sedenify, + send_log, +) +from urbandict import define +from wikipedia import set_lang, summary +from wikipedia.exceptions import DisambiguationError, PageError CARBONLANG = 'auto' TTS_LANG = SEDEN_LANG @@ -66,7 +73,9 @@ def carbon(message): driver.get(CARBON) edit(message, f'`{get_translation("processing")}\n%50`') driver.command_executor._commands['send_command'] = ( - 'POST', '/session/$sessionId/chromium/send_command') + 'POST', + '/session/$sessionId/chromium/send_command', + ) driver.find_element_by_xpath("//button[contains(text(),'Export')]").click() edit(message, f'`{get_translation("processing")}\n%`75') while not path.isfile('./carbon.png'): @@ -74,8 +83,13 @@ def carbon(message): edit(message, f'`{get_translation("processing")}\n%100`') file = './carbon.png' edit(message, f'`{get_translation("carbonUpload")}`') - reply_doc(message, file, caption=get_translation('carbonResult'), - delete_orig=True, delete_after_send=True) + reply_doc( + message, + file, + caption=get_translation('carbonResult'), + delete_orig=True, + delete_after_send=True, + ) driver.quit() @@ -98,19 +112,29 @@ def img(message): return edit(message, f'`{get_translation("processing")}`') - url = f'https://www.google.com/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' + url = ( + f'https://www.google.com/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' + ) driver = get_webdriver() driver.get(url) count = 1 files = [] for i in driver.find_elements_by_xpath( - '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]'): + '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]' + ): i.click() try_count = 0 - while len(element := driver.find_elements_by_xpath( - '//img[contains(@class,"n3VNCb") and contains(@src,"http")]')) < 1 and try_count < 20: + while ( + len( + element := driver.find_elements_by_xpath( + '//img[contains(@class,"n3VNCb") and contains(@src,"http")]' + ) + ) + < 1 + and try_count < 20 + ): try_count += 1 - sleep(.1) + sleep(0.1) if len(element) < 1: continue link = element[0].get_attribute('src') @@ -119,16 +143,14 @@ def img(message): with open(filename, 'wb') as result: result.write(get(link).content) ftype = guess_type(filename) - if not ftype[0] or ftype[0].split( - '/')[1] not in ['png', 'jpg', 'jpeg']: + if not ftype[0] or ftype[0].split('/')[1] not in ['png', 'jpg', 'jpeg']: remove(filename) continue except Exception: continue files.append(InputMediaPhoto(filename)) sleep(1) - driver.find_elements_by_xpath( - '//a[contains(@class,"hm60ue")]')[0].click() + driver.find_elements_by_xpath('//a[contains(@class,"hm60ue")]')[0].click() count += 1 if lim < count: break @@ -154,14 +176,14 @@ def google(message): except BaseException: page = 1 msg = do_gsearch(match, page) - edit(message, get_translation('googleResult', ['**', '`', match, msg]), - preview=False) + edit( + message, get_translation('googleResult', ['**', '`', match, msg]), preview=False + ) send_log(get_translation('googleLog', [match])) def do_gsearch(query, page): - def find_page(num): if num < 1: num = 1 @@ -171,18 +193,14 @@ def parse_key(keywords): return keywords.replace(' ', '+') def replacer(st): - return sub( - r'[`\*_]', - '', - st).replace( - '\n', - ' ').replace( - '(', - '〈').replace( - ')', - '〉').replace( - '!', - 'ⵑ').strip() + return ( + sub(r'[`\*_]', '', st) + .replace('\n', ' ') + .replace('(', '〈') + .replace(')', '〉') + .replace('!', 'ⵑ') + .strip() + ) def link_replacer(link): rep = {'(': '%28', ')': '%29', '[': '%5B', ']': '%5D', '%': '½'} @@ -207,7 +225,9 @@ def get_result(res): f'https://www.google.com/search?q={query}&gbv=1&sei=2oR3X4nhGY611fAP_5-EkAw&start={find_page(page)}', headers={ 'User-Agent': 'Mozilla/5.0 (compatible; Konqueror/2.2-12; Linux)', - 'Content-Type': 'text/html'}) + 'Content-Type': 'text/html', + }, + ) soup = BeautifulSoup(req.text, 'html.parser') res1 = soup.findAll('div', {'class': ['ezO2md']}) @@ -248,16 +268,19 @@ def ddgo(message): 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)' 'AppleWebKit/537.36 (KHTML, like Gecko)' 'Chrome/81.0.4044.138 Safari/537.36', - 'Content-Type': 'text/html'}) + 'Content-Type': 'text/html', + }, + ) soup = BeautifulSoup(req.text, 'html.parser') res1 = soup.findAll('table', {'border': 0}) res1 = res1[-1].findAll('tr') match = do_ddsearch(res1) edit( - message, get_translation( - 'googleResult', [ - '**', '`', query, match]), preview=False) + message, + get_translation('googleResult', ['**', '`', query, match]), + preview=False, + ) send_log(get_translation('ddgoLog', [query])) @@ -288,10 +311,8 @@ def splitter(res): item = res1[i] link = item[0] ltxt = link.text.replace('|', '-').replace('...', '').strip() - desc = (item[1].text.strip() - if len(item) > 1 - else get_translation('ddgoDesc')) - out += (f'{i+1} - [{ltxt}]({link["href"]})\n{desc}\n\n') + desc = item[1].text.strip() if len(item) > 1 else get_translation('ddgoDesc') + out += f'{i+1} - [{ltxt}]({link["href"]})\n{desc}\n\n' return out @@ -317,17 +338,31 @@ def urbandictionary(message): if int(meanlen) >= 4096: edit(message, f'`{get_translation("outputTooLarge")}`') file = open('urbandictionary.txt', 'w+') - file.write('Query: ' + query + '\n\nMeaning: ' + mean[0]['def'] + - '\n\n' + 'Örnek: \n' + mean[0]['example']) + file.write( + 'Query: ' + + query + + '\n\nMeaning: ' + + mean[0]['def'] + + '\n\n' + + 'Örnek: \n' + + mean[0]['example'] + ) file.close() - reply_doc(message, 'urbandictionary.txt', - caption=f'`{get_translation("outputTooLarge")}`') + reply_doc( + message, + 'urbandictionary.txt', + caption=f'`{get_translation("outputTooLarge")}`', + ) if path.exists('urbandictionary.txt'): remove('urbandictionary.txt') message.delete() return - edit(message, get_translation('sedenQueryUd', [ - '**', '`', query, mean[0]['def'], mean[0]['example']])) + edit( + message, + get_translation( + 'sedenQueryUd', ['**', '`', query, mean[0]['def'], mean[0]['example']] + ), + ) else: edit(message, get_translation('udNoResult', ['**', query])) @@ -353,8 +388,7 @@ def wiki(message): file = open('wiki.txt', 'w+') file.write(result) file.close() - reply_doc(message, 'wiki.txt', - caption=f'`{get_translation("outputTooLarge")}`') + reply_doc(message, 'wiki.txt', caption=f'`{get_translation("outputTooLarge")}`') if path.exists('wiki.txt'): remove('wiki.txt') return @@ -424,15 +458,14 @@ def trt(message): transl_lan = LANGUAGES[f'{reply_text.dest.lower()}'] reply_text = '{}\n\n{}'.format( get_translation( - 'transHeader', - ['**', '`', source_lan.title(), - transl_lan.title()]), - reply_text.text) + 'transHeader', ['**', '`', source_lan.title(), transl_lan.title()] + ), + reply_text.text, + ) edit(message, reply_text) - send_log(get_translation( - 'trtLog', [source_lan.title(), transl_lan.title()])) + send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) def deEmojify(inputString): @@ -467,8 +500,7 @@ def lang(message): else: edit(message, get_translation('scraperTts', ['`', tts_langs()])) return - edit(message, get_translation( - 'scraperResult', ['`', scraper, LANG.title()])) + edit(message, get_translation('scraperResult', ['`', scraper, LANG.title()])) send_log(get_translation('scraperLog', ['`', scraper, LANG.title()])) @@ -498,14 +530,12 @@ def currency(message): currency_to = input_sgra[2].upper() convert = currency.convert(number, currency_from, currency_to) out = round(number * convert, 2) - edit( - message, - f'**{number} {currency_from} = {out} {currency_to}**') + edit(message, f'**{number} {currency_from} = {out} {currency_to}**') except Exception as e: edit(message, str(e)) else: edit(message, f'`{get_translation("syntaxError")}`') - return + return HELP.update({'img': get_translation('imgInfo')}) diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index 4017b11..9ef9ede 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -7,13 +7,19 @@ # All rights reserved. See COPYING, AUTHORS. # -from time import sleep from base64 import b64decode from re import match +from time import sleep from sedenbot import HELP -from sedenecem.core import (sedenify, edit, reply_doc, extract_args, - get_webdriver, get_translation) +from sedenecem.core import ( + edit, + extract_args, + get_translation, + get_webdriver, + reply_doc, + sedenify, +) @sedenify(pattern=r'^.ss') @@ -44,8 +50,7 @@ def ss(message): ) driver.set_window_size(width + 125, height + 125) wait_for = int(height / 1000) - edit( - message, f'`{get_translation("ssResult", [height, width, wait_for])}`') + edit(message, f'`{get_translation("ssResult", [height, width, wait_for])}`') sleep(wait_for) im_png = driver.get_screenshot_as_base64() driver.close() @@ -54,11 +59,8 @@ def ss(message): out.write(b64decode(im_png)) edit(message, f'`{get_translation("ssUpload")}`') reply_doc( - message, - name, - caption=input_str, - delete_after_send=True, - delete_orig=True) + message, name, caption=input_str, delete_after_send=True, delete_orig=True + ) HELP.update({'ss': get_translation('ssInfo')}) diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/sed.py index fe99f26..6079079 100644 --- a/sedenbot/modules/sed.py +++ b/sedenbot/modules/sed.py @@ -7,18 +7,21 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import match, sub, IGNORECASE, I +from re import IGNORECASE, I, match, sub from sre_constants import error as sre_err from sedenbot import HELP -from sedenecem.core import edit, sedenify, get_translation +from sedenecem.core import edit, get_translation, sedenify DELIMITERS = ('/', ':', '|', '_') def separate_sed(sed_string): - if (len(sed_string) > 3 and sed_string[3] in DELIMITERS - and sed_string.count(sed_string[3]) >= 2): + if ( + len(sed_string) > 3 + and sed_string[3] in DELIMITERS + and sed_string.count(sed_string[3]) >= 2 + ): delim = sed_string[3] start = counter = 4 while counter < len(sed_string): @@ -37,9 +40,12 @@ def separate_sed(sed_string): return None while counter < len(sed_string): - if (sed_string[counter] == '\\' and counter + 1 < len(sed_string) - and sed_string[counter + 1] == delim): - sed_string = sed_string[:counter] + sed_string[counter + 1:] + if ( + sed_string[counter] == '\\' + and counter + 1 < len(sed_string) + and sed_string[counter + 1] == delim + ): + sed_string = sed_string[:counter] + sed_string[counter + 1 :] elif sed_string[counter] == delim: replace_with = sed_string[start:counter] @@ -83,8 +89,7 @@ def sed(message): if 'i' in flags and 'g' in flags: text = sub(repl, repl_with, to_fix, flags=I).strip() elif 'i' in flags: - text = sub(repl, repl_with, to_fix, count=1, - flags=I).strip() + text = sub(repl, repl_with, to_fix, count=1, flags=I).strip() elif 'g' in flags: text = sub(repl, repl_with, to_fix).strip() else: diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index 2751185..de952f9 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -10,8 +10,7 @@ from collections import OrderedDict from sedenbot import HELP -from sedenecem.core import (edit, reply, extract_args, - sedenify, get_translation) +from sedenecem.core import edit, extract_args, get_translation, reply, sedenify @sedenify(pattern='^.seden') diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 28f43cd..18d1cd9 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -7,15 +7,24 @@ # All rights reserved. See COPYING, AUTHORS. # -from sedenbot import HELP, LOGS, LOG_ID -from sedenecem.core import (extract_args, sedenify, edit, get_messages, - reply_msg, forward, send_log, get_translation) +from sedenbot import HELP, LOG_ID, LOGS +from sedenecem.core import ( + edit, + extract_args, + forward, + get_messages, + get_translation, + reply_msg, + sedenify, + send_log, +) def snips_init(): try: global sql from importlib import import_module + sql = import_module('sedenecem.sql.snips_sql') except Exception as e: sql = None @@ -50,12 +59,10 @@ def save_snip(message): string = None msg_o = forward(msg, LOG_ID) if not msg_o: - edit( - message, f'`{get_translation("snipError")}`') + edit(message, f'`{get_translation("snipError")}`') return msg_id = msg_o.message_id - send_log(get_translation( - 'snipsLog', ['`', message.chat.id, keyword])) + send_log(get_translation('snipsLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index 52f184d..d6f30e4 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -10,9 +10,18 @@ from threading import Event from sedenbot import HELP -from sedenecem.core import (edit, reply, reply_img, send_log, extract_args, - extract_args_arr, sedenify, get_translation, - spam_allowed, increment_spam_count) +from sedenecem.core import ( + edit, + extract_args, + extract_args_arr, + get_translation, + increment_spam_count, + reply, + reply_img, + sedenify, + send_log, + spam_allowed, +) @sedenify(pattern='^.tspam') diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index 9f339dd..c12379f 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -10,8 +10,7 @@ from datetime import datetime from sedenbot import HELP -from sedenecem.core import (extract_args, sedenify, edit, - reply_doc, get_translation) +from sedenecem.core import edit, extract_args, get_translation, reply_doc, sedenify from speedtest import Speedtest @@ -41,44 +40,52 @@ def speed_test(message): response = spdtst.results.share() speedtest_image = response if as_text: - edit(message, - get_translation('speedtestResultText', - ['**', - ms, - convert_from_bytes(download_speed), - convert_from_bytes(upload_speed), - ping_time, - i_s_p, - i_s_p_rating, - ''])) + edit( + message, + get_translation( + 'speedtestResultText', + [ + '**', + ms, + convert_from_bytes(download_speed), + convert_from_bytes(upload_speed), + ping_time, + i_s_p, + i_s_p_rating, + '', + ], + ), + ) else: reply_doc( - message, speedtest_image, caption=get_translation( - 'speedtestResultDoc', [ - '**', ms]), delete_orig=True) + message, + speedtest_image, + caption=get_translation('speedtestResultDoc', ['**', ms]), + delete_orig=True, + ) except Exception as exc: - edit(message, - get_translation('speedtestResultText', - ['**', - ms, - convert_from_bytes(download_speed), - convert_from_bytes(upload_speed), - ping_time, - i_s_p, - i_s_p_rating, - f'ERROR: {str(exc)}'])) + edit( + message, + get_translation( + 'speedtestResultText', + [ + '**', + ms, + convert_from_bytes(download_speed), + convert_from_bytes(upload_speed), + ping_time, + i_s_p, + i_s_p_rating, + f'ERROR: {str(exc)}', + ], + ), + ) def convert_from_bytes(size): - power = 2**10 + power = 2 ** 10 _ = 0 - units = { - 0: '', - 1: 'kilobytes', - 2: 'megabytes', - 3: 'gigabytes', - 4: 'terabytes' - } + units = {0: '', 1: 'kilobytes', 2: 'megabytes', 3: 'gigabytes', 4: 'terabytes'} while size > power: size /= power _ += 1 diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index c8a1a37..08fdd0d 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -8,34 +8,33 @@ # from random import choice -from requests import get from pyrogram.raw.functions.messages import GetStickerSet from pyrogram.raw.types import InputStickerSetShortName - +from requests import get from sedenbot import HELP, PACKNAME, PACKNICK, TEMP_SETTINGS from sedenecem.core import ( - sedenify, - edit, + PyroConversation, download_media_wc, - reply_doc, - get_translation, + edit, extract_args, - PyroConversation, - sticker_resize as resizer, + get_translation, + reply_doc, + sedenify, ) +from sedenecem.core import sticker_resize as resizer # ================= CONSTANT ================= -DIZCILIK = [get_translation(f"kangstr{i+1}") for i in range(0, 12)] +DIZCILIK = [get_translation(f'kangstr{i+1}') for i in range(0, 12)] # ================= CONSTANT ================= -@sedenify(pattern="^.(d[ıi]zla|kang)", compat=False) +@sedenify(pattern='^.(d[ıi]zla|kang)', compat=False) def kang(client, message): - myacc = TEMP_SETTINGS["ME"] + myacc = TEMP_SETTINGS['ME'] kanger = myacc.username or myacc.first_name if myacc.username: - kanger = f"@{kanger}" + kanger = f'@{kanger}' args = extract_args(message) reply = message.reply_to_message @@ -47,7 +46,7 @@ def kang(client, message): media = None if reply.photo or reply.document or reply.sticker: - edit(message, f"`{choice(DIZCILIK)}`") + edit(message, f'`{choice(DIZCILIK)}`') anim = reply.sticker and reply.sticker.is_animated media = download_media_wc(reply, sticker_orig=anim) else: @@ -55,27 +54,27 @@ def kang(client, message): return if len(args) < 1: - args = "1" + args = '1' - emoji = "🤤" + emoji = '🤤' pack = 1 for item in args.split(): if item.isdigit(): pack = int(item) - args = args.replace(item, "").strip() - elif "e=" in item: - emoji = item.replace("e=", "") - args = args.replace(item, "").strip() + args = args.replace(item, '').strip() + elif 'e=' in item.lower(): + emoji = item.replace('e=', '') + args = args.replace(item, '').strip() pname = ( - PACKNAME.replace(" ", "") + PACKNAME.replace(' ', '') if PACKNAME - else f"a{myacc.id}_by_{myacc.username}_{pack}" + else f'a{myacc.id}_by_{myacc.username}_{pack}' ) pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" - limit = "50" if anim else "120" + limit = '50' if anim else '120' def pack_created(name): try: @@ -94,23 +93,23 @@ def create_new(conv, pname, pnick): except Exception as e: raise e msg = send_recv(conv, pnick) - if msg.text == "Invalid pack selected.": + if msg.text == 'Invalid pack selected.': pack += 1 return create_new(conv) msg = send_recv(conv, media, doc=True) - if "Sorry, the file type is invalid." in msg.text: + if 'Sorry, the file type is invalid.' in msg.text: edit(message, f'`{get_translation("stickerError")}`') return send_recv(conv, emoji) - send_recv(conv, "/publish") + send_recv(conv, '/publish') if anim: - send_recv(conv, f"<{pnick}>") - send_recv(conv, "/skip") + send_recv(conv, f'<{pnick}>') + send_recv(conv, '/skip') send_recv(conv, pname) def add_exist(conv, pack, pname, pnick): try: - send_recv(conv, "/addsticker") + send_recv(conv, '/addsticker') except Exception as e: raise e @@ -119,12 +118,12 @@ def add_exist(conv, pack, pname, pnick): if limit in status.text: pack += 1 pname = ( - PACKNAME.replace(" ", "") + PACKNAME.replace(' ', '') if PACKNAME - else f"a{myacc.id}_by_{myacc.username}_{pack}" + else f'a{myacc.id}_by_{myacc.username}_{pack}' ) pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" - edit(message, get_translation("packFull", ["`", "**", str(pack)])) + edit(message, get_translation('packFull', ['`', '**', str(pack)])) if pack_created(pname): return add_exist(conv, pack, pname, pnick) else: @@ -132,18 +131,18 @@ def add_exist(conv, pack, pname, pnick): send_recv(conv, media, doc=True) send_recv(conv, emoji) - send_recv(conv, "/done") + send_recv(conv, '/done') return True if anim: - pname += "_anim" - pnick += " (Animated)" + pname += '_anim' + pnick += ' (Animated)' else: if not reply.sticker: media = resizer(media) - with PyroConversation(client, "Stickers") as conv: - send_recv(conv, "/cancel") + with PyroConversation(client, 'Stickers') as conv: + send_recv(conv, '/cancel') if pack_created(pname): ret = add_exist(conv, pack, pname, pnick) if not ret: @@ -151,7 +150,7 @@ def add_exist(conv, pack, pname, pnick): else: create_new(conv, pname, pnick) - edit(message, get_translation("stickerAdded", ["`", pname])) + edit(message, get_translation('stickerAdded', ['`', pname])) def send_recv(conv, msg, doc=False): @@ -162,7 +161,7 @@ def send_recv(conv, msg, doc=False): return conv.recv_msg() -@sedenify(pattern="^.getsticker$") +@sedenify(pattern='^.getsticker$') def getsticker(message): reply = message.reply_to_message if not reply or not reply.sticker: @@ -174,14 +173,14 @@ def getsticker(message): reply_doc( message, photo, - caption=f"**Sticker ID:** `{reply.sticker.file_id}" - f"`\n**Emoji**: `{reply.sticker.emoji}`", + caption=f'**Sticker ID:** `{reply.sticker.file_id}' + f'`\n**Emoji**: `{reply.sticker.emoji}`', delete_after_send=True, delete_orig=True, ) -@sedenify(pattern=".packinfo$", compat=False) +@sedenify(pattern='.packinfo$', compat=False) def packinfo(client, message): reply = message.reply_to_message if not reply: @@ -205,21 +204,21 @@ def packinfo(client, message): pack_emojis.append(document_sticker.emoticon) out = get_translation( - "packinfoResult", + 'packinfoResult', [ - "**", - "`", + '**', + '`', get_stickerset.set.title, get_stickerset.set.short_name, get_stickerset.set.official, get_stickerset.set.archived, get_stickerset.set.animated, get_stickerset.set.count, - " ".join(pack_emojis), + ' '.join(pack_emojis), ], ) edit(message, out) -HELP.update({"stickers": get_translation("stickerInfo")}) +HELP.update({'stickers': get_translation('stickerInfo')}) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index ac81476..a99a67d 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -7,20 +7,25 @@ # All rights reserved. See COPYING, AUTHORS. # +from ast import Add, BinOp, BitXor, Div, Mult, Num, Pow, Sub, UnaryOp, USub, parse from datetime import datetime -from shutil import which from getpass import getuser -from operator import add, sub, mul, truediv, pow, xor, neg -from ast import (Add, Sub, Mult, Div, Pow, BitXor, USub, - parse, Num, BinOp, UnaryOp) +from operator import add, mul, neg, pow, sub, truediv, xor +from shutil import which from pyrogram.raw.functions.help import GetNearestDc - +from sedenbot import ALIVE_MSG, BOT_VERSION, CHANNEL, HELP, HOSTNAME, USER from sedenbot.modules.ecem import ecem -from sedenbot import (HELP, ALIVE_MSG, CHANNEL, - BOT_VERSION, HOSTNAME, USER) -from sedenecem.core import (edit, reply, reply_doc, send_log, - extract_args, sedenify, get_translation) +from sedenecem.core import ( + edit, + extract_args, + get_translation, + reply, + reply_doc, + sedenify, + send_log, +) + # ================= CONSTANT ================= KULLANICIMESAJI = ALIVE_MSG or f"`{get_translation('sedenAlive')}`" # ============================================ @@ -30,9 +35,12 @@ def neofetch(message): try: from subprocess import PIPE, Popen + islem = Popen( ['neofetch', f'HOSTNAME={HOSTNAME}', f'USER={USER}', '--stdout'], - stdout=PIPE, stderr=PIPE) + stdout=PIPE, + stderr=PIPE, + ) sonuc, _ = islem.communicate() edit(message, f'`{sonuc.decode()}`') except BaseException: @@ -43,19 +51,23 @@ def neofetch(message): def botver(message): if which('git'): from subprocess import PIPE, Popen - degisiklik = Popen(['git', 'rev-list', '--all', '--count'], - stdout=PIPE, stderr=PIPE, universal_newlines=True) + + degisiklik = Popen( + ['git', 'rev-list', '--all', '--count'], + stdout=PIPE, + stderr=PIPE, + universal_newlines=True, + ) sonuc, _ = degisiklik.communicate() - edit(message, - get_translation('sedenShowBotVersion', - ['**', - '`', - 'Seden UserBot', - CHANNEL, - BOT_VERSION, - sonuc]), - preview=False) + edit( + message, + get_translation( + 'sedenShowBotVersion', + ['**', '`', 'Seden UserBot', CHANNEL, BOT_VERSION, sonuc], + ), + preview=False, + ) else: edit(message, f'`{get_translation("sedenGitNotFound")}`') @@ -67,8 +79,10 @@ def pip3(message): edit(message, f'`{get_translation("pipSearch")}`') pipsorgu = f"pip3 search {pipmodule}" from subprocess import PIPE, Popen - islem = Popen(pipsorgu.split(), stdout=PIPE, - stderr=PIPE, universal_newlines=True) + + islem = Popen( + pipsorgu.split(), stdout=PIPE, stderr=PIPE, universal_newlines=True + ) sonuc, _ = islem.communicate() if sonuc: @@ -79,11 +93,15 @@ def pip3(message): file.close() reply_doc(message, 'pip3.txt', delete_after_send=True) return - edit(message, get_translation( - 'sedenQuery', ['**', '`', pipsorgu, sonuc])) + edit(message, get_translation('sedenQuery', ['**', '`', pipsorgu, sonuc])) else: - edit(message, get_translation('sedenQuery', [ - '**', '`', pipsorgu, get_translation('sedenZeroResults')])) + edit( + message, + get_translation( + 'sedenQuery', + ['**', '`', pipsorgu, get_translation('sedenZeroResults')], + ), + ) else: edit(message, f'`{get_translation("pipHelp")}`') @@ -119,8 +137,13 @@ def echo(message): def dc(client, message): sonuc = client.send(GetNearestDc()) - edit(message, get_translation('sedenNearestDC', [ - '**', '`', sonuc.country, sonuc.nearest_dc, sonuc.this_dc])) + edit( + message, + get_translation( + 'sedenNearestDC', + ['**', '`', sonuc.country, sonuc.nearest_dc, sonuc.this_dc], + ), + ) @sedenify(pattern='^.term') @@ -134,6 +157,7 @@ def terminal(message): curruser = getuser() try: from os import geteuid + uid = geteuid() except ImportError: uid = 0 @@ -145,6 +169,7 @@ def terminal(message): sonuc = f'`{get_translation("termNoResult")}`' try: from subprocess import getoutput + sonuc = getoutput(command) except BaseException: pass @@ -153,14 +178,15 @@ def terminal(message): output = open('output.txt', 'w+') output.write(sonuc) output.close() - reply_doc(message, 'output.txt', - caption=f'`{get_translation("outputTooLarge")}`', - delete_after_send=True) + reply_doc( + message, + 'output.txt', + caption=f'`{get_translation("outputTooLarge")}`', + delete_after_send=True, + ) return - edit( - message, - f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{sonuc}`') + edit(message, f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{sonuc}`') send_log(get_translation('termLog', [command])) @@ -180,26 +206,39 @@ def eval(message): file = open('output.txt', 'w+') file.write(evaluation) file.close() - reply_doc(message, - 'output.txt', - caption=f'`{get_translation("outputTooLarge")}`', - delete_after_send=True) + reply_doc( + message, + 'output.txt', + caption=f'`{get_translation("outputTooLarge")}`', + delete_after_send=True, + ) return - edit(message, get_translation( - 'sedenQuery', ['**', '`', args, evaluation])) + edit( + message, + get_translation('sedenQuery', ['**', '`', args, evaluation]), + ) else: - edit(message, get_translation('sedenQuery', [ - '**', '`', args, get_translation('sedenErrorResult')])) + edit( + message, + get_translation( + 'sedenQuery', ['**', '`', args, get_translation('sedenErrorResult')] + ), + ) except Exception as err: - edit(message, get_translation( - 'sedenQuery', ['**', '`', args, str(err)])) + edit(message, get_translation('sedenQuery', ['**', '`', args, str(err)])) send_log(get_translation('evalLog', [args])) -operators = {Add: add, Sub: sub, Mult: mul, - Div: truediv, Pow: pow, BitXor: xor, - USub: neg} +operators = { + Add: add, + Sub: sub, + Mult: mul, + Div: truediv, + Pow: pow, + BitXor: xor, + USub: neg, +} def safe_eval(expr): diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index c968472..411ea4b 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -8,45 +8,45 @@ # from os import execl, path -from sys import executable, argv -from heroku3 import from_key +from sys import argv, executable -from sedenbot import HELP, HEROKU_KEY, HEROKU_APPNAME, REPO_URL +from heroku3 import from_key +from sedenbot import HELP, HEROKU_APPNAME, HEROKU_KEY, REPO_URL from sedenecem.core import ( - extract_args, - sedenify, edit, + extract_args, + get_translation, reply, reply_doc, - get_translation, + sedenify, ) from git import Repo # type: ignore from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError requirements_path = path.join( - path.dirname(path.dirname(path.dirname(__file__))), "requirements.txt" + path.dirname(path.dirname(path.dirname(__file__))), 'requirements.txt' ) def gen_chlog(repo, diff): - ch_log = "" - d_form = "%d/%m/%y" + ch_log = '' + d_form = '%d/%m/%y' for c in repo.iter_commits(diff): - ch_log += f"%1•%1 %2[{c.committed_datetime.strftime(d_form)}]: {c.summary} <{c.author}>%2\n" + ch_log += f'%1•%1 %2[{c.committed_datetime.strftime(d_form)}]: {c.summary} <{c.author}>%2\n' return ch_log def update_requirements(): reqs = str(requirements_path) try: - _, ret = execute_command(f"{executable} -m pip install -r {reqs}") + _, ret = execute_command(f'{executable} -m pip install -r {reqs}') return ret except Exception as e: return repr(e) -@sedenify(pattern="^.update") +@sedenify(pattern='^.update') def upstream(ups): edit(ups, f'`{get_translation("updateCheck")}`') conf = extract_args(ups) @@ -59,58 +59,58 @@ def upstream(ups): txt += f'**{get_translation("updateLog")}**\n' repo = Repo() except NoSuchPathError as error: - edit(ups, get_translation("updateFolderError", [txt, "`", error])) + edit(ups, get_translation('updateFolderError', [txt, '`', error])) repo.__del__() return except GitCommandError as error: - edit(ups, get_translation("updateFolderError", [txt, "`", error])) + edit(ups, get_translation('updateFolderError', [txt, '`', error])) repo.__del__() return except InvalidGitRepositoryError as error: - if conf != "now": - edit(ups, get_translation("updateGitNotFound", [error])) + if conf != 'now': + edit(ups, get_translation('updateGitNotFound', [error])) return repo = Repo.init() - origin = repo.create_remote("upstream", off_repo) + origin = repo.create_remote('upstream', off_repo) origin.fetch() force_update = True - repo.create_head("seden", origin.refs.seden) + repo.create_head('seden', origin.refs.seden) repo.heads.seden.set_tracking_branch(origin.refs.seden) repo.heads.seden.checkout(True) ac_br = repo.active_branch.name - if ac_br != "seden": - edit(ups, get_translation("updateFolderError", ["**", ac_br])) + if ac_br != 'seden': + edit(ups, get_translation('updateFolderError', ['**', ac_br])) repo.__del__() return try: - repo.create_remote("upstream", off_repo) + repo.create_remote('upstream', off_repo) except BaseException: pass - ups_rem = repo.remote("upstream") + ups_rem = repo.remote('upstream') ups_rem.fetch(ac_br) - changelog = gen_chlog(repo, f"HEAD..upstream/{ac_br}") + changelog = gen_chlog(repo, f'HEAD..upstream/{ac_br}') if not changelog and not force_update: - edit(ups, get_translation("updaterUsingLatest", ["**", "`", ac_br])) + edit(ups, get_translation('updaterUsingLatest', ['**', '`', ac_br])) repo.__del__() return - if conf != "now" and not force_update: + if conf != 'now' and not force_update: if len(changelog) > 4096: edit(ups, f'`{get_translation("updateOutput")}`') - file = open("changelog.txt", "w+") + file = open('changelog.txt', 'w+') file.write(changelog) file.close() - reply_doc(ups, ups.chat.id, "changelog.txt", delete_after_send=True) + reply_doc(ups, ups.chat.id, 'changelog.txt', delete_after_send=True) else: edit( - ups, get_translation("updaterHasUpdate", ["**", "`", ac_br, changelog]) + ups, get_translation('updaterHasUpdate', ['**', '`', ac_br, changelog]) ) - reply(ups, get_translation("updateNow", ["**", "`"])) + reply(ups, get_translation('updateNow', ['**', '`'])) return if force_update: @@ -136,31 +136,31 @@ def upstream(ups): return edit(ups, f'`{get_translation("updateBotUpdating")}`') ups_rem.fetch(ac_br) - repo.git.reset("--hard", "FETCH_HEAD") + repo.git.reset('--hard', 'FETCH_HEAD') heroku_git_url = heroku_app.git_url.replace( - "https://", f"https://api:{HEROKU_KEY}@" + 'https://', f'https://api:{HEROKU_KEY}@' ) - if "heroku" in repo.remotes: - remote = repo.remote("heroku") + if 'heroku' in repo.remotes: + remote = repo.remote('heroku') remote.set_url(heroku_git_url) else: - remote = repo.create_remote("heroku", heroku_git_url) + remote = repo.create_remote('heroku', heroku_git_url) try: - remote.push(refspec="HEAD:refs/heads/master", force=True) + remote.push(refspec='HEAD:refs/heads/master', force=True) except GitCommandError as error: - edit(ups, get_translation("updaterGitError", ["`", txt, error])) + edit(ups, get_translation('updaterGitError', ['`', txt, error])) repo.__del__() return edit(ups, f'`{get_translation("updateComplete")}`') try: - heroku_app.scale_formation_process("seden", 1) + heroku_app.scale_formation_process('seden', 1) except BaseException: pass else: try: ups_rem.pull(ac_br) except GitCommandError: - repo.git.reset("--hard", "FETCH_HEAD") + repo.git.reset('--hard', 'FETCH_HEAD') update_requirements() edit(ups, f'`{get_translation("updateLocalComplate")}`') @@ -184,4 +184,4 @@ def execute_command(command): return sonuc, islem.returncode -HELP.update({"update": get_translation("updateInfo")}) +HELP.update({'update': get_translation('updateInfo')}) diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index 6b96e1e..ff136f2 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -7,16 +7,17 @@ # All rights reserved. See COPYING, AUTHORS. # -from time import time from os.path import isfile +from time import time + from sedenbot import HELP, TEMP_SETTINGS from sedenecem.core import ( download_media_wc, - sedenify, edit, extract_args, - reply_doc, get_translation, + reply_doc, + sedenify, ) @@ -34,10 +35,11 @@ def progress(current, total): if (curr_posix := time()) - TEMP_SETTINGS[f'upload_{posix}'] > 5: TEMP_SETTINGS[f'upload_{posix}'] = curr_posix edit( - message, get_translation( - 'updownDownload', [ - '`', '(½{:.2f})'.format( - current * 100 / total)]), ) + message, + get_translation( + 'updownDownload', ['`', '(½{:.2f})'.format(current * 100 / total)] + ), + ) edit(message, f'`{get_translation("downloadMedia")}`') media = download_media_wc(reply, progress=progress) diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index cdf3131..d48ec3e 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -8,9 +8,8 @@ # from requests import get - -from sedenbot import HELP, WEATHER, SEDEN_LANG -from sedenecem.core import edit, extract_args, sedenify, get_translation +from sedenbot import HELP, SEDEN_LANG, WEATHER +from sedenecem.core import edit, extract_args, get_translation, sedenify # ===== CONSTANT ===== if WEATHER: @@ -33,14 +32,13 @@ def havadurumu(message): CITY = args if ',' in CITY: - CITY = CITY[:CITY.find(',')].strip() + CITY = CITY[: CITY.find(',')].strip() try: req = get( f'http://wttr.in/{CITY}?mqT0', - headers={ - 'User-Agent': 'curl/7.66.0', - 'Accept-Language': SEDEN_LANG}) + headers={'User-Agent': 'curl/7.66.0', 'Accept-Language': SEDEN_LANG}, + ) data = req.text if '===' in data: raise Exception diff --git a/sedenbot/modules/whois.py b/sedenbot/modules/whois.py index e5be989..a88e460 100644 --- a/sedenbot/modules/whois.py +++ b/sedenbot/modules/whois.py @@ -7,9 +7,15 @@ # All rights reserved. See COPYING, AUTHORS. # -from sedenbot import HELP, BLACKLIST, BRAIN -from sedenecem.core import (extract_args, sedenify, edit, reply_img, - get_translation, download_media_wc) +from sedenbot import BLACKLIST, BRAIN, HELP +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply_img, + sedenify, +) @sedenify(pattern='^.whois', compat=False) @@ -45,8 +51,11 @@ def who_is(client, message): first_name = reply_user.first_name or get_translation('notSet') last_name = reply_user.last_name or get_translation('notSet') - username = f'@{reply_user.username}' if reply_user.username else get_translation( - 'notSet') + username = ( + f'@{reply_user.username}' + if reply_user.username + else get_translation('notSet') + ) user_id = reply_user.id photos = client.get_profile_photos_count(user_id) dc_id = reply_user.dc_id @@ -62,17 +71,30 @@ def who_is(client, message): caption = get_translation( 'whoisResult', - ['**', '`', first_name, last_name, username, user_id, photos, - dc_id, bot, scam, verified, chats, bio, last_seen, sudo - if sudo else '', blacklist if blacklist else '']) + [ + '**', + '`', + first_name, + last_name, + username, + user_id, + photos, + dc_id, + bot, + scam, + verified, + chats, + bio, + last_seen, + sudo if sudo else '', + blacklist if blacklist else '', + ], + ) if photo and media_perm: reply_img( - message, - photo, - caption=caption, - delete_file=True, - delete_orig=True) + message, photo, caption=caption, delete_file=True, delete_orig=True + ) else: return edit(message, caption) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index ceb2fe0..6933063 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -10,18 +10,17 @@ from os import remove -from youtube_dl import YoutubeDL -from youtube_dl.utils import DownloadError - from sedenbot import HELP from sedenecem.core import ( edit, extract_args, - sedenify, get_translation, + reply_audio, reply_doc, - get_translation, - reply_audio) + sedenify, +) +from youtube_dl import YoutubeDL +from youtube_dl.utils import DownloadError @sedenify(pattern='^.(youtube|yt)dl') @@ -47,7 +46,8 @@ def youtubedl(message): edit(message, get_translation('downloadYTVideo', ['**', title, '`'])) ydl_opts = { 'outtmpl': f'{title}.%(ext)s', - 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'} + 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', + } with YoutubeDL(ydl_opts) as ydl: ydl.download([url]) edit(message, f'{get_translation("uploadMedia")}') @@ -56,7 +56,8 @@ def youtubedl(message): f'{title}.mp4', caption=f"{get_translation('title', ['**' , ':'])} {title}`\n`{get_translation('uploader',['**',':'])} {uploader}", delete_after_send=True, - delete_orig=True) + delete_orig=True, + ) elif util == 'mp3': edit(message, get_translation('downloadYTAudio', ['**', title, '`'])) @@ -68,7 +69,9 @@ def youtubedl(message): 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '320', - }]} + } + ], + } with YoutubeDL(ydl_opts) as ydl: ydl.download([url]) edit(message, f'`{get_translation("uploadMedia")}`') @@ -76,7 +79,8 @@ def youtubedl(message): message, f'{title}.mp3', caption=f"{get_translation('uploader',['**',':'])} {uploader}", - delete_orig=True) + delete_orig=True, + ) remove(f'{title}.mp3') diff --git a/sedenecem/core/__init__.py b/sedenecem/core/__init__.py index 343f64f..6a81194 100644 --- a/sedenecem/core/__init__.py +++ b/sedenecem/core/__init__.py @@ -7,11 +7,11 @@ # All rights reserved. See COPYING, AUTHORS. # +from .conv import * +from .image import * from .misc import * +from .replier import * from .sedenify import * from .sedenlog import * from .send import * -from .replier import * from .webdriver import * -from .image import * -from .conv import * diff --git a/sedenecem/core/conv.py b/sedenecem/core/conv.py index 11da7ce..06d51df 100644 --- a/sedenecem/core/conv.py +++ b/sedenecem/core/conv.py @@ -7,13 +7,13 @@ # All rights reserved. See COPYING, AUTHORS. # -from time import sleep from os import remove +from time import sleep + from sedenbot import CONVERSATION, app class PyroConversation: - def __init__(self, client, chat_id): self.client = client or app self.chat_id = chat_id diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index 7d9bd42..f4cf32e 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -8,6 +8,7 @@ # from math import floor + from PIL import Image diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index d3d2790..dd7dbba 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -10,15 +10,15 @@ from re import escape, sub from pyrogram.types import Message -from sedenbot import TEMP_SETTINGS, app, BRAIN, BOT_PREFIX +from sedenbot import BOT_PREFIX, BRAIN, TEMP_SETTINGS, app -MARKDOWN_FIX_CHAR = "\u2064" +MARKDOWN_FIX_CHAR = '\u2064' SPAM_COUNT = [0] -_parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r"\." +_parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r'\.' def reply( - message, text, preview=True, fix_markdown=False, delete_orig=False, parse="md" + message, text, preview=True, fix_markdown=False, delete_orig=False, parse='md' ): try: if fix_markdown: @@ -40,11 +40,11 @@ def extract_args(message, markdown=True): text = message.text or message.caption text = text.markdown if markdown else text - if " " not in text: - return "" + if ' ' not in text: + return '' - text = sub(r"\s+", " ", text) - text = text[text.find(" ") :].strip() + text = sub(r'\s+', ' ', text) + text = text[text.find(' ') :].strip() return text @@ -52,11 +52,11 @@ def extract_args_arr(message, markdown=True): return extract_args(message, markdown).split() -def edit(message, text, preview=True, fix_markdown=False, parse="md"): +def edit(message, text, preview=True, fix_markdown=False, parse='md'): try: if fix_markdown: text += MARKDOWN_FIX_CHAR - if message.from_user.id != TEMP_SETTINGS["ME"].id: + if message.from_user.id != TEMP_SETTINGS['ME'].id: reply(message, text, preview=preview, parse=parse) return message.edit_text( @@ -72,28 +72,28 @@ def download_media(client, data, file_name=None, progress=None, sticker_orig=Tru file_name = ( data.document.file_name if data.document.file_name - else f"{data.document.file_id}.bin" + else f'{data.document.file_id}.bin' ) elif data.audio: file_name = ( data.audio.file_name if data.audio.file_name - else f"{data.audio.file_id}.mp3" + else f'{data.audio.file_id}.mp3' ) elif data.photo: - file_name = f"{data.photo.file_id}.png" + file_name = f'{data.photo.file_id}.png' elif data.voice: - file_name = f"{data.voice.file_id}.ogg" + file_name = f'{data.voice.file_id}.ogg' elif data.video: file_name = ( data.video.file_name if data.video.file_name - else f"{data.video.file_id}.mp4" + else f'{data.video.file_id}.mp4' ) elif data.animation: - file_name = f"{data.animation.file_id}.mp4" + file_name = f'{data.animation.file_id}.mp4' elif data.video_note: - file_name = f"{data.video_note.file_id}.mp4" + file_name = f'{data.video_note.file_id}.mp4' elif data.sticker: file_name = f'sticker.{("tgs" if sticker_orig else "TGS") if data.sticker.is_animated else ("webp" if sticker_orig else "png")}' else: @@ -115,21 +115,21 @@ def get_me(): def forward(message, chat_id): try: - return message.forward(chat_id or "me") + return message.forward(chat_id or 'me') except Exception as e: raise e def get_messages(chat_id, msg_ids=None, client=app): try: - ret = client.get_messages(chat_id=(chat_id or "me"), message_ids=msg_ids) + ret = client.get_messages(chat_id=(chat_id or 'me'), message_ids=msg_ids) return [ret] if ret and isinstance(ret, Message) else ret except BaseException: return [] def amisudo(): - return TEMP_SETTINGS["ME"].id in BRAIN + return TEMP_SETTINGS['ME'].id in BRAIN def increment_spam_count(): @@ -146,11 +146,11 @@ def get_cmd(message): if text: text = text.strip() return parse_cmd(text) - return "" + return '' def parse_cmd(text): - cmd = sub(r"\s+", " ", text) + cmd = sub(r'\s+', ' ', text) cmd = cmd.split()[0] cmd = cmd.split(_parsed_prefix)[-1] if BOT_PREFIX else cmd[1:] return cmd diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 7b04676..06037a4 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -8,17 +8,19 @@ # from os import remove + from .misc import MARKDOWN_FIX_CHAR, download_media_wc def reply_img( - message, - photo, - caption='', - fix_markdown=False, - delete_orig=False, - delete_file=False, - parse='md'): + message, + photo, + caption='', + fix_markdown=False, + delete_orig=False, + delete_file=False, + parse='md', +): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -32,12 +34,7 @@ def reply_img( pass -def reply_audio( - message, - audio, - caption='', - fix_markdown=False, - delete_orig=False): +def reply_audio(message, audio, caption='', fix_markdown=False, delete_orig=False): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -48,12 +45,7 @@ def reply_audio( pass -def reply_voice( - message, - voice, - caption='', - fix_markdown=False, - delete_orig=False): +def reply_voice(message, voice, caption='', fix_markdown=False, delete_orig=False): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -65,19 +57,19 @@ def reply_voice( def reply_doc( - message, - doc, - caption='', - fix_markdown=False, - delete_orig=False, - progress=None, - delete_after_send=False): + message, + doc, + caption='', + fix_markdown=False, + delete_orig=False, + progress=None, + delete_after_send=False, +): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR if isinstance(doc, str): - message.reply_document( - doc, caption=caption.strip(), progress=progress) + message.reply_document(doc, caption=caption.strip(), progress=progress) if delete_after_send: remove(doc) else: diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 62d16ae..aef9d5f 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -8,45 +8,44 @@ # from os import getpid, system -from subprocess import Popen, PIPE +from subprocess import PIPE, Popen from sys import exc_info from time import gmtime, strftime from traceback import format_exc -from pyrogram import ContinuePropagation, StopPropagation +from pyrogram import ContinuePropagation, StopPropagation, filters from pyrogram.handlers import MessageHandler -from pyrogram import filters - from sedenbot import ( - SUPPORT_GROUP, BLACKLIST, BOT_VERSION, BRAIN, + SUPPORT_GROUP, TEMP_SETTINGS, app, get_translation, ) -from .sedenlog import send_log_doc -from .misc import edit, _parsed_prefix, get_cmd from sedenbot.modules.admin import is_admin +from .misc import _parsed_prefix, edit, get_cmd +from .sedenlog import send_log_doc + def sedenify(**args): - pattern = args.get("pattern", None) - outgoing = args.get("outgoing", True) - incoming = args.get("incoming", False) - disable_edited = args.get("disable_edited", False) - disable_notify = args.get("disable_notify", False) - compat = args.get("compat", True) - brain = args.get("brain", False) - private = args.get("private", True) - group = args.get("group", True) - bot = args.get("bot", True) - service = args.get("service", False) - admin = args.get("admin", False) - - if pattern and "." in pattern[:2]: - args["pattern"] = pattern = pattern.replace(".", _parsed_prefix, 1) + pattern = args.get('pattern', None) + outgoing = args.get('outgoing', True) + incoming = args.get('incoming', False) + disable_edited = args.get('disable_edited', False) + disable_notify = args.get('disable_notify', False) + compat = args.get('compat', True) + brain = args.get('brain', False) + private = args.get('private', True) + group = args.get('group', True) + bot = args.get('bot', True) + service = args.get('service', False) + admin = args.get('admin', False) + + if pattern and '.' in pattern[:2]: + args['pattern'] = pattern = pattern.replace('.', _parsed_prefix, 1) def msg_decorator(func): def wrap(client, message): @@ -54,28 +53,28 @@ def wrap(client, message): return try: - if not TEMP_SETTINGS.get("ME", None): + if not TEMP_SETTINGS.get('ME', None): me = app.get_me() - TEMP_SETTINGS["ME"] = me + TEMP_SETTINGS['ME'] = me if me.id in BLACKLIST: - raise RetardsException("RETARDS CANNOT USE THIS BOT") + raise RetardsException('RETARDS CANNOT USE THIS BOT') if message.service and not service: return - if message.chat.type == "channel": + if message.chat.type == 'channel': return - if not bot and message.chat.type == "bot": + if not bot and message.chat.type == 'bot': message.continue_propagation() - if not private and message.chat.type in ["private", "bot"]: + if not private and message.chat.type in ['private', 'bot']: if not disable_notify: edit(message, f'`{get_translation("groupUsage")}`') message.continue_propagation() - if not group and "group" in message.chat.type: + if not group and 'group' in message.chat.type: if not disable_notify: edit(message, f'`{get_translation("privateUsage")}`') message.continue_propagation() @@ -91,7 +90,7 @@ def wrap(client, message): func(message) except RetardsException: try: - system(f"kill -9 {getpid()}") + system(f'kill -9 {getpid()}') except BaseException: pass except (ContinuePropagation, StopPropagation) as c: @@ -100,21 +99,21 @@ def wrap(client, message): try: date = strftime("%Y-%m-%d %H:%M:%S", gmtime()) - if get_cmd(message) == "crash": - text = get_translation("logidTest") + if get_cmd(message) == 'crash': + text = get_translation('logidTest') else: if not disable_notify: edit(message, f'`{get_translation("errorLogSend")}`') text = get_translation( - "sedenErrorText", ["**", "`", exc_info()[1]] + 'sedenErrorText', ['**', '`', exc_info()[1]] ) ftext = get_translation( - "sedenErrorText2", + 'sedenErrorText2', [ date, message.chat.id, - message.from_user.id if message.from_user else "Unknown", + message.from_user.id if message.from_user else 'Unknown', BOT_VERSION, message.text, format_exc(), @@ -123,21 +122,21 @@ def wrap(client, message): ) process = Popen( - ["git", "log", '--pretty=format:"%an: %s"', "-10"], + ['git', 'log', '--pretty=format:"%an: %s"', '-10'], stdout=PIPE, stderr=PIPE, ) out, err = process.communicate() - out = f"{out.decode()}\n{err.decode()}".strip() + out = f'{out.decode()}\n{err.decode()}'.strip() ftext += out - file = open(get_translation("rbgLog"), "w+") + file = open(get_translation('rbgLog'), 'w+') file.write(ftext) file.close() send_log_doc( - get_translation("rbgLog"), caption=text, remove_file=True + get_translation('rbgLog'), caption=text, remove_file=True ) raise e except Exception as x: diff --git a/sedenecem/core/sedenlog.py b/sedenecem/core/sedenlog.py index fe9f7f9..7ace488 100644 --- a/sedenecem/core/sedenlog.py +++ b/sedenecem/core/sedenlog.py @@ -8,17 +8,17 @@ # from os import remove -from sedenbot import app, LOG_ID + +from sedenbot import LOG_ID, app + from .send import send, send_doc def send_log(text, fix_markdown=False): - send(app, LOG_ID or 'me', - text, fix_markdown=fix_markdown) + send(app, LOG_ID or 'me', text, fix_markdown=fix_markdown) def send_log_doc(doc, caption='', fix_markdown=False, remove_file=False): - send_doc(app, LOG_ID or 'me', doc, - caption=caption, fix_markdown=fix_markdown) + send_doc(app, LOG_ID or 'me', doc, caption=caption, fix_markdown=fix_markdown) if remove_file: remove(doc) diff --git a/sedenecem/core/send.py b/sedenecem/core/send.py index fe5932b..21a7eb9 100644 --- a/sedenecem/core/send.py +++ b/sedenecem/core/send.py @@ -8,6 +8,7 @@ # from pyrogram.types import Chat + from .misc import MARKDOWN_FIX_CHAR @@ -17,11 +18,13 @@ def send(client, chat, text, fix_markdown=False, reply_id=None): if len(text) < 4096: if not reply_id: - client.send_message(chat.id if isinstance( - chat, Chat) else chat, text) + client.send_message(chat.id if isinstance(chat, Chat) else chat, text) else: - client.send_message(chat.id if isinstance( - chat, Chat) else chat, text, reply_to_message_id=reply_id) + client.send_message( + chat.id if isinstance(chat, Chat) else chat, + text, + reply_to_message_id=reply_id, + ) return file = open('temp.txt', 'w+') @@ -32,8 +35,7 @@ def send(client, chat, text, fix_markdown=False, reply_id=None): def send_sticker(client, chat, sticker): try: - client.send_sticker(chat.id if isinstance( - chat, Chat) else chat, sticker) + client.send_sticker(chat.id if isinstance(chat, Chat) else chat, sticker) except BaseException: pass @@ -42,7 +44,8 @@ def send_doc(client, chat, doc, caption='', fix_markdown=False): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - client.send_document(chat.id if isinstance( - chat, Chat) else chat, doc, caption=caption) + client.send_document( + chat.id if isinstance(chat, Chat) else chat, doc, caption=caption + ) except BaseException: pass diff --git a/sedenecem/core/webdriver.py b/sedenecem/core/webdriver.py index f90bb80..335c642 100644 --- a/sedenecem/core/webdriver.py +++ b/sedenecem/core/webdriver.py @@ -7,8 +7,8 @@ # All rights reserved. See COPYING, AUTHORS. # -from selenium.webdriver import Chrome, ChromeOptions from sedenbot import CHROME_DRIVER +from selenium.webdriver import Chrome, ChromeOptions def get_webdriver(): diff --git a/sedenecem/sql/__init__.py b/sedenecem/sql/__init__.py index 517ee24..9f4b21c 100644 --- a/sedenecem/sql/__init__.py +++ b/sedenecem/sql/__init__.py @@ -1,7 +1,7 @@ +from sedenbot import DATABASE_URL from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, scoped_session -from sedenbot import DATABASE_URL +from sqlalchemy.orm import scoped_session, sessionmaker BASE = declarative_base() diff --git a/sedenecem/sql/blacklist_sql.py b/sedenecem/sql/blacklist_sql.py index 93b1113..2386760 100644 --- a/sedenecem/sql/blacklist_sql.py +++ b/sedenecem/sql/blacklist_sql.py @@ -1,6 +1,7 @@ from threading import RLock -from sqlalchemy import func, distinct, Column, String, UnicodeText -from sedenecem.sql import SESSION, BASE + +from sedenecem.sql import BASE, SESSION +from sqlalchemy import Column, String, UnicodeText, distinct, func class BlackListFilters(BASE): @@ -16,9 +17,11 @@ def __repr__(self): return "<Blacklist filter '%s' for %s>" % (self.trigger, self.chat_id) def __eq__(self, other): - return bool(isinstance(other, BlackListFilters) - and self.chat_id == other.chat_id - and self.trigger == other.trigger) + return bool( + isinstance(other, BlackListFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger + ) BlackListFilters.__table__.create(checkfirst=True) @@ -39,8 +42,7 @@ def add_to_blacklist(chat_id, trigger): def rm_from_blacklist(chat_id, trigger): with BLACKLIST_FILTER_INSERTION_LOCK: - blacklist_filt = SESSION.query( - BlackListFilters).get((str(chat_id), trigger)) + blacklist_filt = SESSION.query(BlackListFilters).get((str(chat_id), trigger)) if blacklist_filt: # sanity check if trigger in CHAT_BLACKLISTS.get(str(chat_id), set()): @@ -67,16 +69,18 @@ def num_blacklist_filters(): def num_blacklist_chat_filters(chat_id): try: - return SESSION.query(BlackListFilters.chat_id).filter( - BlackListFilters.chat_id == str(chat_id)).count() + return ( + SESSION.query(BlackListFilters.chat_id) + .filter(BlackListFilters.chat_id == str(chat_id)) + .count() + ) finally: SESSION.close() def num_blacklist_filter_chats(): try: - return SESSION.query(func.count( - distinct(BlackListFilters.chat_id))).scalar() + return SESSION.query(func.count(distinct(BlackListFilters.chat_id))).scalar() finally: SESSION.close() diff --git a/sedenecem/sql/filters_sql.py b/sedenecem/sql/filters_sql.py index 8679c0c..760d0ac 100644 --- a/sedenecem/sql/filters_sql.py +++ b/sedenecem/sql/filters_sql.py @@ -1,8 +1,8 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError -from sqlalchemy import Column, UnicodeText, Numeric, String +from sqlalchemy import Column, Numeric, String, UnicodeText class Filters(BASE): @@ -20,8 +20,10 @@ def __init__(self, chat_id, keyword, reply, f_mesg_id): def __eq__(self, other): return bool( - isinstance(other, Filters) and self.chat_id == other.chat_id - and self.keyword == other.keyword) + isinstance(other, Filters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) Filters.__table__.create(checkfirst=True) @@ -36,8 +38,7 @@ def get_filter(chat_id, keyword): def get_filters(chat_id): try: - return SESSION.query(Filters).filter( - Filters.chat_id == str(chat_id)).all() + return SESSION.query(Filters).filter(Filters.chat_id == str(chat_id)).all() finally: SESSION.close() diff --git a/sedenecem/sql/gban_sql.py b/sedenecem/sql/gban_sql.py index 7a497fd..460bb3a 100644 --- a/sedenecem/sql/gban_sql.py +++ b/sedenecem/sql/gban_sql.py @@ -1,5 +1,5 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError diff --git a/sedenecem/sql/gmute_sql.py b/sedenecem/sql/gmute_sql.py index 21eb3e5..29612bc 100644 --- a/sedenecem/sql/gmute_sql.py +++ b/sedenecem/sql/gmute_sql.py @@ -1,5 +1,5 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError diff --git a/sedenecem/sql/keep_read_sql.py b/sedenecem/sql/keep_read_sql.py index 1d6607a..642d063 100644 --- a/sedenecem/sql/keep_read_sql.py +++ b/sedenecem/sql/keep_read_sql.py @@ -1,5 +1,5 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError diff --git a/sedenecem/sql/lydia_sql.py b/sedenecem/sql/lydia_sql.py deleted file mode 100644 index c805339..0000000 --- a/sedenecem/sql/lydia_sql.py +++ /dev/null @@ -1,74 +0,0 @@ -from sqlalchemy import Column, UnicodeText, LargeBinary, Numeric -from sedenecem.sql import SESSION, BASE - - -class LydiaAI(BASE): - __tablename__ = "lydia_ai" - user_id = Column(Numeric, primary_key=True) - chat_id = Column(Numeric, primary_key=True) - session_id = Column(UnicodeText) - session_expires = Column(Numeric) - - def __init__( - self, - user_id, - chat_id, - session_id, - session_expires - ): - self.user_id = user_id - self.chat_id = chat_id - self.session_id = session_id - self.session_expires = session_expires - - -LydiaAI.__table__.create(checkfirst=True) - - -def get_s(user_id, chat_id): - try: - return SESSION.query(LydiaAI).get((user_id, chat_id)) - except: - return None - finally: - SESSION.close() - - -def get_all_s(): - try: - return SESSION.query(LydiaAI).all() - except: - return None - finally: - SESSION.close() - - -def add_s( - user_id, - chat_id, - session_id, - session_expires -): - adder = SESSION.query(LydiaAI).get((user_id, chat_id)) - if adder: - adder.session_id = session_id - adder.session_expires = session_expires - else: - adder = LydiaAI( - user_id, - chat_id, - session_id, - session_expires - ) - SESSION.add(adder) - SESSION.commit() - - -def remove_s( - user_id, - chat_id -): - note = SESSION.query(LydiaAI).get((user_id, chat_id)) - if note: - SESSION.delete(note) - SESSION.commit() diff --git a/sedenecem/sql/mute_sql.py b/sedenecem/sql/mute_sql.py index e566382..48b3be6 100644 --- a/sedenecem/sql/mute_sql.py +++ b/sedenecem/sql/mute_sql.py @@ -1,5 +1,5 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError @@ -21,8 +21,11 @@ def __init__(self, chat_id, sender): def is_muted(chat_id, sender): try: - ret = SESSION.query(Mute).filter(Mute.chat_id == str( - chat_id), Mute.sender == str(sender)).all() + ret = ( + SESSION.query(Mute) + .filter(Mute.chat_id == str(chat_id), Mute.sender == str(sender)) + .all() + ) return len(ret) > 0 except BaseException: return None @@ -37,8 +40,11 @@ def mute(chat_id, sender): def unmute(chat_id, sender): - rem = SESSION.query(Mute).filter(Mute.chat_id == str( - chat_id), Mute.sender == str(sender)).all() + rem = ( + SESSION.query(Mute) + .filter(Mute.chat_id == str(chat_id), Mute.sender == str(sender)) + .all() + ) if len(rem): for item in rem: SESSION.delete(item) diff --git a/sedenecem/sql/notes_sql.py b/sedenecem/sql/notes_sql.py index 3bc684e..decd1ea 100644 --- a/sedenecem/sql/notes_sql.py +++ b/sedenecem/sql/notes_sql.py @@ -1,8 +1,8 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError -from sqlalchemy import Column, UnicodeText, Numeric, String +from sqlalchemy import Column, Numeric, String, UnicodeText class Notes(BASE): diff --git a/sedenecem/sql/pm_permit_sql.py b/sedenecem/sql/pm_permit_sql.py index b8388c6..d9e8413 100644 --- a/sedenecem/sql/pm_permit_sql.py +++ b/sedenecem/sql/pm_permit_sql.py @@ -1,5 +1,5 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError @@ -19,8 +19,7 @@ def __init__(self, chat_id): def is_approved(chat_id): try: - return SESSION.query(PMPermit).filter( - PMPermit.chat_id == str(chat_id)).one() + return SESSION.query(PMPermit).filter(PMPermit.chat_id == str(chat_id)).one() except BaseException: return None finally: diff --git a/sedenecem/sql/snips_sql.py b/sedenecem/sql/snips_sql.py index 56e7dce..1eb29b0 100644 --- a/sedenecem/sql/snips_sql.py +++ b/sedenecem/sql/snips_sql.py @@ -1,9 +1,9 @@ try: - from sedenecem.sql import SESSION, BASE + from sedenecem.sql import BASE, SESSION except ImportError: raise AttributeError -from sqlalchemy import Column, UnicodeText, Numeric +from sqlalchemy import Column, Numeric, UnicodeText class Snips(BASE): diff --git a/sedenecem/translator/__init__.py b/sedenecem/translator/__init__.py index fad3a63..af380f4 100644 --- a/sedenecem/translator/__init__.py +++ b/sedenecem/translator/__init__.py @@ -7,8 +7,8 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import getcwd, listdir, path from json import loads +from os import getcwd, listdir, path pwd = f'{getcwd()}/sedenecem/translator' diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 83980f9..15f017a 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -1,6 +1,6 @@ { "added": "added", - "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.gban <username/reply>\nUsage: Bans the person in all groups you have in common with them.\n\n.ungban <username/reply>\nUsage: Removes the person from the gbanned list\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.gmute <username/reply>\nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute <username/reply>\nUsage: Removes the person from the gmuted list\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.", + "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.", "adminUsage": "I'm not admin!", "adminlist": "%1Admins in%1 %2%3%2%1:%1", "afkEnd": "I'm no longer AFK.", @@ -59,7 +59,7 @@ "autoppResult": "Profile photo has been updated!", "banError": "%1Something went wrong!%1\n\n%2%3%2", "banFailUser": "Please specify a valid user!", - "banLog": "%1#BAN\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", + "banLog": "#BAN\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "banProcess": "Whacking the pest!", "banResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4banned!%4", "barcodeInfo": ".barcode <text>\nUsage: Make a barcode from provided content.\nEg: .barcode https://devotag.com\n\nNote: Use .decode command to get decoded content.", @@ -132,7 +132,7 @@ "deepfryError": "I can't deepfry this!", "deepfryInfo": ".deepfry or .fry [1-5]\nUsage: Applies deepfry effect to specified image.", "deepfryNoPic": "%1reply to a picture or sticker for me to %2!%1", - "deezerArlMissing": "Deezer ARL Token missing! Add it to environment vars.", + "deezerArlMissing": "Deezer ARL Token missing! Add it to config vars.", "deezerInfo": ".deezer <deezer track url>\nUsage: Downloads songs or albums from Deezer.", "delErrorLog": "Well, I can't delete a message", "delInfo": ".del\nUsage: Deletes the message you replied to.", @@ -198,7 +198,7 @@ "filterUpdated": "%2Filter%2 %1%3%1 %2updated%2", "filtersSqlLog": "Unable to run filters module, no SQL connection found", "founderResult": "%1=======================================\n\nThis bot;\nDeveloped by%1 %2[NaytSeyd](https://t.me/NightShade)%2 %1and%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2\n%1In addition\nLovingly edited by%1 %2[Sedenogen](https://t.me/CiyanogenOneTeams)%2\n\n%1=======================================%1", - "gbanLog": "%1#GBAN\nUSER: [%2](tg://user?id=%3)", + "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4globally banned!%4", "gbannedUsers": "GBanned Users:", "geniusToken": "Please set the Genius token. Thank you!", @@ -225,7 +225,9 @@ "gitUserInfo": "%1 GitHub information", "gitUserNotFound": "User not found.", "gitWebsite": "Website", - "gmuteLog": "%1#GMUTE\nUSER: [%2](tg://user?id=%3)", + "globalsInfo": ".gban <username/reply>\nUsage: Bans the person in all groups you have in common with them.\n\n.ungban <username/reply>\nUsage: Removes the person from the gbanned list\n\n.listgban\nUsage: List GBanned users.\n\n.gmute <username/reply>\nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute <username/reply>\nUsage: Removes the person from the gmuted list\n\n.listgmute\nUsage: List GMuted users.", + "globalsSqlLog": "Unable to run GBan and GMute command, no SQL connection found", + "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4", "gmutedUsers": "GMuted Users:", "googleDesc": "No description found.", @@ -251,12 +253,12 @@ "kangstr7": "Ay look over there (\u2609\uff61\u2609)!\u2192\nWhile I kang this...", "kangstr8": "Roses are red violets are blue, kanging this sticker so my pacc looks cool", "kangstr9": "Imprisoning this sticker...", - "kickLog": "%1#KICK\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", + "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%3%4)", "kickProcess": "Kicking...", "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4", "kickmeResult": "Goodbye.. i go away \ud83e\udd20", "langName": "English", - "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key missing! Add it to environment vars.%2", + "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key missing! Add it to config vars.%2", "lastfmInfo": ".lastfm\nUsage: Shows currently scrobbling track or most recent scrobbles if nothing is playing.", "lastfmProcess": "[%1](%2) %3is now listening to:%3\n\n\u2022 [%4](%5)\n%6%7%6", "lastfmProcess2": "[%1](%2) %3was last listening to:%3\n\n", @@ -288,7 +290,7 @@ "lydiaError2": "Reply to a user to activate Lydia AI on them", "lydiaError3": "This person doesn't have Lydia activated on him/her.", "lydiaInfo": ".addcf <username/reply>\nUsage: Add's lydia auto chat request in the chat.\n\n.remcf <username/reply>\nUsage: Remove's lydia auto chat request in the chat.\n\n.repcf <username/reply>\nUsage: Starts lydia repling to perticular person in the chat.", - "lydiaMissingApi": "%1[Lydia](https://coffeehouse.intellivoid.info/dashboard)%1 %2API key missing! Add it to environment vars.%2", + "lydiaMissingApi": "%1[Lydia](https://coffeehouse.intellivoid.info/dashboard)%1 %2API key missing! Add it to config vars.%2", "lydiaResult": "%1Hey dude:%1 %2", "lydiaResult2": "%1Lydia successfully enabled for user:%1 %2%3%2 %1in chat:%1 %2%4%2", "lydiaResult3": "%1Lydia successfully disabled for user:%1 %2%3%2 %1in chat:%1 %2%3%2", @@ -309,7 +311,7 @@ "mirrorError": "Error: Different mirror not found for the link", "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.birakmamseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", - "muteLog": "%1#MUTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", + "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", "muteResult": "%1[%2](tg://user?id=%3)%1 %4muted!%4", "nameOk": "Your name was succesfully changed.", @@ -334,7 +336,7 @@ "notesRemoved": "%2Note%2 #%1%3%1 %2removed%2", "notesSqlLog": "Unable to run notes module, no SQL connection found", "notesUpdated": "%1Note successfully updated. You can call the note with .call #%2%1", - "ocrApiMissing": "%1[%2](https://ocr.space/ocrapi)%1 %3API key missing! Add it to environment vars.%3", + "ocrApiMissing": "%1[%2](https://ocr.space/ocrapi)%1 %3API key missing! Add it to config vars.%3", "ocrError": "Couldn't read this.\nI guess i need new glasses.", "ocrInfo": ".ocr <language>\nUsage: Reply to an image or sticker to extract text from it.\n\nGet language codes from [here](https://ocr.space/ocrapi)", "ocrReading": "Reading...", @@ -354,7 +356,7 @@ "packinfoResult": "%1Sticker Title:%1 %2%3%2\n%1Sticker Short Name:%1 %2%4%2\n%1Official:%1 %2%5%2\n%1Archived:%1 %2%6%2\n%1Animated:%1 %2%7%2\n%1Stickers In Pack:%1 %2%8%2\n%1Emojis In Pack:%1\n%9", "phhError": "%1Couldn't find anything for%1 %2%3%2", "picspamLog": "#PICSPAM\nPicSpam was executed successfully", - "pinLog": "%1#PIN\nGROUP: %2 (%1%3%4%3%1)%1", + "pinLog": "#PIN\nGROUP: %1 (%2%3%2)", "pinResult": "Pinned Successfully!", "pipHelp": "Use .seden system command to see an example.", "pipSearch": "Searching...", @@ -383,7 +385,7 @@ "privateUsage": "This command can only be used private chats.", "processing": "Processing...", "profileInfo": ".username <new username>\nUsage: Changes your Telegram username.\n\n.name <firstname> or .name <firstname> <lastname>\nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio <new bio>\nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online <disable>\n\nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", - "promoteLog": "%1#PROMOTE\nUSER: [%2](tg://user?id=%3)\nGROUP: %4 (%1%5%6%5%1)%1", + "promoteLog": "#PROMOTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "promoteProcess": "Promoting...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4promoted!%4", "providedProxy": "Proxy connection provided ...", @@ -398,7 +400,7 @@ "quotlyInfo": ".q\nUsage: Enhance ur text to sticker.", "randomResult": "%1Query:%1\n%2%3%2\n%1Output:%1\n%2%4%2", "randomUsage": "2 or more argument required.", - "rbgApiMissing": "%1[%2](https://www.remove.bg/api)%1 %3API key missing! Add it to environment vars.%3", + "rbgApiMissing": "%1[%2](https://www.remove.bg/api)%1 %3API key missing! Add it to config vars.%3", "rbgInfo": ".rbg reply to any image (Warning: does not work on stickers.)\nUsage: Removes the background of images, using remove.bg API", "rbgLog": "error.log", "rbgProcessing": "Removing background from this image..", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2876375..3fd7afc 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -1,6 +1,6 @@ { "added": "eklendi", - "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.gban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.gmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.", + "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.", "adminUsage": "Y\u00f6netici de\u011filim!", "adminlist": "%2%3%2 %1sohbetinde bulunan y\u00f6neticiler:%1", "afkEnd": "Art\u0131k AFK de\u011filim.", @@ -20,7 +20,7 @@ "afkstr15": "Sahibim burada de\u011fil, bu y\u00fczden bana yazmay\u0131 b\u0131rak.", "afkstr16": "Burada olsayd\u0131m,\nSana nerede oldu\u011fumu s\u00f6ylerdim.\n\nAma ben de\u011filim,\ngeri d\u00f6nd\u00fc\u011f\u00fcmde bana sor...", "afkstr17": "Uzaklarday\u0131m!\nNe zaman d\u00f6nerim bilmiyorum !\nUmar\u0131m birka\u00e7 dakika sonra!", - "afkstr18": "Sahibim \u015fuan da m\u00fcsait de\u011fil. Ad\u0131n\u0131z\u0131, numar\u0131n\u0131z\u0131 ve adresinizi verirseniz ona iletibilirm ve b\u00f6ylelikle geri d\u00f6nd\u00fc\u011f\u00fc zaman.", + "afkstr18": "Sahibim \u015fu anda m\u00fcsait de\u011fil. Ad\u0131n\u0131z\u0131, numaran\u0131z\u0131 ve adresinizi verirseniz ona iletibilirim ve b\u00f6ylelikle geri d\u00f6nd\u00fc\u011f\u00fc zaman sizinle ilgilenebilir.", "afkstr19": "\u00dczg\u00fcn\u00fcm, sahibim burada de\u011fil.\nO gelene kadar benimle konu\u015fabilirsiniz.\nSahibim size sonra d\u00f6ner.", "afkstr2": "Arad\u0131\u011f\u0131n\u0131z ki\u015fi \u015fu anda telefona cevap veremiyor. Sinyal sesinden sonra kendi tarifeniz \u00fczerinden mesaj\u0131n\u0131z\u0131 b\u0131rakabilirsiniz. Mesaj \u00fccreti 49 kuru\u015ftur. \nbiiiiiiiiiiiiiiiiiiiiiiiiiiiiip!", "afkstr20": "Bahse girerim bir mesaj bekliyordun!", @@ -59,7 +59,7 @@ "autoppResult": "Profil foto\u011fraf\u0131n\u0131z ayarland\u0131!", "banError": "%1Bir hata olu\u015ftu!%1\n\n%2%3%2", "banFailUser": "Ge\u00e7erli bir kullan\u0131c\u0131 belirtiniz!", - "banLog": "%1#BAN\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", + "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "banProcess": "D\u00fc\u015fman vuruldu!", "banResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %1yasakland\u0131!%1", "barcodeInfo": ".barcode <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir barkod yap\u0131n.\n\u00d6rnek: .barcode https://devotag.com\n\nNot: \u00e7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", @@ -199,7 +199,7 @@ "filterUpdated": "%2Filtre%2 %1%3%1 %2g\u00fcncellendi%2", "filtersSqlLog": "Filters mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "founderResult": "%1=======================================\n\nBu bot;%1\n%2[NaytSeyd](https://t.me/NightShade)%2 %1ve%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2 %1taraf\u0131ndan geli\u015ftirilmektedir.\nEk olarak%1\n%2[Sedenogen](https://t.me/CiyanogenOneTeams)%2 %1taraf\u0131ndan sevgi ile d\u00fczenlenmi\u015ftir.\n\n=======================================%1", - "gbanLog": "%1#GBAN\nKullan\u0131c\u0131: [%2](tg://user?id=%3)", + "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", @@ -226,7 +226,9 @@ "gitUserInfo": "%1 GitHub bilgileri", "gitUserNotFound": "Kullan\u0131c\u0131 bulunamad\u0131.", "gitWebsite": "Website", - "gmuteLog": "%1#GMUTE\nKullan\u0131c\u0131: [%2](tg://user?id=%3)", + "globalsInfo": ".gban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.listgban\nKullan\u0131m: K\u00fcresel yasaklanan kullan\u0131c\u0131lar\u0131 listeler.\n\n.gmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.listgmute\nKullan\u0131m: K\u00fcresel susturulan kullan\u0131c\u0131lar\u0131 listeler.", + "globalsSqlLog": "GBan ve GMute komutu \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", + "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4", "gmutedUsers": "K\u00fcresel susturulan kullan\u0131c\u0131lar:", "googleDesc": "A\u00e7\u0131klama bulunamad\u0131.", @@ -253,7 +255,7 @@ "kangstr7": "Hey \u015furaya bak. (\u2609\uff61\u2609)!\u2192\nBen bunu d\u0131zlarken...", "kangstr8": "G\u00fcller k\u0131rm\u0131z\u0131 menek\u015feler mavi, bu \u00e7\u0131kartmay\u0131 paketime d\u0131zlayarak haval\u0131 olaca\u011f\u0131m...", "kangstr9": "\u00c7\u0131kartma hapsediliyor...", - "kickLog": "%1#KICK\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", + "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor...", "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4", "kickmeResult": "G\u00fcle G\u00fcle ben gidiyorum \ud83e\udd20", @@ -311,7 +313,7 @@ "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.birakmamseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", "mockUsage": "bANa bIr mETin vEr!", - "muteLog": "%1#MUTE\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", + "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "muteProcess": "Sessize al\u0131n\u0131yor...", "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4", "nameOk": "Ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", @@ -355,7 +357,7 @@ "packinfoResult": "%1\u00c7\u0131kartma paketi ba\u015fl\u0131\u011f\u0131:%1 %2%3%2\n%1\u00c7\u0131kartma paketi k\u0131sa ad\u0131:%1 %2%4%2\n%1Resmi paket mi:%1 %2%5%2\n%1Ar\u015fivlenmi\u015f mi:%1 %2%6%2\n%1Animasyonlu mu:%1 %2%7%2\n%1Pakettki \u00e7\u0131kartma say\u0131s\u0131:%1 %2%8%2\n%1Paketteki emoji say\u0131s\u0131:%1\n%9", "phhError": "%2%3%2 %1aramas\u0131 i\u00e7in bir ROM bulunamad\u0131%1", "picspamLog": "#PICSPAM\nPicSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", - "pinLog": "%1#PIN\nGRUP: %2 (%1%3%4%3%1)%1", + "pinLog": "#PIN\nGRUP: %1 (%2%3%2)", "pinResult": "Ba\u015far\u0131yla sabitlendi!", "pipHelp": "Bir \u00f6rnek g\u00f6rmek i\u00e7in .seden system komutunu kullan\u0131n.", "pipSearch": "Aran\u0131yor...", @@ -384,7 +386,7 @@ "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", "processing": "\u0130\u015fleniyor...", "profileInfo": ".username <yeni kullan\u0131c\u0131 ad\u0131>\nKullan\u0131m: Telegram'daki kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 de\u011fi\u015fir.\n\n.name <isim> veya .name <isim> <soyisim>\nKullan\u0131m: Telegram'daki isminizi de\u011fi\u015fir. (Ad ve soyad ilk bo\u015flu\u011fa dayanarak birle\u015ftirilir.)\n\n.setbio <yeni biyografi>\nKullan\u0131m: Telegram'daki biyografinizi bu komutu kullanarak de\u011fi\u015ftirir.\n\n.reserved\nKullan\u0131m: Rezerve etti\u011finiz kullan\u0131c\u0131 adlar\u0131n\u0131 g\u00f6sterir.\n\n.block\nKullan\u0131m: Bir kullan\u0131c\u0131y\u0131 engeller.\n\n.unblock\nKullan\u0131m: Engellenmi\u015f kullan\u0131c\u0131n\u0131n engelini kald\u0131r\u0131r.\n\n.online <disable>\nKullan\u0131m: Hesab\u0131n\u0131z\u0131, siz Telegram'da \u00e7evrimi\u00e7i olmasan\u0131z bile \u00e7evrimi\u00e7i g\u00f6sterir.\nA\u00e7mak i\u00e7in .online, kapatmak i\u00e7in .online disable yaz\u0131n.\n\nNOT: Etkili olmas\u0131 i\u00e7in son g\u00f6r\u00fclmenizi herkese a\u00e7\u0131k yap\u0131n.", - "promoteLog": "%1#PROMOTE\nKULLANICI: [%2](tg://user?id=%3)\nGRUP: %4 (%1%5%6%5%1)%1", + "promoteLog": "#PROMOTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "promoteProcess": "Yetkilendiriliyor...", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici!%4", "providedProxy": "Proxy ba\u011flant\u0131s\u0131 sa\u011flan\u0131yor ...", From 1298954e4d347990ac3ef4215e216beb8f4c4fda Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Fri, 16 Apr 2021 22:16:34 +0300 Subject: [PATCH 033/242] amogus --- sedenbot/__init__.py | 6 ++-- sedenbot/modules/amogus.py | 60 ++++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 sedenbot/modules/amogus.py diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 8306ee2..331cf61 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -107,10 +107,8 @@ def set_logger(): # Check that the config is edited using the previously used variable. # Basically, check for config file. -CONFIG_CHECK = environ.get('___________DELETE_______THIS_____LINE__________', None) - -if CONFIG_CHECK: - LOGS.warn(get_translation('removeFirstLine')) +if environ.get('___________DELETE_______THIS_____LINE__________', None): + LOGS.warn(get_translation("removeFirstLine")) quit(1) # Telegram APP ID and HASH diff --git a/sedenbot/modules/amogus.py b/sedenbot/modules/amogus.py new file mode 100644 index 0000000..1dc9743 --- /dev/null +++ b/sedenbot/modules/amogus.py @@ -0,0 +1,60 @@ +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from io import BytesIO +from random import randint +from textwrap import wrap + +from PIL import Image, ImageDraw, ImageFont +from requests import get +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify, send_sticker + + +@sedenify(pattern='^.(amogu|su)s', compat=False) +def amogus(client, message): + args = extract_args(message) + if len(args) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + + edit(message, f"`{get_translation('processing')}`") + + arr = randint(1, 12) + fontsize = 100 + FONT_FILE = 'sedenecem/fonts/GoogleSans.ttf' + url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' # Thanks + font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) + + imposter = Image.open(BytesIO(get(f'{url}{arr}.png').content)) + text_ = '\n'.join(['\n'.join(wrap(part, 30)) for part in args.split('\n')]) + w, h = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textsize( + text_, font, stroke_width=2 + ) + text = Image.new('RGBA', (w + 40, h + 40)) + ImageDraw.Draw(text).multiline_text( + (15, 15), text_, '#FFF', font, stroke_width=2, stroke_fill='#000' + ) + w = imposter.width + text.width + 30 + h = max(imposter.height, text.height) + image = Image.new('RGBA', (w, h)) + image.paste(imposter, (0, h - imposter.height), imposter) + image.paste(text, (w - text.width, 0), text) + image.thumbnail((512, 512)) + + output = BytesIO() + output.name = 'sus.webp' + image.save(output, 'WebP') + output.seek(0) + + send_sticker(client, message.chat, output) + message.delete() + + +HELP.update({'amogus': get_translation('amogusInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 15f017a..ad11691 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -41,6 +41,7 @@ "alwaysOnline2": "Always online mode is already enabled!", "alwaysOnlineOff": "Always online mode disabled!", "alwaysOnlineOff2": "Always online mode is already disabled!", + "amogusInfo": ".amogus\nUsage: Converts typed text to amogus", "androidInfo": ".magisk\nGet latest Magisk releases\n\n.device <codename>\nUsage: Get info about android device codename or model.\n\n.codename <brand> <device>\nUsage: Search for android device codename.\n\n.specs <brand> <device>\nUsage: Get device specifications info.\n\n.twrp <codename>\nUsage: Get latest twrp download for android device.\n\n.orangefox <codename>\nUsage: Get latest OrangeFox download for android device.\n\n.phh <ABI>\nGet latest Phh releases", "androidPhhHeader": "%1Latest Phh %2AOSP Releases%1", "answerFromBot": "I didn't get an answer from bot!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 3fd7afc..664ffcc 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -41,6 +41,7 @@ "alwaysOnline2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten etkinle\u015ftirildi!", "alwaysOnlineOff": "S\u00fcrekli \u00e7evrimi\u00e7i modu devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", "alwaysOnlineOff2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", + "amogusInfo": ".amogus\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", "androidInfo": ".magisk\nG\u00fcncel Magisk s\u00fcr\u00fcmleri\n\n.device <kod ad\u0131>\nKullan\u0131m: Android cihaz\u0131 hakk\u0131nda bilgi\n\n.codename <marka> <cihaz>\nKullan\u0131m: Android cihaz kod adlar\u0131n\u0131 aray\u0131n.\n\nspecs <marka> <cihaz>\nKullan\u0131m: Cihaz \u00f6zellikleri hakk\u0131nda bilgi al\u0131n.\n\n.twrp <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel TWRP s\u00fcr\u00fcmlerini al\u0131n.\n\n.orangefox <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel OrangeFox Recovery s\u00fcr\u00fcmlerini al\u0131n.\n\n.phh <mimari>\nG\u00fcncel Phh AOSP s\u00fcr\u00fcmlerini al\u0131n.", "androidPhhHeader": "%1G\u00fcncel PHH %2AOSP S\u00fcr\u00fcmleri%1", "answerFromBot": "Botdan cevap alamad\u0131m!", From f2eacefa290ff978aafa17df1a247d1e5020d4c1 Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Sun, 18 Apr 2021 22:05:26 +0300 Subject: [PATCH 034/242] seden: memes: Add new command Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- sedenbot/modules/ban.py | 2 +- sedenbot/modules/memes.py | 31 +++++++++++++++++++++++++------ sedenecem/translator/en.json | 3 +++ sedenecem/translator/tr.json | 3 +++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 85d6bf4..96aab30 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -464,7 +464,7 @@ def get_users(client, message): ) -@sedenify(pattern='^.zombies', private=False, admin=True, compat=False) +@sedenify(pattern='^.zombies', private=False, compat=False) def zombie_accounts(client, message): args = extract_args(message).lower() chat_id = message.chat.id diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index e491903..e42a643 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -777,6 +777,28 @@ def h(message): ) +@sedenify(pattern='^.gay') +def gay_calculator(message): + args = extract_args(message) + reply = message.reply_to_message + random = randint(0, 100) + + try: + replied_user = reply.from_user + except BaseException: + pass + + if random: + if args: + return edit(message, f'**{get_translation("gayString", [args, random])}**') + if reply: + if replied_user.is_self: + edit(message, f'**{get_translation("gayString3", [random])}**') + else: + return edit(message, f'**{get_translation("gayString2", [random])}**') + edit(message, f'**{get_translation("gayString3", [random])}**') + + @sedenify(pattern='^.react$') def react(message): edit(message, choice(REACTS)) @@ -794,15 +816,12 @@ def run(message): @sedenify(pattern='^.xda$') def xda(message): + """ + Copyright (c) @NaytSeyd, Quotes taken + from friendly-telegram (https://gitlab.com/friendly-telegram) | 2020""" edit(message, choice(XDA_STRINGS)) -''' -Copyright (c) @NaytSeyd, Quotes taken -from friendly-telegram (https://gitlab.com/friendly-telegram) | 2020 -''' - - @sedenify(pattern='^.f (.*)') def payf(message): paytext = extract_args(message) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index ad11691..62876e8 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -199,6 +199,9 @@ "filterUpdated": "%2Filter%2 %1%3%1 %2updated%2", "filtersSqlLog": "Unable to run filters module, no SQL connection found", "founderResult": "%1=======================================\n\nThis bot;\nDeveloped by%1 %2[NaytSeyd](https://t.me/NightShade)%2 %1and%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2\n%1In addition\nLovingly edited by%1 %2[Sedenogen](https://t.me/CiyanogenOneTeams)%2\n\n%1=======================================%1", + "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 is %2\u00bd gay!", + "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 This person is %1\u00bd gay!", + "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 I am %1\u00bd gay!", "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4globally banned!%4", "gbannedUsers": "GBanned Users:", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 664ffcc..5bcfbc6 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -200,6 +200,9 @@ "filterUpdated": "%2Filtre%2 %1%3%1 %2g\u00fcncellendi%2", "filtersSqlLog": "Filters mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "founderResult": "%1=======================================\n\nBu bot;%1\n%2[NaytSeyd](https://t.me/NightShade)%2 %1ve%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2 %1taraf\u0131ndan geli\u015ftirilmektedir.\nEk olarak%1\n%2[Sedenogen](https://t.me/CiyanogenOneTeams)%2 %1taraf\u0131ndan sevgi ile d\u00fczenlenmi\u015ftir.\n\n=======================================%1", + "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 \u00bd%2 gay!", + "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 Sen \u00bd%1 gaysin!", + "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 \u00bd%1 gayim!", "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", From 477a409d7761e452c27abf1036dad0dd1f5ac91b Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Mon, 19 Apr 2021 23:32:26 +0300 Subject: [PATCH 035/242] seden: fonts: Change GoogleSans to OpenSans Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- sedenbot/modules/amogus.py | 4 ++-- sedenbot/modules/rgb.py | 2 +- sedenecem/fonts/GoogleSans.ttf | Bin 119984 -> 0 bytes sedenecem/fonts/OpenSans.ttf | Bin 0 -> 561512 bytes 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 sedenecem/fonts/GoogleSans.ttf create mode 100644 sedenecem/fonts/OpenSans.ttf diff --git a/sedenbot/modules/amogus.py b/sedenbot/modules/amogus.py index 1dc9743..f999e78 100644 --- a/sedenbot/modules/amogus.py +++ b/sedenbot/modules/amogus.py @@ -28,7 +28,7 @@ def amogus(client, message): arr = randint(1, 12) fontsize = 100 - FONT_FILE = 'sedenecem/fonts/GoogleSans.ttf' + FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' # Thanks font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) @@ -57,4 +57,4 @@ def amogus(client, message): message.delete() -HELP.update({'amogus': get_translation('amogusInfo')}) +HELP.update({'amogus': get_translation('amogusInfo')}) \ No newline at end of file diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index cc9f440..e082d13 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -39,7 +39,7 @@ def sticklet(client, message): draw = ImageDraw.Draw(image) fontsize = 230 - FONT_FILE = 'sedenecem/fonts/GoogleSans.ttf' + FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' font = ImageFont.truetype(FONT_FILE, size=fontsize) diff --git a/sedenecem/fonts/GoogleSans.ttf b/sedenecem/fonts/GoogleSans.ttf deleted file mode 100644 index ab605f9e2e3c347708a810435ba3a7debc52e1df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119984 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 diff --git a/sedenecem/fonts/OpenSans.ttf b/sedenecem/fonts/OpenSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..57d86a62bbc96352c570c8e8769b50e557d17263 GIT binary patch literal 561512 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�jvedMS@|Q;K!EY@D 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%0A6$@AgLPFktyY{TZnh<>|NB*6mPwvdF%*ly)|pL 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ɅtjlqHeF! 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%0AVm-!3|Ic8Waoo0}Q7K=g8$C4IXg^0N4me3NPjp z_zXn|LSzIWg$F=SFnx`JUJBY^0{Q{ZqlFg*F^axh7LGWkJ59z|gtK(T>la6(O}<*l z_LSRGvwUh%v%l47Q%heXGMTPwKfny~PA}SlUSvp>+*TASn8lKiM#hMWQ=X#Y$s$vI zWm9v3RJC?dH>6LixPVtydgBMJ{#2pMqs&(+2(33N#6qH*qi%0ev=NCt(P(3Fln%&5 z5v9#SS^Cm-h51@JLn*^r6$*PjS{?c#opd@ZQV1yX1_yUlXk50;#Dv>T%UUfU0%eR) zZw3ksi51CLO(wnn#vYYSgit8*nogGqSB!0lCY<9PcCD5;g}<~2VllhxqtW`96Yt`e z1e=7BJxohs{r7@Zd%h4iJ}PO#5JLYr9DfRlCBI=o!V}C81RevqZ8#8Bm}=~G%kd3? z*C|LAh8@gJX`dD(EuURbD&Sa%kt8`x7ns99DTX99F4fc;kF_@u%bMNwVkswgP{s+B zO;wX7xt&LAY~8>xtO=i{)H-=eZTlP7Bpd}5iWi=cDjyE`QsxiZ@9a&;lJ<}aO=Lqu zSD;4a0sfw`e7s~g|E=Y{>fo5puKKaWNBkD<pYCv1SNoXmE}u0Ya%CG*@s9Rbx-t77 z{GcT!0aFFVAW2oEs_HTaVKm1~es@*Xz2A0~N)*UiV6=S}@F+;Ny4P1BD{(}$l+$&H z<3E&}#{a9lJaLl$f+>eTgH{Cc*wMVDVhueF{Fxbu9sM1w2jTbTOmA4`6pTOY2;J+k z2tdTs2B`dw4aFb}0Q?AzxoMw&Bujy#=yRMJ7K7IZY&kdb9F=pbc)=*bGN26S>S2KH z1?vY_(Gu!q;DRl=d<W2`sq4dii<`#EB0fcaXnI-9m7LWY$!GKkh)TjKRiSycP&ZhQ zNP|2r(nx4A{|<k8YFRW9o;dMJ$e{nZ;L4o`4;+x;Oq%ih9Dk{~yrb*MH<5Ia+Gy#h z<1Z`D+<x^n)WM44e5=zeOZW*HC59q48g1#4Dg13Kx*zx*r{Rhs6az64Vx3>iNYywG zyUac{5^7=`MyFV-)seOv?MlB1YL}_R)}UAm4PI$VY~hM$&h9=EwUi{iZdEi8bTgsG z3?qhOWRM(g(=9hz)e48zHgudoG$pb?Cjybot2N3H3AJ>fn2uO1HCpqlJ;vr<t6C~1 zp!SJ#ctCU;(+@guDddfe2m9Pr5Yv4W<hMK=rt>Qf0o8kQDCEH}!ia;I4XGr8k|)^u z;b<t$#DO6<#bH@4H#az1pB{M%!QciTmi$0I6Oards&L$JBAt^6u}C%wo+CRj^TIX2 zM+Koyuxa5_2+Ps$1By!j`gByJPeSq!IgWhgrcX-JGiI!-FD<UwT4yZHB%8L@nZlup zzLMVQlj6nWddmAMdcK($VH%p<vMePMTOmEzXrGoU4iOScEH!oc6=kUl(CqPDVVkF< zEE6}`oaU{A&#k<}rt?-q`g%|I28~!<TQjD+rdB1=RaEy(-Yj-AQVuDpy7oH;ReorH z5|3q=!osr3KJ+(uYV!nJnQ#Q2@=sZGeLB;G$;9@zubDTgr?^9!GH9nkfb+D<%5Z3G z6<fZ3-o}~Zx{I@H`MBOvMjEzKng@IZHg^ylJ9Rxl%4Ep+^6ddn?85v~lSIPQdtKp0 z0dXQ)*X}fDFS%h!RmSdb>?(@S9VdpOP>FPxxFZ%{)u+ri=zU?r%K9#CU2+2|F3m4s z69t8)Dt~Zl6@HR&1^iXM&{TeHb}{y=633yi1juMSPkck)D#%V2;LcI-6hU+yxPSyr z6h|6>V^c8j=9CVBaS5uc(C8McBC(VpYvr6U#5X2o_BM`#0y5ddM0NIYLbHG7oZVaI z&%WYXM>1um%9NV&o3EHXuzkyGyDtaTard0r`~JbK;>^^U0}RKkICGp%&b)l*oJHIB z{Unhn@`KryS(opbJAdmAzJ1RAJuhEzCFt5{?C@C8a%ML8w(3C691l5XeGsRDt<fF~ zNtm30l3nNR1JBOM7G&H4-G5voo1E?lT_rFCVZZ|I1XJO!&meLzt_3Ls-(AQfhHyhs zeNXwk#-fISiXlV!T)b00A1D4G&zmPV?mhVp(f`KD^_QRg1|{ItGry}1=YtOIEXsFK z@41}goG!aF8n!#nK6~@bpU`DL!Nc0q=p%mPX|xtOAA{;qosDznHg+WvU3lA>GV8K? zbmpP&(7SwqRF7`WI^sf|dE&%M(4-3;PFGtYj?*kGaDr0JFZv|=HK8B9^FQ&YLBs(Y z#ka$0(OXOwe84Wqw5k*kVmVYu%FCI;Z~|v486W}A5iTDdOCuOUAu*GM7dYAgt5*oT zqy<6%#LyYikQxGItdPq|fEVUB5*)-u2^mB<AqcD)m@B{}U}49f#FRz6^0HKpmO?tN z8r>?@NXc$O18OK*E|tE_Nc5t!A<~APw#y+ATgFlB&Pigel;ICZRWkG!D?hJ^8RmcB zzvmgB(c>{vR~bFtp)QS>{FY(9m9bK^_*;h6NLl%}O!jTcG587d^GzbPT*41B@Q4oS z_$K&b2GLTLTyzto%dV7K2s7bOijk7h|M;VxfoeZ$nHHHTbCyM4pojQ+-sa)>F(&Bt zg8s~T7NfUx%-57eM12F=6{5dpbbJ`BSMXHC$N0a%m?QMrFvEV#JOt4Mu(DtT@UVh} zfrBGP#6?VW7(hXs6!>Xw3}Y!`!`{=yFGi~?)_mI;ZLL--RT0|QTRXFF^4=f*5t=B9 zxSWxQ(-pakY*}QLn=bDwyRxx|qBVqw3;O!5CzTM7R#@oz<tuo^^G_0&^7pW<Y#ekI z7PJ-6l3YS^hx$BuiESN>zD2r+J`ASQ!B9HQ-y_=c^J@07p%3|;!V94wM3Ux#jy%kY z9$@|iI+q=~dF3G?8UQB%<hgJHfEq#susVXJ1ZaXO7NlARGLxxK_<#NVKUQD1X(M_T zu@IX2#I_xqHuI&|44zp(yB|$_?z8M=6tk{>Hh<^yUowB%vFH7d`7HnQsZ;!!yx~tj z1C@^c`)7#ZuNXXY;<IP&zU#TOf2KsoKY#YFyPgxSYv*uQRLZEqra2KT@KDj)Fe0Yp zjF=;X4MUIpg%r*%#Yv&?rX3s4v6VoOGe#u#T!|Y}Fhk{36YTiR6`NtfgztwOXjYd7 zStOPUFiTeLnlpQPg<mZ4J0n5w{QigUxN_eus1ZdEku20-QPCA{=iE-GYkBc8m3>@f zNjes7t!%n>s9Iw*smaH6Mk6X<D7CTPX_SaXl(sXfR;ds*hM>o^&99u!LB$ib*{)N! z)TNT|ZhH1xhV}xVV<oJVDh!9LR-XlOmv!wLvHc2Yq-9e=HoG`t^*H@QPZ)Jis7*%A z6FTG5QyY~sl7WRLl?5KwB33E|UT~6sUi2kf0JihRkj1bOI)&Vl8@)=X>4BGfZu~}Q z73dY}2?4Lpm1cke6rKs(T^K?k_y@!`5HoUP2z|<+(w>S1dvPia(zuW;0<@A&<*8LZ zB$zRYqzZ>n3SL9#6q)lfLv;Z?j4*gjg!%ED8}{0nWrfy6iQ5;5+ZiU~^SE0rW9rIZ z-*+`dY~1p<KR^I{F{$y^j%8@2d1|m>jLRl7*$h5Sm^E7h^@$`}Pp$GlF*twk!<tDv z|IXV#?%ahwKsu!U?gzx3>7(|2%71t2u_rzz)cm_@vssOPLdTU_Bd2>G^=XZ%sLV$( zMpL=Z1f>V)$4LUjyTZm$alogQv0^nCRZ@DboFL{byzRDWGd(h?2wJ-uwE>OEPqVZ{ ztxaph%BGB`4r=0vpjb9RP$IX*VKVdI6kJxiY7_s)e^3WjMlYFV+J8Bp;d%bs@4x5& zv12D(qaknj^x*P4mMldZKSt`4Ce13f#jIJSHQqz(NRs6S%svVd24zw|tO+~t`8%1V z^ryh*T@cMy4;jTXfLqQ3FXd9m{96US%Jop$HrF3F*X8@ByxX7_<6U`wf~xorL5;J= zffmk_14yW08*u3<Ec@wLGLE!yZ6N|RYDB7pnoEIPi8??$AQPB#TzpXw^m6I~&=EXG ztg8sI9Att)tG_tsfdp?4WN@*to*;zw|M2tOvl<)b&2DX+bqMW1k!fW^ThV{i6=sNi z94fv_JC0w;XT+Qmj1d~MzBr?vP@%iFso!3bfqWa4UsBw-xWpl&DhdofNnB-zprAzY z1+JASNMtXjI+(=GXwNM%I-BT~_pJQg&f^DU#j=T->|<TBj%AxJzGI_2ChJmV|Jf#Q zT(EfN6&JwMb~PVpkkt%K>bPto9NeVg`zqz-GwU<+8{|=Wm6G3ADoaj|dd5ZN#qv@$ z9c!LHzoB(@fA$7y(o=^_y1fO~15FCeP?VUOJxJbb`2`_K(8XC8jdjEH9Z=1fp~s~= znix=hTN9>&h)$!a>zo|mpVw+7erD)#vYPR?^ydDe6LNpik>|z#wjF&R_i`L=U>>I* z1pZ-z9+bsEPu+qXaRKzH0~#TOnwkuu*J*OS3N>QP1cXk=>}VEWN?-?2slemJJ*izI zMIY=!!x@aw0_^|}L%BxiD=Spx#1I6a3`B#`m&Fmqq!z*mMm&0)7Ql#keCtR2E4SUk zzxu(}%^w2l-v7XxKl9fO$?sh8BogiYenX<Zq&JYrK1%+0&&5yir}y#yLhK_darSZ2 zvaoDYi%jCVU+wEKkP{+Rp$@L7Hs+M3l6H-!#8XTfWy)$&$E0l=i6-;>q*7%KrFSYs z%2kX<V`kX*C0;AB<~CIP@z!l{5w{%TU-@v$<OOI4DH~!xI=vT(pImX;ohosUj}67h zfBpU2ef-Z)UPRVB98|~*W~m06Vh@y|u)4rDJLY4UawquOm{_wD@_!^uW1(qZmB_z{ z%+4d$AX<ExynEMEP9M6$b$A2z;|(uhR@2vjT<3<^u1iPe!3lLIR4@xpZ%Px;5m*(E zfGtWR^rQw+LvZuKkb)M2*%y{Q0DM8DgCI>%$l&Ht$FVvFAdkJyAiLO58VCIda<VWV z;J(fUyW&ti$QxU|`gQ(&{se#WwKZ#AgDwj7$ou;0W%KXP?%6%`z=Hcw>4{?~b3gy1 zj5|ClWfakhUg~lgxF`8bU4<sq9gQ4#+eG&rY~%c85~($0aj7|rQ{gx<Yj3Kt$(9a= z<CI@(I$o=_r|zh|n%HnF^1Z%h&Fhey^)P=D_TE6gTlNm;9lK!BL;RjcQ2B!o^3P<q zy~RW(u4O5*D^%zhLnf7$NWN2F*jpIW1oCA(zrf%pkwg|Sd-5HMBjev0+cUPCKm+^K zP|bhSz8Zi~Y-bu)GFQ<vz?O}B!z_TT`W&_6Q3hhi3G^wTbx>`SfOwc!oj;twJTYCX z<Iu1PmIH*1#$zNTbnEaUa6<_#TuW<Yh+WY63(pM;ip<FHqI08;xhKp7243bW=!Wwa z{{a8in`_@j%ilYMf^V+n*YiuLF`P!D)NSzU_!;(~S#{?UsOjz4bi3q{n=X0+Q9EDX zfo^<j*&{r^^V1&=)pfWdVWZV%)kD=hkwoM@_SuKNnu5pbv>9~A>;t7WwO+s5*W3=h zy~1s!8;Q*ADF2;x>)zqty!|A<`@Qw=paSA8et@fk9+#*C8do^<k;zpTD;SF8f0Ur7 z-urq-_QA#3W$2+d`O~}j!6#N6yRS+baE1#)+`$iR`Rcai%5QA*u2e^}|8cTsTrf0t zJV%Myy(J)%9LbvpF*R3%A9WUFFRzD)8f=4#V;dZlqriOx14)5$1$6{dEEej3VqMsf z0O2n<R&_K?OrU6BOi;@Z1V1O7zz+e4V5^9alz;_yEGRe-o(-L6f%XNd5atD+>YObW zMxKGvu7Y_BPbh#FUO5=0=*%Uup(FR<q>KA5oOziq(qb~Zd`{(NeF93ySQC7sjbaq~ zRIJKv(<^129p<1htT)R|p(#`E2TL?U^HARScBt)0b1Jz^>M@z^w)W}G{4?nK!-(x! zYFL$3u~xUiY3gs7dVI^aV<$E@H<VKE+}F^&`NZ)pYbQ=tAgFVB@Fh(>`q!Z^h90~Z zEdGb}<ppkwnIiPyvI(1P8Wp`?N`n!}XmDzLP{RsZTm}oL{C-(zIPbm(hlU=Ub9ePL zzD6P=nxLkv#im6>Nrvd+e}^(l=UuI)RW{D)?%zLc<K`2`wrx4?+P3hYt`nQL93RuU z#-Z1E`(U*|?EG*cbAtXotd*5{cR&QxJ$WzWz4F`jl7l6or3!_sMq#;7C|3ldjm<$U zhX*;gj9{8aMqH>0CLk4*I!X$bT9}?B%qfGdhPalgJHX)y$$eUQORvS!WrTv-li>Hk z1b{(Kpc`nQn%0aU90AS#;1&lL2?tS(FuSl&;WSY54No){fOD!F5EgKx6YEd%ukiQq zubf=J@qH9UohbVL#v#$?$Ns(-N&d3(HonqnuWEqo$u8UY6hnHZX-NYVU5D1Ti|k`2 ztiEWfQKNPJ{=n?|`pF1^RX*U<sI<zcYPDSM>!A1{aZ)O?s-ba>QYs00`yWQbAMnG| z9yqvVTJkMqG0K(cF|)4%m;F_>a@Aj@G$A#KRFET4tsuPVSBm)4bd=7lnmFm=OU5M| zBqbax5;LKYs3_`Ir)JPmhUh<xicfCX06*gJ9sDcrZ`$-eDn>tjvwQI|KD+yaf1=l0 z=Jd?5YP7TMQ+$xejy#+uq_m<<!POHcE)%&_t=G*y5SpW)NR^0%d@hAbp(;RTx*i%o zkv6kJrZ$xF+p}LkmYT8ShGWwZ^*R5h!4(W$Gkfmk`v+<kXhenlzpNT1WEd-nDhVq7 z=bdxOB>Be)7p<5yer+Js&oYq^z+fVtgqH<d)eyu01V948u@&9G=LRrE;n@@>lgyPc z!xczIARz#a4lpVBi5xtD(jm+{#7~_k%AU)-fKPdUopp#p9H~V?ZXTP+bhekKx-#>g zG^z_5Cd7P!irO(EcAUG+!9w}`kD&3a&050$sM3`A%q!#C<D0fT`sk)@$EfHdTQ@xN z;I_?=4X)TSt#9k58PlmhZaJ}K%e3j6sT-2xx|X+g@OS)vlY{ems{6JTl=|({mgwkG zW4=|ag357LR3%lZTqpDfm#<=sC%a_xV<)z3f)j3i<gtz0jvfE>#HNjXGd6uPZPO;$ z!+Z+c^j>DX^i?41hlKh;IB(@QvOA<<jdG^AYcwxIKuR@W8KpY6KA;Ae;CF&`c)si( z_;rvB)OW2VnfA6+rn9lm#4qrtiX5o^dwXS-tHLPF8`h+=2Oe3!=J5wNZJ2V|j7b}| zOq#NeJaKHprU#F$+jMN!Wm6|_**v9hEv+qSZcV4UI<wcJI}EXsfT!ZY5<7prJy~p| zP+n?1$~*Dsn#~WR@)?(IIJ$LHwteIA<D1tVKelz+rfriaUA7ux6^JeT%WM_Pg5M5! zgit;k;{=$Ru+I%IY}jCP6vV0J*n+UyN8b}7yZ}nzFl@2F#cBl70X0`4)G)*5!I3P7 zQRDz@z$Qa**a_0o2yJ143qa@4ksA!20}(i}4G4<9YkHuzWn;CipwR6v3>zW0N(EL< zwH7SMjfEknW1?B_$v4Z3Rjg7PRoOxKk;K&+t;(aeNKDfwU3b;IXbKS?U&7e#+}hr- zyD-fVX&VF_P*Q4Xhf|+eTjRIcq!bASg{3A(Wo?;WZH`zCN^i#J%S`xv)ryp(3R2h8 zCH;K~r#DnGvB&09l+=`3%)#0U4d<_Pt2&G_ouUL9LQ5Hw&8CvcRFHNL1<X1tu9!82 zMhjybYP;LV6zVPRdh3cUs}@`xi9lDw*fNb;?ykBmc3-F4R99WQ333bN4zZLq8w~D9 z#~6z`8V}LaGhT1T3v`NkgqC?3=u{2yl3uWm)j*|^MgKb<Ar|`a0E@^wSH(&&7kK?( zE<oyt2*dycu`nF2%!?-t&xV24rl4CGh*3ol%>i?06DlkS9D{jz;|KhU{9*p34>xZ5 z5GBx9l=yHH|NW5f*w>Gv?iYssk#0_>A>;od^1b)@vwQi|Ppu#yloXjG_MpvS64%(E z(j?UkT?Ez57wfB$dP%7kt^RYRl5tDyHB}4gu*RWfHFYzk&<RxL@`{OZM^N&^jT=AY zUpjJ_f8nDon?FVg{tJrQdxAgy$SGnI{yI;5$>7gVT=FcE??Wd3MWm*UP|iRLojD5m zudiXhq`k#HQXbVP8Z7>U{6fD(yU0G{g7UedYHK{8F8aZ&@cS2xE3t-sqMW?S-$~y@ zzXCYGL7nmwPys&=a;tK}I*2p$dHY!00U1`PJApNKSQI%&HdeQU8}!Nm*$M+F2V5Fr zdf-9Juq4zVgF)8%0~#3j(a%(#3j-7cVL__JJeI?FEZl&DV*V^d48b{p7lW@a0QEod zyI63BH0d-06$i027b9OO=^0m+nJ_VXd9tHBk!o!v561SVRbbq;lBuRx$-LxvrCiE+ zR3^edQACThiZ%l(k+{TGzqMGRARR&Hc0{+5y;o`TA8(o6%y^}X6sF}0h>W51ztd{i zfYzlm0CUh9m<H(mEn%ckURc>7*XfNCCZcn5VyWAsLZym`RV(t2Gq^~wB*~PF95Et@ zhhgbggGehvQc)Ev%H9OU|D;gEMXI5WjxR0i>CKe%e%O{wbdO1uH1UJaIb8fuN^T1q zptjmxkLkp;TlP|wbFSrSv2_2Xx6W;TuADBoTjOECUL+$8CM3C8WFgpBkdf~PSLPFE zUm)cadPc^g(h9rG;is%qBo@Z2adH+}u)8}TX(LDxTdXpdG=R=0QfQ^hRqL!oEu!U; z@^BLq*GEiF13Q6$E>K`{y6vwNlKP!$$qfGUd<nAH*mgPC11aJX{yMge%|KKrt`huv zpdM`S8)dB+HvuPwxq^X%vQGjyp5w64@Ey-3OfrT@K_(aW@IIUad<^)_+<S0R44N2s zh4qVp8s5Yt!{*^VfKHFwDTF{6bZk0zPArpQYR1>ZjAxMB6|Zbbq#((wP;K7v>NPjK zu+yZ9BtsMGE@-JN28zN}7?n_7T_MwG!{DxkKIwJq=FMhQlvD!gNus{F>*{<2tu2r! zM665!z$dek*}pkC2naNCC>C)#Bd%p?xz6X07WsWzxyF_sM$e23I_&euFSzo+!WB`y z*r^+M;Ob|ezwYjNS`bGq!Ld8vzq5C)-5wg(=CHO@nq)frl>W@TT5V->%lKGKl}OAO z*NbFnhjm3&OHZVC+>CUw<%lV=&e1j5nQX34=wwE_XM7>~sDPXRp1#yXMhg{x;m@GZ zGGNB1h$Z}$?07Z_*?jlrJ(Bkt@ITChasw(XxDj3ka5&;^A7z4qq6rVsZ7}x<7|j$+ z1}xB=&;;CVbW(E@n6tNx){YxtdbzpA(>XfXc=iO2dG3gjImZRz8T>8KcNVinz#_Nb zJF-nC*ioq)4mn3Z^ihipx(u>nu}EfXGg}pUuTG}(nbaCR7h0KyY;5Qy5LXsEG-|)A zsKjkn(5l+RoItTYfJkcE)N2k-n$A#EzCPxklc=Rwj#SeF)wNSqoVk#bE6c&xk&4?@ zP+qps5)E+fRx<;obq&^7q9EiBLfi!OA(p5$5(yzE>skvE5OWl?T7x!31?|8S>H;aP zjL|5JoJz&YwKnUMIx*DfNT#9JfyN9?&I~@iHBj!cgjeMkMpErt9<W;c=3=QPR<NTb z6l^O<`{fXQDKV(r<y=tX?-)3|aA4qqTNgIx<A~RCw@St;M9}adIIxgEoo_U2<pzt< zml@M}!&HByp!(wK?ka~<V}WKB+`#OXu^OE}9ttVdCU-t^)<S1-TP^=ddu1?~VxBj; z-A2ys?iL<7<9jxpLJMm_1oi@j68t)O`TU*YYuPJ-Z)`@`uLIIz&Z`FzYymJ_oVTRU zfdQ19#l$9{L5=`&q#&r{xw#&Zt73M9k>XcC5a2*S3plZoVE!EofRY80wG)`4gl_~w z9l1fT3y3zzeFU=RM=4f793UT{EeHe|xt*iKQQ-M4rREM34gyoPfKHqNt~B?RIkW>N zH40IX|0*-NEf%+lx6)FHL|-TtJ8UpMA%AU69|MV6l;NVI8#hduxI&-LkW(wGCsfD% zm0}6a1td}_B~$rCP|~Pm;iPfERY{3hsnQV8lJ4FGB`%SO^@_xFk0q*8DM*E!^(JBk z&V*TApwdZO%QE9=Xpc-$sqln0jhjU6tsB?1k3ob}-7>x)O%N<4g?hRT@v6y&%2>ix z7%ldz^>US-<E-UcTe$(SO-9iox71MRtIR;<Plkc+QX)c1rmKAgVWV13NoYo?D6tqx z$|&Mr7adTWOzNK(z$5wBLWPo86e$P>BW|mCLTlHB7xd4rgj56wA(U&UUv=5x@#D5$ zF<GTjTPUKkZ`#-0trHU7NHCDEw=N`<R<5Bt-d>}GbgV15a8Xxt%i<L=qcIv=v3P%1 zB;R;ddonIBouG@AwI=(zeF0G@q`$beIpWmki*#Df_4=&ocBoJb4b@qhGwxMt+!f=m z=^Z<^_v(o?cCEr2cS&h6!;mzj${QOdWNR&4+!<+XTAj)S1HQJV@GOT_Y1Fb3)5LKp zFH{>hgGb2YNmc)|$Z9iNQl~3p9(NeZyYFr_=IcT5)#{+_tXN*4z%mt@$k&P%LB9KD zutePgy<eV%^$`FH8O?MD$CY3j6_x^~MtsM_o`Xe3K;_Zb1agK7>ACl?sDmLBEDz_n ztN`F}`m}(h|Gt>;mkK>CAmlUmwWC=9xf+W(BP}dTa+F~T{BlGl8#Qp?b3@ZX1G9)K zNGyDE<=B=?T|uZe+{zrfeD(VMkN2uHW?SX*%JM%qcv{>AG&iHI%T;pG;sup4S<+&0 zl{Zu*9TW{wUd@a9?NWtq>bhNvtqQp<Q?#hHu!Uw^#Z@a^W?MS@y~*tHn&Hn2t)|c< zco=;lqcSGq6_o>=N-8bNq>H4;`}t$HJnzkK?c9L;V`9Vtmu%*tzS^d;s4C-j+NkS4 z+5WGlmM&$$TwT_2(X#a)-)e&{ERJS(XH#vWc}*s+Hrj0+T^$p~=PyWB&#`N*c2Cgh zT-G@wtdPo}&0T&WO;KQE<7$h?EUn1bsFiAq#yU{5a8g%yPeWTiaW^D9I32w%hr?0n zfUt^@XZ+8c+HNouSs@Y0W@Cpwx%}uO*Gwt%sWpumu;4v3+%A$aTOl_x4`EZf8blc! zO@T=Og5<H!6oN*<5}(6AKs3RBCcq(-I?hQyf><Nq3^pwS{IXB-N9|#Mtl-*sZ8RD| zDiybS`?#rd>LxF(uC0#8B%#)}j*iB<I;X{baKpUg8BXPmMAB6esJP|~hv^SP&g`kq zwF}U|MVVN|!t6GW6VlwEBO3vFX@kYg_14sxEG3~Lw=3q*sR2MJn<(znE7gW-d&(2) zpT8h%C;d(c83O+B$KiId9)4pRSY&tp*S{0U>%aLkeZs;}0lim&(gxIyjSm>CA+{EL zyAaTU(<Sw}jSYd>4qQNK3&NYEHXwR|Kk5iOGB8SYMa4x(Wl*i&+B<bl{X{%U{n8)| z5?Cp6gEa669;Bk8bY)R7^1>hueo53WTyf=r<qKP&oeOKIwWS-qo>WBv&G0iXlA`<y zd5^5JKz5;G8OY0hQdC!qq*E0ak-5>W+>;yKvJ0}?3Jt%GZexNQU;F<sxLlPz=?%|b zFh4iA?V`q~_;;S3!0e_8qhreDMTLa~{fdNt2L&Y({%sV3c(CkMGKmCD=5Gf{<PxDy zi3aRFUsIFlPN)a(f;{as=+P}uwPza4&Qhr9x&yMduLT?I(Y)h%PeFXR3q)-gOAlym zen=YC!!To-#JmuKTw$<BEj_?#>0hm6f{`r8LE+<=XLI;X;T7CU3O<g-wOpk?j98<u zjh+KuI<HYG9C5Az)yVmT1J6O+XiHV$K;esi{lpsb-f)jZtaBe6TdtZRpZT<plD9$^ z29nIaFzw+7`Ah5?t0!vl5;~t*<MCT#{B_Ww%B@Y_lZ+;#|Mb}70ZS~qs@Up|y2Sc7 z;~9@O`DWaqj7NBvM{5kk&2rj6{Adu{<7Tn7m><rBBJtt8;$$?%zu|<G3aI}ARn*D3 z2AMPBF~_qHIJJqQ406ZfnL<(<kD~EcfH&{O7g_d(nMfqIsXVjp<o9Tu?cw+stFPOp z4))AcIah0}bpy3?pDgG|`o>%oZ3{*9QKI@1jlFG}GdgL7#xc$l*NeuM6oA53R-m$6 zFm)_w`?X88cD=Yus&Vu<D}4Imz}Oy*eMU>wf{I08`+ID-SWGj21TSDS*ixQ>%5Ma$ zP+$%t=5FYYn3K~yfJDE_Ke<}Vx#`84!nvaajUHa-pyfB5<$rg?d8a{uW|#^vg#mSe zGB;|V$C9BED;Svca<bcqf+0YkVE4}f1=s}Oi~>PnG6H8n8JOP?PJBKmQszW8NOFe$ zx1%@wvqnt67&_sLlL06>0T;qRF0x+anOdY$L72K$9w;z3N>l-v&Xt`;ftgJ&azvx> zrV8FPX3ES~x6`EIP?uQ>+8n$CVOL(A);89fk|=}lDYS>mUtd-1arQw*vuIJW%%-#2 z{Cb&K(m!)fC|y=LZ~m!IYOk#|nxGuwl-^}q?vJPIj`cw>ufm|MS>P8bH2Ka@O{}(| z&3emL@$)B9?{_tvUMw<0)<vWw+1I~#?;oC#@LwMOgU4VuM{Od;WUSaKyJ~6WrD3PT zH@>W#<R5T1H*_eezZDc3?3yd;(=`TDzQI;iV$f4&hq-ZzT_5tv3_-6!rMDZU8g=WK zibS2kRFFHSAQFwG{3R}vu0R)x7@?GA>zK+|17|GIN2~k>eVx0u)u#6c;S6U#W*R11 z&4CV$)S0TRU)mH^890T-7-(uEh;o-_Jz4IQTWpn;P?qSAmvxUB>twetE|6Kx=}E4Y zQH@8c%y$J-@l?4R7|y4pTxPdI-Y*vlKUA6ugmos0?jCbsdNgE`tL1hVfE&5%x>(q$ zgXXm9h=#IPPXoCNFzzSz6hna*a3N%7T%C6lya*wxn5u!;exvB-L@yyERhWJtIjBD_ zP#!cX5y(ZzPz9MtNI1dX!ciUtG$xSqh+c!knp4;g0kP9L;v*UGkV6@4tX@OSGd-Lf zlNb;m_#}PcJ%pmBnEZ%Y<dtk`+m&l%HWyKC_llJvsiiE{(crC&#$*bEQI@F^L0dtI zN{a5ZI8@N*qn5B(ZAPW*%%7v(=~|H|5HedmtcBLm^#v=&w@9Q^?fNAt^FX2OY=Suw zNtTNx17m6icf0a|5kXZ?88aqaz!)imv>G~1DNJ<9;5L(8{d*Bn7n<vFmw8INRVt<^ zO&^(gfy$z1<hF{Kl_kyYvff@690pQ}>x?lVE#Q0;)I~{$Lpk(Gb$*G(rKsI|ZE2+r z<Y`(<TGYX`r&OuuXt%MkQ9a9)z22vFdgKcJ`*%0KLx$vTkc&#xMV_JW1E3RWBi@9g zy@ysQG=xrpeG|i%(su!`qQHxz1dbJj=n;^Vz+dZAbIG`x4M2G;A4HsEwF}m81tMM0 zkOmJkmYBfD4x_6c$!>zFVxbV4o_dXULVc}+gBwmyn)o-Qu0cPRmbj?dlg!f4Qfqej zwzTJ$uqo^<K-xfk<J4l}Si+~4%&NKmcj(saX}Ytzs)MiFb3MBIx>fxCv+anTx)>4D z{a5kV@t?g{XtEckUtP4OjK7e-iEQwtL@TGf_~LvV+9~{=PZ$&R3P?q`M|eI&PdC8t z;ao{Qq^!cP7zIoakA<MlG6bbd;n!dUBk{FMG1`qBmDZ}oSP8(nV(`OXC`D8G`(%lP z3-vT~7I_Bu&oBnVmMM_+Z&R`3i;{1k*?e8wk27>SzuxTY;yyu_44x>}qvuUY>Xo^3 z4_AXesjTZshRlDCTlloS_+H{ETa+EBZQxJ!93iFrORFqShav2q>_i;@Cv+-%>jGVI zA-53!HWAF#W{4@71^Ihc$p6Syiys4Vf#84lL--A3GDFTSrv4OIm9Sujbr7{yFfhlV za?`KB8J9=QM^HH&W)#Yx#lhaI_kd>>iqv7*4JC!;2{kJ4JPV6f=yHLdLV)GS175`X zxEd0s;ZAN06*)Q`>N*NBMx(<7Z8YPRS4|SPS`Ownis-Gpe||U~=hl!bljfBxIkm)= zWg|YRcVmwf{4HXZCU0@tP1WTFW{jc6@6o7TP^LQ=s^P|P6`j%^uA)iPscLFf7m3(! z?jla#MRUdGR)U=#8~OvWk)&c~zpgPYBeENb%>kdvRYsS1`J}nB1$}8wWY<f<CjRur z%c(E!H%TQ1Nw#DU(O4>PcN8GX>+QgQW4lO%V~^zgk4Pbmbv5-5x=V=5b?1d4AGii0 z+UJ1020s@U7xM`tjfFN$Jea)-!WYyI!6TlILtqY!Hx{3S<P8{coYVnB4q1mV#;}@3 zj58ysiH#;{=t=K!gG<aXmbz~6R?0c}F)1?|IXzU_mbPl-R!U}clVgdgJ5>*>`MK6= ztJocuT|oRxZJOv`t_)<iKPyfaR<E0K$=dN-YO-rBt&C)fUm=;IC+-VH0vbErHTXGY zsT`WQkeChHR;9`b8Ky)xw8#`?Zy~0qB`P&thGbN+m@kk+)a^@pvp0KlQTBQ3B`c?l zTf5UIWm_Wgh~5|K6>0$^+zEx~r5^y>6l9X(a*3Rpg}hO8_^vE}*InQPqz?OL&GHTQ zdS=anL!l-e9M4_|G^_`@g+4I>4`dT_+VemAmxATa7Xn9Xdq&Q|<eQTBH|Q&$NSvMg zo72Jh@b_OhdOj_r@~4v?5TMUL-&aej?kW7Mb4m2^bLWdcTC1=5Q{~y3-<~dy7>3M4 z;$}FX3f5R23GOQhK}ljJ1%4HVMdx4Ug=2SY>s`4ek~H3~PZn=LN6wu-xn*;T+f`as z=&!5Tb++!eUqT9Fu!Qx&nCU@7$kUF9f<QnFjk7mEG;=fLd0$l?4}=nlP%wtp#ygXV zu9$N4tDcI+<I!UH8`+-O|C=u(hEKuo`72wL=LAgzeh)Sl;;JS<eQ6jc2tN~H!iAi? zU+2y~OBpZLXPo|d<$cTLvsJ}Kj6$Z8YQ=hyksN54av?fC`t|u$r8>2teEX#}83Mwl zB#@-7Aj)=}`Q$fO0ha(PD3934+z0CkOqx*GSbzRELbr@Rp!MfYx?{(Go@YCJekYrT zik@Z|F%K4NM}7gMF`RetTjz-n?%sKaWS(SZf3K$nnMTgarZ?@_ds%&z+;ry1Z!ZpL zn6DLR*aBCkhPsH@7yye=533qGp*_j8I)JbE@Ll&d*Gwx&)|9_8uJvt9-E<Kq?8Zgq z)8rRuPSkHt&GHB*$9SEi&0e+`IICfFmIeLoeB%6Psvw-ngbPv+jsE<nQY9seGU+qB zetVhteEd2}C9E~mZ&w=$_zh$OodL01=G@xrOijD@iNkYex1YZV`RDgr{`MIChqJBA zRsp?#22@ecNOJ2D(?@RooIj-2{@5bTL-M4VxNqbdsimF&XyJNKH4Ogp+l&04;=%vT zwebJ%YdcB3OFqiTATvJ?olo6<wDO?j@UaK)m0d6GA6Pn-k=?fMsvBod=my)?4(cUh z1)~FQm4|XO2MD!ea%7_H587htrTRw_nw$5+am)BJV#)9(nB&po03p$`$%%g1AJ~M2 zFROYYrAa?r32&nf)Vstw#w8q!ffQFqNfYa)M*9`FvndPX$~;!7O+8)$$Nmqo|9^V? z==%92hsBVqluUpOr~hA-eFuD0#rFSBT|yEPI`~i{7E~Z)cjw;C0+y{9rAZM5C2o?N z?80U@Y#|iuvsY9^L9ut``Ru)SANJmR@6TQ!{?DB=DfpiL-XA{MnYm@YbIzPO=S;aX zwmV4)i_!YFB64#7%YXMw*`a3llc&wAIIg=X^<Ogj<lFkU$T?&E1FrjZ<(9JQwx<h? z?Hayid(vB^p?Q`)t*L7roM-Pg)zB8rX@eD-sG!Mu=%yw7)!(m`hr>z=*S3M}u)t^x z2kWstd`>=8+mf!M4qi5UgWWK5ZTPy-Vr$`oWlc-O`h&AWI~Ke?@`{6ImnM&#yl4UJ zUIzN(w{!TbT`%|K=jH<^K8h{!+w}0>)`nRt=n|7(deO24V*RjWW@z@{2Z!qnhfir- z1XkCSZ>9Vh_zBun^AjADZhme{w#c~aM|t>z_EuZfwuJWRKfZ5i*X}zib&{M3=iqGD z|EPPnEHvGef--;Xtp*Ojo(ry2y1lhNy4tlhTL5iJEvOp1)AsZC3r=k=pI<R{%;^2+ z+f!R;Qy+?CKuxIg6;go-b#?i!-Tp||a=?64-Vb~q(!+P~T?c{Jjz(^$%?l4+K4(K< z`r)lGn1B4)73s458&lK8n~fbq+ggWrZ8~9PWRIZc(Uy4`o{uGc^LiFoK4<xvf5>V$ z@Vuk3H!I+`&dV!-->}cdANf6e%oYXQAP>K9@`xRFbSCf7f8~~0KfTK+=ahca<sPF) z_n$wY2cetS6~OD!f5z+KeL-AS?>c7t`TJX2uzJxJaMGu0>*@-vF0Yr|)q@5A9$#04 zz|P~Y1rNAKgn+FXgO*1<z49dgPW9W=MVs1=Ya8e9pQ&y=@M!m)kzFOS1omwJ{}7H( zF)xfHZ!X+p%;fqH&gpulW$?)(;FGiMdY)bNC(o|xdfhxb44#E^TWA#h&N5s_1l>6} zVei(iL-&8Ta^$dn7mHiEUd-QGF?{ICmA`!jPf}0e_b>QxUeJD*hg3s17QT2I1V@hV zPz_4OC8&lE|K*j}e);j$H-FyD&pr9n3(oKQh{AFuVs~wX4;#Pj+DM=yc;P3XKl${h zzz8S@e8xwEd?DC)_}CkcObTK#Hgp|1|ExX3{Vw7QyWY%q_`_>H+Wa&;rr&{4d-gJv z13Z}>$Lwfks=UM9w&E7T^)p=;dL$)NZaMa~N4~vp{(|)fbe=ReH0j)~9J}@$`SD?Q zEIsSWD`6Ey+q^wzTft)2v&Wo+c`xi*!~e#M@Z~UgS>2Ohy?Mi)zJZ#7)v#>>RGeOL zP47Q_{g*Es@x_8Ui>_`yZ1tlX&Nz)++;z+g4_y2eDJP>hkW2b)bK^<ZJTP;wj+nh- z@v&#Gf{#~UKKT3@?>@Wc1{afhhId`bTQv^`xq8&yI0mW$jz@$gLjm9iJLR^`5&G1X zuWp({9*ytZzv5zc*QSYwcAY{B$I+&P=Dzh>*QG8mkAbpp%^O|540q+qW2qfLLk))g z)7fwv%I!+rPRIQHr!KNx*H0hkmhMO!Ht!PL?~(<i^MUKzo<8J4m~(yq(GQ1{Ti0!# z6WqG%sr6SVC|?QceS+8s?L>3MJzclj1J#a`<J)fqe21>A{wA)w4%j_=6gH<x*#|HF z{QkpVw^vmB_@&eJ<-&%BeaDYGYyPS;*Na_N4c>Hl*K=K05&8ZTUu_^kvh#026}Tal z2#5E+boG@tb=SvLd@Rr;-THv>1rp$6HwD<%n=ihNy>`sHU*89hI0fa~MVD_oSDuJF zv@*;+!wq<#*TlO&UX%^Dl(1OV4(hM<fgN<Ohd*C&eAiQ(K6}IS``YWs$WJ#OcE<8k z)-PXm&#`Onx$yEeXI~*U-hDy-rfDbtcGr`y+<NF8b545W(mRe@+Pd(BN#UawI-Q%w zXU^R-aaQxnla9suTm<#7BgoJQ_bXDjCTgJbS__>8Sdpf6d7wWeV4H9~Ri<Z!pinQy z3v}(|$#0$YY}fk_7H+XC%76IAA%h$DZkRQ0+&S}4TfP3VRYTbPM^~S*uItD5p89Ow z?5-!fUgnPl0@p8!hxgq3;?<Ykiug;qhQPLy2SN`FTFQ=n^~}KS_3#5WTac}RuuUiM zK#y<?W2aws>W}BG{{E-0jv7(E^6lrp`{tDomz0<BwO6fKyY94W&ad#Dan=6kCqDa% zA9%*)uu%Nxt{=KyxT2YSH+JJKuRQn6J5NHh^k{7Gf2yG-CPH2OM-T9SE)E$~n0<WT z4kA7Lugk|@-J;+4+o=D!yy8RX7mbA-LALZAp=b2Jmv-GBI!<5ybz#Ma-TkRwkN)4w z`8MduF|O`L4@>uk{y)n2b9ZNntouJow*)&;zqb6(Wz#;ddrAcGu-oq){6Djg{@Cpy zpZnJ(>v5oe<^Ollpg9bK(J10J3-~t)z6-@Tfqud>CQiZc`O3tZr-s0+w|<&?DoM%2 zg=aF^Y~s>0l(w39KaZftn|Qfr6n)IZD?PO=YT^StyRyfiWe^Vi2X|)z6DOdj-Tu6L zor3(mW#Y`Ur{_l#=bo*|0VXay%g9kCE<L-_i6-99)1Mw<;^m%tda8+6dJcqr_%Qzg zo;fTJU&*Fn4mAzm$GUq8Iq<z~4g4h`orG93d|U3G>yg(_rQm7}=&vZ~5ck}aI`}TK z4%#%vd@veP1ADLM;C~y;%Q=vWLOc&SxW8_mX^3UrJpy9swsbMQ!fB~#Nfwhe&AE={ z`E*;VShI6<ml}VauWmg2_{P;t$>rKIPE9nI@5tqo#dIzUFVBD+9mvN$wIvHDzsBM0 zmK=PmmWEp$PIh)OTZp&k7C~|rJl6(mJ2UWP9+FO5DU*bV56V!d|4)K=7;=h&B>Z{4 z<NuhG5B#~hNxtyx=%g(m$M-<|d1iks<bF`dJdlCRsoA1%GoffTx}bfFoC0!-x^l80 zJl${9!nC-%wMjb=yBP9xpJ@d`Gmvu=R8lQ$B;x+nLxc3g=Ov!~oO}T|T7$&QoDK7U z7)|Ah&AIGS_eO27Zc=-4v6Cyd)@IU8{#vWnst@?#?LQ~*Ke+WLESd&I%tO)Udpy%} z`6dWl2`fVFPK13sCcsx!?*CdKrvSvdHQu6G<w~V1RkfN0?eL^0RV;Q)oG_uYv$Hl? zXm+wKPQDprrnWiP4tJ*l2QY&IWZ;T;sVB1<08GKv8W4jHfND0WE#GlP`;YbHa<mRI zPXk^7B)R0G))Z4tO}Hc341u{buBMyO{#tnc&uHuguhsOm0bOp@^%lGUSEj<chbTz= zbP$ZVZhN{Aed*WWwYhxTgbWs~Fkxy#G(LTHe7r6IXd9}vndkSLK@k4SKkrPqNZ=_3 zb65iWEH8|Ql!2Wo_qd}e1E9B34PP`2g0G{8fCo4X*4~eRoe#HyeE~;7zib=W9k2#Q zVMjxMbqDYdcLMvgGw6(6!JdwTGS>nfuI})IASw_@8w9r=1acw>?_|$z@a5?qpzp%4 z!*LY!Qylh4odPrc4RET%K0yC8pnV4HI=e5l$=NVoG8Zal9_)f~0Lak-_+IKDSQXL; zU;ZayRlp^%hV2+wD}0vc6PQsw6;3B|&jPuWFwcpE!)ZmUJ>SAf!Kc8tG{?ibk+<Lk zOLr&pA3WcKwslVdcpTQgT?-Q0>^Tj@W+RBnV{qE#)1D{bRN1HC#LHILG4vVFv+(6X zo97!?gYuH+1vqUs<@p>=q&q=tq{Yx2GazgiLY*!F0dpIA2}EuwNcLx7dscvUT?s2# z4uSm#E`l}ahk1^G6HC4T>vIR}wIX3msUI8}4d-2Zx}fV&K`O}r&+o)Tsz@~%2p>}p zB7?~gGL#I1-5y7fkz^}4e{&Ss+VivL7x+YMTT(-|BcsXoWC!?4c_%W4>`ZncyTU}) zI5OVztLJ6-kaq%n)#roxsQ`Q;V!^p>K~hgbuxr>PGTHN<=Y5!++#R-9-;;z%ghb&> z=QwQiF@@|!8pz&cAJ{qJdNP$vBh$$YG84XyokeDoIb^QqH_s-|2c8efeq<impBzBu zlLh2Jau8Wa8p**VNt#GAX(0|AN7V)&gr?z8n8ltuNrtqOEXjF3@_bA>JeQLtBu@&Y zNJ?ZW=_Jd@a<YP~B!|EjLWhyV$r0p8&ns~9=xd(WJ#Tp4^t|JF)$=xNhkO({njAxp zCC8EDVGD#4$w}m7atb+>tRkn8)5#fRH93>4A!m`Z$y&0GoI}nf>&bcKd~yM~kX%GA zCYO*)$z|knas|1PTt%)X*N_e5ALLr{PjVd`&w2y7k=#UXCby7V$!+9zatFDS+(qst z_mF$ZedK=6x#R)zAbE&9Odf%)UmhcmlYfzo<O%X5d5Szuo*~bY=g9Nq1@a<!iM&i+ zA+M6x$m`?{*zV#j@-}&gyi49A?~@P6hvXyJjpq~cDfx_iPQD;tlCQ|u<QwvD@-6v} zd{2IWeL8+3Ka*d`ujDtfiEJia<ag?Uqm(GjKXEGHV<s={N6X-xm2z4^E9n4QMXTvR zI*1OYL+DUCj1H$G=t$TN=x=ls-I{Jgx1}|7J35+fPj{d@Quv~f?o4-~yV9|A934+< z=>%Fwebi3_R8foCG)U`dh)$%F=w!MZ-JR}1_oQJOp-~!xBghi)aqV8Tf$mNBp;PHJ zI-Sm-GwHr`7M)G!(7AL!I*;y851{ku0(u}lh%Tg!^kAB#O|+S|P=~hCHkzVox`-~O z8QM;>G)Ft=5}Kz4TBIeqly=f(bU9r?SJFf1q4Y3%I6Z<MN&ildqDRwX=&|%TdOSUW zo=8uE!@5qPr_xpQG<rHcgRZ7$(lzufdNy54*U@w6xpX}}kDgC2pcm4M=*9FBdMUk( zUQVx|SJJEK)$|&=f&PPDOaDo)qu0|L=#BIydNaL+-b!zyx6?c5o%AkxH@%16OYfui z(+B8-^db5%eS|(rAES@cf6<Nf3Hl^`iat%Bq0iFi==1ai`XYUazD!@CuhQ4(>+}u! zCVh*(P2ZvK()Z~5^aJ`K{fK@{KcSz}&*<m$3;HGfihfPMq5r1e((mZ^^auJQ{fYie zf1$t9-{>Z~nRe0NnTK_6oF|xMuzf8nWBpk<t6-IE0IOovY#<xN2D2e-C>zFxvk`10 z+lu{-jbdA~ZP>P~1~v^F&9-MdupQY>Yz*6(?ZS3tW7#-1p4GAmtd9AZp9PqL&sA*} zWc4h>CbCIvGTV*q&h}t?vM`IVD2uT;ORy<yFV?{JX8W+IY#N)+X0VxTUp9-)hK=s# zvi;aRSh05_+n*f(``X{?d5Fyi@AqcU!!ZB;0DO&ejpq@zfE@_?`rXS8^4!H1vPO0= zOR^@`%vzY^`H8i%HkM*(wumie8P*QF`CP!VEXO+767X%;!rm_Dz`DS%J(t0Wq3dCN z;40WDaE<47R$xU|VoO;kTgH~N6>KFtgdNHbV~4XN*pck->?n3LJBA(0j$_BO6WEFD zBz7`8g`LV)vD4V;><qS=oypd)v)I{eEnCOVVdt{->^ycpyMSHDE@Bt6OW39CGIlw; zf?dh3Vpp?k*ar3wb}joSyN+GYZeTaEo7m0l7IrJUjor@fV0W^+*xl?Nb}ze+-OnCi z53+~Y!|W0ED0_@O&i=(VvM1P+>?!s%dxkyBo@39m7ubvJCH69Vg}usNW3RI}*qiJv z_BMNmz02NX@3RlshwLNvG5dsl%06SCvoF|}>?`&)`-c6SeapUM-?JaskL)M*Gy8@8 z%6?;;*k;zne&-%eIOU9UF1X}g-Vb(y><^z^R`5zbfLHNqK9CRMgZU6Xln;ZEzY%;S z-wH;@M!_i4Hhf!N!?)w3`Sx(o#EyI?K8Ekicj3FjXw5h{0i%{r;C0-`{XD=Gx3~=> z67@X9C-O;rGT)8w&iCMZ@-UC^C>)^?=LtTA@5LM7<cxjzR6dPQ=QH?BzAvA}XY)CH zF5i#O<NNai_<X*AAIJ~l3wa|ym?wD?Z{{uBf$_68p5ke~h%e?D-p;c;$2<5Ep63N# z<R!k8ck*R?IbXq7@<aHc{4jnvKY}00|IUx%NAqL&vHUoGJU@Y-$WP)Y^Hcb#d=)>9 zpU%(VtNEFH4L^&Y&DZjE{2YERU(e6u=fmL|7xIhv#rzU}DZh+g&adEC@~imO{2IQ2 z|ASx4|H-f8*Yg|rjr=BlGrxu3%5USh^E>#R{4RbszlYz;@8kFL2l#{hA^tFbgg?q3 z<B#)y@s0cm{v>~jKh2-v&+_N^^ZW(=B7cd$%wOTJ^4Iw5{0;sle~Z7(-{J4__xSt# z1O6fZh=0sK;h*x)_~-l!*sbj={x$!G|C@iyzvJKYANY^_C;l`4h5yQb<D2+q-o<|x z9#{cI1ruBdAz}ZfexeMHcr6zdqEZYHRiau96obTISg<uz3=_k}2r*J@CH^KxiLJ#p zVp~xowiBbp_F@OIqu5D|5j%@r#I9nj7$?SyS}_4W*Y^p(2nZ!CVT+)s7a=iGOcImD zZen+_huBkuMMOm5@RYbnh$&(((IEB~`-rJxnwTzTh?!zvF-y!AbHrS+pO`217YB&> zVu3hN93&QsMscu6iYC!4T7)B7MVm;8v{)n-i;QR&S&<VRVu{F$f+&iTSSmWjGO=8& z5G%zY;!tszI9wbdjud|vM~S1wG2&QpoH$;bAWjq~iIc@C;#9FpoF+~eXNc9}OtD6s zCC(OW#X510I9IF}=ZW*h1>!<+k+@h~A}$q|iOa<m;!1IqxLRBT>%smZt`+|j*NN-J z4dO;|lek&jB5oD8iQB~;;!bgwxLe#K?iKfm`^5v|LGh4ySUe&g6_1I>#lOTx@q~C% zJSCnM&xmKmbK-gNf_PE9BwiM;z|QrriPyy&;!W|Ecw4+9-WBhO_r(X|L-CRLSbQQr z6`zUE#TVjB@s;>md?WrXz7^kz@5K*rj>}KtXYq^pRs1G4iOr%*{4PC`zz2j(aw(*Q zVe@{n47N}!mld*74v<x{S`L(h<X|}jwn7^QTUm~fBjr}|Z*r8}T5cn^l{IoZIa+Qn zcaS^Eo#Ys~v)o1QD#yxka=fgS6J(wANxuw8B`s;opsbf6IZ;lMljUx5ce#h$Q-)<k zMrBOKWkODod&vek`*|NZRZf%B<qVi3+gHw#v*jE)SMDe0$^GR4a=u(350nSVg|bl| zER(WHHp>?2$X3}VQ!*_V$;C1w+htbfWQSZL^Rgg|vLu(vPPt4jmn-B-d5Anz9wrZ$ zN5~`P-{n#AXnBl0Rvrgu-k%^(lqboP<tg%1xk{cUPnT!N)$&ZaMxG_lmTToYd5%0+ zu9xS@^W_EdLV1zASY9G8m6yrO<rVTud6m3cUL!Zif5>a)Kjn4udU=DqQQjnPmbb`T z<!$nId564H-X-sr_sDzYee!<!fP7FsBp;TK$VcU4@^SeuxlukLpOjC@r{y#9S^1oN zUcMk-lrPDb<ty@4`I>xPz9HX~Z^^giJMvxmo_t?^AU~8J$&ckH@>BVl{9Jw^zm#9e zujM!L-|}1eo%~+@Ab*rU$)Dvf@>ltr+$1;4F8RCH16yEH*fg7a1<aXvz5Tpp?!J4l z|3jsBfVaw9?Hveb=M44^@eXyrkM@r6j`VKj{hN1`cWdu9-fg`#-tD}jz1w?t@b2i{ z$veiovv(KouHLcUao+LXTJHpJo!95}djnqOwY;`B=&kpLyc4~Xypz4Vd3T4M4fgbg zy%BHJ8}r7!3GWo|Ufu@S%W)s?RPQwJbnguBOz*zlS>D;+QZ`*z2PZpV7%R&yg~1Hy z@;lyed$Kv7%X*U-%5YQOS?b874!q%9TQ2J?_9ihbk2a_C&87C%jI*q~xi`+EEx96Z zh94PgPC|CF1%nvmoh*9e2E;KSaRhO6&_53EC7WSbpxEE(iF)G(+QCr9F-J!S<x_gW z%iDV6e2QC;Hu$NS0)rXtNzAvRtxuwSuiod&Q@wG%S5q?2Q}81jis?*?BVo)90&jyU zecF`10ZX4o7B!f1rZJ=qd(-ry^1XZWEnn0duh^$gK`It+k!Z_1PBxRwwxpZo)MRt1 z=*Wx?DyH^*F4HF=ry^4_I^a`X;xq6gr{nuse1CeMS1Pi75^_4epGBr-lO4H2F`w&5 zIV_%SV@|fsJHrs4GsMq8;&VEvoRKPJ+miWGdnQ>bR_6Mq<SZ;h9?LMR4{QaDHucnv zoP}k`V=x=vD(Ikmb}#G73%&7vbDEu&bS9JRS2Phh2VXDhfX{KOvglUjT&&6xR^?nn zP03I*7pW=fK+Mgjvu&c}{#MTIOG{;`Z^}E@RCx)*@_D@_EAQ+RKcG*1d2d|KM;ce? zp#OZRFeCmedZHqe%eEE7j8rZUc3A(;)xRb8SGQwdqOO0~Eh}b|?1_5Aaec{~baYrg zqmW4!QW($m#w%v`sfG$r2R(J7^Y{D0CXDoha%G%CVG&-4L@`VRbQq{Z#RAI!O*+6+ zx!mINmZnTwKDpFsZf|mPXmT>SPEpKdbA`&5w3BxV>4Hx64`(`3NgXXqW^+X+<D`=n z@s2_oxS%ihix<uP24I%HR57DH?P@Me%<c1V{~7I08$L8J4UcbuB{UX~IK`xxk_3ve z$RXu5czizGVt^;+q#(v!K(SA<qaz8evc0J#NvD<Q^b*}a4Q)f0fX+;_S*e_uoo;JS zvN_3;*OZ3MOr=>A{$>`^2onxfxcPOLsLDLpo$jCPVMK+~hoXK^5M+-_kB$u~;r;Gc z^+loI=S}dqXmg1ZEl#GG^qRc*3b?`Cn=tGOq`8@i#hSE?CJhT2iPCI|E=zlJNFU3m za{UV~ajofWG9z@96@gAu2G#*IHp3qjN#tDSSGZYs7p>|KMXfOTV$GSGI?-RhtfN?z zn31TM-=u9FDkE{qNSvy}`>GBlQq?K*oMpo9_kt!}Z^DoXQPfpk#H3NeRb9-a<0edC z=tGHDJ}jT|8GN6?_ZfVj!S@+_pTYMTe4oKLO-=a>zR%$M48G6c`wYI{;QI}}-{AWV zzTe>c4Zh#t`whO|;QI}}-_Y+j_<n=$H~4;o?>G1XgC8*X0fQef_yL0-F!%w3A29d< zgC8*X0fQef_yL0-F!%w3A29gJ;46c#48Ah>%HS)5uMEC2_{!ibgRcy}GWg2iD}%2L zzB2fh!M6;)W$<B_TGz!=uw?~?2H!IHmch3SzGd(&gKrsp%ivoE-!k}?!M6>*ZSZY_ zZyS8u;M)e@Hu$!|w++5+@NI){8+_Z~+Xmk@__o0h8vLNa4;p;fp;yx#H26V-A2j$u zgC8{bL4zMO_(6joH26V-A2j&EMCCp?P{qwK|CxdLn##p}Q`N;(u2cXIs{3|zF<s2$ zx>I0=Ms_4KFoT*&uh6rjeeR5CNfy${j;sT&0hnWxKfE%+)wAjUasw%?H<Z*HO6m<I z^@fsqLrJ}%q~1_cZ#Wq;oD3OGh72b|hLd2JG)F^*lOe;&kl|#=;D-!;$l!+ze#qd5 z41UPqhYY^y6sWMl4;%cj!4Dh!u)z--{IJ0f8~m`r4;%cj!4Dh!u)z--{IJ1~82Tdy zKVtAB20vo(BL+WW@FNC4V(=pdKVtAB2H*H9Dq`>>20vo(qXs`}@S_GlYVe~5KWgx! z20v=>qXs`}@S_GlYVf0m{;0u^8vLlij~RSp>6Ec_DrWFw20v!-V+KEF@M8u)X7FPM zKW6Y_20v!-V+KEF@Z$zQZs?C2`r`&aZt&v<KW^~j20w1_;|4!&@Z$zQZt&v<KW^~j z20vl&69zwF@QrV&5(YnE@Dm0<Vek_MKVk3_20vl&69zwF@Dm0<f%um3BrM}eSjLmE zj3;3kPXfLY*X<C#_ckHoTXl$U)giuBhxnH9B&<5bw~QxY)giuBXYh?DVHr=tGM<Em zo`f%s<%3Z3#}`LS2w_6b)6TW*#9*HG?`5YB%I0-DUuB)$F}K`4_jpGPO@7$k;kvGG z2sOVU)bv8A>4i|!3!$bLLQOA(nqCMszaiB0La6x-p{5r?O|LJG?HxjckL?}O1|Qoy zqzyi{cSsw2Z10dZ_}JbdZSb+ZL)zeDdxx~a$M)`vV|#~C%Nv9SAKN>m4L-JaNE>`? z?~pe5*xn&+@Ugu^+Tde*hqS@R_U?;gdxy~AV|$0R;Xk%_NE`lRdxx~)Kel&B8~$T^ zhqU28ws%My{$qRh#j(9ZX!wuq9nyw=Z10dZ^kaL6w4oo{JERT$*xn&+=*RXBX+uA@ zcSsxhvAz4^37<Rw1$Vg)P%5#t!(D@ptsT+^9a}r35j}zanJ<A<`eBukn-7GVItVp) zA=K1CsJRQF&L2X}T?lpl5Nhs1sPl(Va~DEQ9W1i4D)qV*J$44&+sY*+=&rgWW_eiC zBHPnhci5)jz<QvTGG|#cbgSU0iWN>iS15Hj`E)K{(V8pedlTuUPIscbkY3guuPi{G z*`Ac+uJ-CqRb<oIo|npc9x8+GS-P%1q<pf&DM0@YSrLx#cqyOL37?_chuX#O!+we1 z=dUYs3Pl*;DmpD?Fyg22QWeD%j7ed<P~HkfHSr2vI+M^j+lKSD;k<1)ZyV0rb*iih zhO-tIiphLgXDVF;yky?Ii`=&j_wBk+B`!;X!HsOL#Tn4z=v7K4X<pKT?zih=19Zmj zs6j^x%058n-jghAN@g-J_>fyRs3+E$E~a3~QS;&fTU=T;w2;gc(=aem1Sc}@bQWNM zp->o9YKQS57;WmzC)02}*HI{kHD66`ttZ=@Dx@ZrYfE>eoo1&B>Fh~kaeeh$b&E;f z?ZZ1Qtr=LOmq|Ivd~uk5CF8UfJ96o45$FUQXW4LlS+Dfl;`%U+_#clC(U(gdTRb~l zU;MK?T5_G)Egm0HSX=^1!G(4yZvu7};C>;OEpLVqK3M(+o&RiA9{TpVWOLCKUPxCq zrE{s|ie$dUO(DJ51NGZ}t1_1dzq}>4OlK)!oB+lXs&pdf^1x-PQ*F|VoIH5&8K=F< z6%SnwUH8R-tw~sZS6JTOl*{B=({QT{3IU^9i;L;16pVF1U1R~jd2z-m?|^|X2cPvW zPiL1Wvu)Ky;6Xmw-kQq;%b{%z=)I7E6?=V>kh?Rm_iho!{$QzEp9eajcB*<G=!DwL z59&>~=StZk5Y^`qw~|wpohb)~z*<{U$$WcqpstwKJk$WxcPkXQtk9l@N`+WOI=i$d zQ4QrO6ql4B3n$;6%Vn!zG_P+O84eCXD~zva9HGIu8^t@=DTW;!QTf@}DTXvEKN~y6 zkVfTaW2YF>sQhf~6hqpyI8?fj)}{qQt=i%1PgK4(c8Vd5%Gb66*d(FF6$*=iSZpym zxugVx-hFQ8Vcf7<b4K58f*Q_s^trG&>uSP2FLWe3v#0=iv)9e0PxifOoqcc8WZ!$i zWZ!$i&0Z8bk}Xb|ZmBs~F<ah}PPT)BcjLS{Q)(*H54cI8AM%<9`nfmV%UBO~eYrch zX=<{(y#&&l%(#~Z^ofJLs0Kq&1ilng?oGJBTcOwL%_fT_&=>-QKHpK*30kTE-BcJl zcAILI147X!Jp$~YYn76jywkkAIRk9R1~CfLn1I$$T;AdMI$V)THy8ffP5)or4E)ti z^;b8ozq)Dv)y?34z1iku!M--TCJdO8DIm{-k-<o>)6$Uy18jKE0gVYB$}ImcH>=>W z_GA{=->6?P870cVk#-wrqpv6C?}-I^VyY)*^~CJKDAF)_mT{-Jv^pNt=UTB76lAe5 z0tHV$rtfw?i<ZEhLdZDqZ1=rFg{#^?7IUs@D+g8A9Ulm@NiHYc`z>1WRk;KBZ~=6e zPFI_B&jZ!nuXI0wW*S7VtqdHQ=EZJoAgbC%(6x=A$qP;f*%Jy3&|>JcG!{B@U<d~0 zb1jXz*2W~LZ!pa1Qej8|q}?erE`kY`LPx&T;$)kRh6)9%!4h;j`Nk%&jaj!@m~2C? z9cXQJb4o$AIE8W;{%&u?YOH{H$Wp$s#c9l>3j=edB1pN@qM74b4^i^lp}77v8VWl< zemm^z4<%_z15}}XkgF*hQ@QqB!C6*<v7zGfa!?H2@k$pr*^vTzz-a-U4(sJX*1#eF zt-e2`{X<)wd>#g&8q+W}(O4+y@qDMOJ=q49G+k;(var(x<%pEQSUvayDW|2XJ(&l= zZF3B916tBcb9oRqq;*g!s~-SRS9n3zjf6+)%3Dj2OlBHOI5Xt8BYqKe=U3!Rknv=J z?_J8~#D1<@BVjrMN>&wdD|8wdlc_McRZMwjJIi2(%qgGkw7aCc@qYW3(%I%xc3I_Y zr?4aqYO4jlDKIsL-77%S{EZ}BE=TM1uTTH_^)I^JHg>Au2}@tK^>0xB*6ZJp{tfHj zi2jZ0U)_m-_cTgWqr@~yOryj!3U;0Rc1*eN#56`sW5hH@Ok>0}MoeSGG)7Eg#A5m_ zjS$laag7ky2yu-N54g|7b^dXke_ZDu*ZIeF{@5Xcr!+!bBg8dATqDFaLR=#xGy)EJ z_-!2WfKa1g7YI@sC!ui?8YiJ~u<HZ&HBLg~;2;NF(?|)8lt|FlR#)5-5%kF+)CL(s zT`dr5haeHhv5-Upvw<)sOTGk5>tW!-z>h%y1BHQwfsH{BgL(`?7=$s1U=YQ?AjC11 z(18!}e2C{mJRjou5YLBrKE(4Oo)7VSi04B*AL98C&xd$E#PcDZAMpYvKv>u}+@(E$ zumOY(AZ!5f0*Dttya3__5HEmu0mKU+9=K+1o(h4Wf2LCiq!36UkU}7Z^eMzsh^G)w zA)Z1!g?I|_6yhnwvk=chJPYwG#Iq33LOcucEX1=A&q6#4@hrr%5YIw93-K((vk}im zJR9+B#Iq64Mm!tw;8SIn6E@=6h-V|7jd(WV*@$N&o{e}x#0w%`5b@xXOgGOU;sp^e zh<HK73nE?+@q&mKM7$v4)nnH6m~}m7U5{DUW7hSUbv<TXk6G7a*7cZmJ!V~xS=XDa zk(7EQr5;HMAzldaLWmbayfEGi<GnE63!8gbmN1qjjAaR9S;APBFqS2ZWeH<h!dR9t zmL-g331e9zh!;V;2;xN$FM@ax#ET$a1o0w>7eTxT;zbZIf_M?ciy&SE@uG+qMZ75D zMG-HGcu@q3B2W~8q6id4pg86d$6Vr=OB{2FV=i$lbsRH`V`g#8ERLDQO=ei?IF>q& zrH*5%<5=p1#)FAySKMG88Uqarv(T8*u&PeOsyYp;>NKpX)3B;e!>T$B3$xGoo`!|F zXH02WRi|MU>PVP(*7s0HD%6n*b)-TasZd8M)R78xq(U93P)EXSGv<PLs3R5XNQF95 zp^j93#PcJ4ex%Qj^!brKKhoz%`rtTpmoI*#&yV!^5zmi!ex%Qj^!brKRK_ZRc&LmO zDr1Gp7|wpuSqBggb+SU8tWYN_)X55UvO=A#P$w(Y$qJRRQb-YMVTD>)p%zxCg%xUH zg<4pl7FMW*6>4FHT3DeLR;Yy)YGH+1SfLhHsD+h<97HXwPzx*6!V0ypLM^ON3oF#Z z3bn98Ev!%rE7ZaYwXi}htWXQXSqhq2sD%}3VTD>)p%#V@`SB5~G1S5ewXi}htWXOp z)WWbmG`@f}hFVym7FMW*Ve>it2x?)4S{OEC#fylCT3DeLR;Yy)YGH+1SOrl+f`}JH zJXFmJRkK3XtWY&8RLu%ivqIIZP&F%5%?eesLe;ELH7iujsvhxBH7iuj3RSa0)vQo8 zD^$%Yge@S15*b4JLP%c-`5D3%5W*G^!WIz177#+b5YiVy`cU1&e5_^`s(Xd%UWE}4 z)xAP>uTb49RQC$iy+U=bP~9t3_X^d$LUpfD-78f03e~+rb+1s}D^&Li)xAP>uTb49 zRQC$iy+U=bP~9t3_b}(GX+U+aP~9t3_X^d$LUpfD-78f0DvJC>t*=n)E7bZ5wZ1~F zucFA$DDpFkcv0kM6!{rL4IM*%#;`raP(EYG&luvxu>N9Le=)4T7}j45>o10QF~o}@ zUJUVKh=+zqp&?Reh!h$kg@#C>AyR0F6dEFhhDf0yQfP=&9P!W)DKtb14UsYy2qsco zO`AZ%5=dAA2}>Yh2_!6mge8!o1X7ehgoLIDW<%Y~U@{Z~ohM9%VoK);6QP*Wd0J@B zU=|cF>a1Z76jM5Dm;uF<W){qUVoEd1LURU_pZX%2Gno3s6yl*dv(TJbXwED&XBL_> z3(c8@=FCEu-$HX{p*geAoLOkjEHq~pnllT{nT6)eLUU%JIkV85S!m8IG-no?GYid` zh33pcb7rAAv(TJbXwED&XK*U1CJfD)h2{*-a>a|tPc&y1nllT{nT6)eLUU%JIkV85 zS!l;B>{M82&fshmohO<e3r&xOrpH3lW1;D>(DYbndMq?O7MdOlO^=19$3oL%q3N;E z^jK(mEHph9njQ;HkA<ekLepcR>9NrCSZI1IG(8r|pk-tbd!JSU1tp;c1*Vr=iePpL z1I<X7Tf&qU6dMK8MnB6&!GtL#d_)VTje=>TpJk(9!UPgNqVa6>vuqSh8wJxw!L(5@ zZS=Ej6igch(?-FxQ7~;3OdAE$M!~dEFl`h}8wJxw!L(5@Z4^u!1=B{sv{5i&CPkMM z1=B{sv{5i^6igch(?-FxQ7~;3OdAE$M!~dEFl`h}*m_E5je=>TVA?2{uuV7K)5^m} z!L(5@Z4^u!1=B{sv{5i^^s{X2``Rd`F!i7_L(d8(9x#P<gq{`5I^adEJYdcNQ&>mn zS;2&ZzKEWHjh=vwo`8*>fQ_Djjh=vwo`8*>fQ_Djjh=vwo`8*>fQ_Djjh=vwo`8*> zfQ_Djjh=vwo`8*>fQ_Djjh=vwo`8*>fQ_C2YzL!Djh=vwo`8*>fQ_C2j8@_!NFRCv zFj%QCq9<UZCt#x|V528sqbFdaCt#x|V528sqbC4EjhGAa6FmVNJpmg%0UJF58$AIS zL)0&zCt#x|V528sqbFdaCt#x|V528sqbFdaCt#x|V527hqlK6!(ubaajh=vwo`8*> z0E`&wC!-e6!hED?sD+8y{tbGi07msP7dKkf(7UqUO;<K_FL=QR^}>HQT5i7Mz{`5Y zKVHUF{BC?e0}Qz1T6{Mt8}@dCN_WY&UX<*n`nf4LQtd|iVwJ;STOIWC+?ysI)bDq% zCe)bK`U{0Vi|woW#39-=uumGNiZQcjU2n$GM0Xrkmelu&hu!$#UXExsxw-barn%@| z8`S%pyI%hfk7>ra*9Pcv_vD1ho=htB-R}4CX3wkD-K4ws-3jY~-4~i*9uKa~jsEe< z)M!7Lb*}Bm!eF_(ZlX3<knRNtVCDmYetJ~`M7-`w3LU~smwO+k&D{HXae^Ds3l!W3 z+yxU5iZ+)!Rein}a?}0WdSs!W`{j^(t-9|AL+%B4hY#49nd%*h+;g8s=#4<!H1*Da aUk+*9Cxp0n9zP-HbHgW}yZoB&@c#i6f{HZ& literal 0 HcmV?d00001 From 00b271577773380e3ab44c4dbf5133bdcf14b135 Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Wed, 21 Apr 2021 14:45:29 +0300 Subject: [PATCH 036/242] seden: Nuke birakmamseni command Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- sedenbot/modules/misc.py | 27 --------------------------- sedenecem/translator/en.json | 3 +-- sedenecem/translator/tr.json | 3 +-- 3 files changed, 2 insertions(+), 31 deletions(-) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index e5cb6b2..d531cf7 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -206,33 +206,6 @@ def base64(message): edit(message, f'Input: `{args[1]}`\nDecoded: `{lething[:-1]}`') -@sedenify(pattern='^.b[ıi]rakmamseni$') -def birakmamseni(message): - '''Copyright (c) @Adem68 | 2020''' - url = 'https://birakmamseni.org/' - path = 'api/counter' - - headers = { - 'User-Agent': 'ajax/7.66.0', - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, br', - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Origin': f'{url}', - 'X-Requested-With': 'XMLHttpRequest', - } - - try: - response = post(url=url + path, headers=headers) - count = response.json()['counter'].lstrip('0') - except Exception as e: - return edit(message, get_translation('banError', ['`', '**', e])) - - sonuc = get_translation('birakmamseniResult', ['**', '`', count]) - - edit(message, sonuc, preview=False) - - @sedenify(pattern='^.ascii$') def img_to_ascii(message): reply = message.reply_to_message diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 62876e8..b6d6770 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -66,7 +66,6 @@ "barcodeInfo": ".barcode <text>\nUsage: Make a barcode from provided content.\nEg: .barcode https://devotag.com\n\nNote: Use .decode command to get decoded content.", "barcodeUsage": "%1Usage:%1 %2.barcode <text>%2", "bioSuccess": "Successfully changed Bio.", - "birakmamseniResult": "%1\u26ab\u26aa Birakmam Seni Data \u26ab\u26aa%1\n\nAs of now, as part of %1BIRAKMAM SENI%1 campaign %2%3%2 \ud83d\udda4\ud83e\udd0d pieces of support were provided.\nCome to, support our %1BE\u015eIKTA\u015e%1 \ud83e\udd85 !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSupports coming through SMS, Money Order / Eft and Postal Check channels are added to meter periodically.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1has been blacklisted for this chat.%1", "blacklistChats": "Blacklists in the Current Chat:", "blacklistCheck": "This person is blacklisted in Seden UserBot!", @@ -313,7 +312,7 @@ "mediaInvalid": "Invalid media.", "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.birakmamseni\nUsage: It shows number of support provided to Be\u015fikta\u015f's Birakmem Seni campaign\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 5bcfbc6..99fba43 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -67,7 +67,6 @@ "barcodeUsage": "%1Kullan\u0131m:%1 %2.barcode <metin>%2", "base64Info": ".base64\nKullan\u0131m: Verilen dizenin base64 kodlamas\u0131n\u0131 bulun. \u00d6rnek .base64 en merhaba", "bioSuccess": "Biyografi ba\u015far\u0131yla de\u011fi\u015ftirildi.", - "birakmamseniResult": "%1\u26ab\u26aa B\u0131rakmam Seni Kampanyas\u0131 Verileri \u26ab\u26aa%1\n\n\u015eu an itibar\u0131yla %1BIRAKMAM SEN\u0130%1 kampanyas\u0131 kapsam\u0131nda %2%3%2 \ud83d\udda4\ud83e\udd0d adet destekte bulunuldu.\nHaydi sen de hemen %1B\u00dcY\u00dcK BE\u015e\u0130KTA\u015e\u2019IMIZA%1 \ud83e\udd85 destek ol !\n\n[https://birakmamseni.org](https://birakmamseni.org/)\n\n%2=============================\nSMS, Havale/Eft ve Posta \u00c7eki kanallar\u0131 ile gelen destekler periyodik olarak sayaca eklenmektedir.\n=============================%2", "blacklistAddSuccess": "%2%3%2 %1bu sohbet i\u00e7in kara listeye al\u0131nd\u0131.%1", "blacklistChats": "Bu grup i\u00e7in ayarlanan karaliste:", "blacklistCheck": "Bu ki\u015finin Seden UserBot kullan\u0131m\u0131 yasaklanm\u0131\u015ft\u0131r!", @@ -315,7 +314,7 @@ "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", - "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.birakmamseni\nKullan\u0131m: Be\u015fikta\u015f'\u0131n B\u0131rakmam Seni kampanyas\u0131na yap\u0131lan destek say\u0131s\u0131n\u0131 g\u00f6stermektedir.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", + "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", "mockUsage": "bANa bIr mETin vEr!", "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "muteProcess": "Sessize al\u0131n\u0131yor...", From b3a323028fc3e15571ed35bbd526aa494f7f3f77 Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Thu, 22 Apr 2021 21:05:31 +0300 Subject: [PATCH 037/242] seden: Nuke Lydia module Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- sedenbot/modules/lydia.py | 107 ----------------------------------- sedenecem/translator/en.json | 9 --- sedenecem/translator/tr.json | 9 --- 3 files changed, 125 deletions(-) delete mode 100644 sedenbot/modules/lydia.py diff --git a/sedenbot/modules/lydia.py b/sedenbot/modules/lydia.py deleted file mode 100644 index 3903c1b..0000000 --- a/sedenbot/modules/lydia.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from time import sleep - -from coffeehouse.api import API -from coffeehouse.lydia import LydiaAI -from sedenbot import HELP, LOGS, LYDIA_APIKEY -from sedenecem.core import edit, get_translation, reply, sedenify - -ACC_LYDIA = {} - -if LYDIA_APIKEY: - api_key = LYDIA_APIKEY - api_client = API(api_key) - lydia = LydiaAI(api_client) - - -@sedenify(pattern='^.repcf') -def repcf(message): - if not LYDIA_APIKEY: - return edit( - message, get_translation('lydiaMissingApi', ['**', '`']), preview=False - ) - edit(message, f'`{get_translation("processing")}`') - try: - session = lydia.create_session() - reply = message.reply_to_message - msg = reply.text - text_rep = session.think_thought(msg) - edit(message, get_translation('lydiaResult', ['**', text_rep])) - except Exception as e: - edit(message, str(e)) - - -@sedenify(pattern='^.addcf') -def addcf(message): - if not LYDIA_APIKEY: - return edit( - message, get_translation('lydiaMissingApi', ['**', '`']), preview=False - ) - edit(message, f'`{get_translation("processing")}`') - sleep(3) - reply_msg = message.reply_to_message - if reply_msg: - session = lydia.create_session() - if not reply_msg.from_user.id: - return edit(message, f'`{get_translation("lydiaError")}`') - ACC_LYDIA.update({(message.chat.id & reply_msg.from_user.id): session}) - edit( - message, - get_translation( - 'lydiaResult2', - ['**', '`', str(reply_msg.from_user.id), str(message.chat.id)], - ), - ) - else: - edit(message, f'`{get_translation("lydiaError2")}`') - - -@sedenify(pattern='^.remcf') -def remcf(message): - if not LYDIA_APIKEY: - return edit( - message, get_translation('lydiaMissingApi', ['**', '`']), preview=False - ) - edit(message, f'`{get_translation("processing")}`') - sleep(3) - reply_msg = message.reply_to_message - try: - del ACC_LYDIA[message.chat.id & reply_msg.from_user.id] - edit( - message, - get_translation( - 'lydiaResult3', - ['**', '`', str(reply_msg.from_user.id), str(message.chat.id)], - ), - ) - except Exception: - edit(message, f'`{get_translation("lydiaError3")}`') - - -@sedenify(incoming=True, outgoing=False, disable_edited=True, disable_notify=True) -def user(message): - try: - session = ACC_LYDIA[message.chat.id & message.from_user.id] - msg = message.text - message.reply_chat_action('typing') - text_rep = session.think_thought(msg) - wait_time = 0 - for i in range(len(text_rep)): - wait_time = wait_time + 0.1 - sleep(wait_time) - reply(message, text_rep) - except BaseException: - pass - - message.continue_propagation() - - -HELP.update({'lydia': get_translation('lydiaInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index b6d6770..7a86049 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -289,15 +289,6 @@ "locksUnlockNoArgs": "I can't unlock void bruh", "locksUnlockSuccess": "%1unlocked %2 for this chat!%1", "logidTest": "This is a test report, it is for LOG_ID check.", - "lydiaError": "Invalid user type.", - "lydiaError2": "Reply to a user to activate Lydia AI on them", - "lydiaError3": "This person doesn't have Lydia activated on him/her.", - "lydiaInfo": ".addcf <username/reply>\nUsage: Add's lydia auto chat request in the chat.\n\n.remcf <username/reply>\nUsage: Remove's lydia auto chat request in the chat.\n\n.repcf <username/reply>\nUsage: Starts lydia repling to perticular person in the chat.", - "lydiaMissingApi": "%1[Lydia](https://coffeehouse.intellivoid.info/dashboard)%1 %2API key missing! Add it to config vars.%2", - "lydiaResult": "%1Hey dude:%1 %2", - "lydiaResult2": "%1Lydia successfully enabled for user:%1 %2%3%2 %1in chat:%1 %2%4%2", - "lydiaResult3": "%1Lydia successfully disabled for user:%1 %2%3%2 %1in chat:%1 %2%3%2", - "lydiaSqlLog": "Unable to run lydia module, no SQL connection found", "lyricsError": "Error: please use '-' as divider for <artist> and <song>\neg: Adele - Hello", "lyricsError2": "Please give the artist and song name", "lyricsInfo": ".lyrics\nUsage: .lyrics <artist> - <song>\nNOTE: '-' separator is important!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 99fba43..93db226 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -291,15 +291,6 @@ "locksUnlockNoArgs": "Hi\u00e7li\u011fin kilidini a\u00e7amam dostum", "locksUnlockSuccess": "%1Bu sohbet i\u00e7in %2 kilidi a\u00e7\u0131ld\u0131!%1", "logidTest": "Bu bir test raporudur, LOG_ID kontrol\u00fc i\u00e7indir.", - "lydiaError": "Ge\u00e7ersiz kullan\u0131c\u0131.", - "lydiaError2": "Lydia AI'y\u0131 etkinle\u015ftirmek i\u00e7in bir kullan\u0131c\u0131y\u0131 yan\u0131tlay\u0131n.", - "lydiaError3": "Bu kullan\u0131c\u0131da Lydia aktif de\u011fil.", - "lydiaInfo": ".addcf <kullan\u0131c\u0131 ad\u0131/yan\u0131tlayarak>\nKullan\u0131m: Lydia'n\u0131n otomatik sohbetini etkinle\u015ftirir.\n\n.remcf <kullan\u0131c\u0131 ad\u0131/yan\u0131tlayarak>\nKullan\u0131m: Lydia'n\u0131n otomatik sohbetini devre d\u0131\u015f\u0131 b\u0131rak\u0131r.\n\n.repcf <kullan\u0131c\u0131 ad\u0131/yan\u0131tlayarak>\nKullan\u0131m: Lydia'n\u0131n otomatik sohbetiini belli bir ki\u015fi i\u00e7in etkinle\u015ftirir.", - "lydiaMissingApi": "%1[Lydia](https://coffeehouse.intellivoid.info/dashboard)%1 %2API key eksik! L\u00fctfen ekleyin.%2", - "lydiaResult": "%1Hey dostum:%1 %2", - "lydiaResult2": "%1Lydia etkinle\u015ftirildi!\nKullan\u0131c\u0131 ID:%1 %2%3%2\n%1Grup ID:%1 %2%4%2", - "lydiaResult3": "%1Lydia devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!\nKullan\u0131c\u0131 ID:%1 %2%3%2 %1Grup ID:%1 %2%3%2", - "lydiaSqlLog": "Lydia mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131.", "lyricsError": "Hata: l\u00fctfen <sanat\u00e7\u0131> ve <\u015fark\u0131> i\u00e7in b\u00f6l\u00fcc\u00fc olarak '-' kullan\u0131n\n\u00d6rnek: Rota - Belki Ba\u015fka Zaman", "lyricsError2": "L\u00fctfen sanat\u00e7\u0131 ve \u015fark\u0131 ismini veriniz", "lyricsInfo": ".lyrics\nKullan\u0131m: .`lyrics <sanat\u00e7\u0131 ad\u0131> - <\u015fark\u0131 ismi>\nNOT: '-' ayrac\u0131 \u00f6nemli!", From f2d09e9dac29cac375e249001999fa6147542a8f Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Mon, 26 Apr 2021 13:00:30 +0300 Subject: [PATCH 038/242] seden: currency: Change method Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- requirements.txt | 1 - sedenbot/modules/scrapers.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 65362f3..dc2d6ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ bs4 coffeehouse cowpy -currencyconverter deethon emoji gitpython diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index f90a323..114ec54 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -16,7 +16,6 @@ from urllib.parse import quote_plus from bs4 import BeautifulSoup -from currency_converter import CurrencyConverter from emoji import get_emoji_regexp from googletrans import LANGUAGES, Translator from gtts import gTTS @@ -521,16 +520,19 @@ def doviz(message): def currency(message): input_str = extract_args(message) input_sgra = input_str.split(' ') - currency = CurrencyConverter() - if len(input_sgra) == 3: try: number = float(input_sgra[0]) currency_from = input_sgra[1].upper() currency_to = input_sgra[2].upper() - convert = currency.convert(number, currency_from, currency_to) - out = round(number * convert, 2) - edit(message, f'**{number} {currency_from} = {out} {currency_to}**') + request_url = f'https://api.ratesapi.io/api/latest?base={currency_from}' + current_response = get(request_url).json() + if currency_to in current_response['rates']: + current_rate = float(current_response['rates'][currency_to]) + rebmun = round(number * current_rate, 2) + edit(message, f'**{number} {currency_from} = {rebmun} {currency_to}**') + else: + edit(message, f'`{get_translation("currencyError")}`') except Exception as e: edit(message, str(e)) else: From 5a97a5176f0f24605abb42a181cb04de9ae0b2ea Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Mon, 26 Apr 2021 14:39:29 +0300 Subject: [PATCH 039/242] seden: Cleanup code * Also pyrogram version updated to 1.2.9 Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- requirements.txt | 3 +-- sample_config.env | 6 +----- sedenbot/__init__.py | 3 --- sedenecem/translator/en.json | 4 ---- sedenecem/translator/tr.json | 4 ---- 5 files changed, 2 insertions(+), 18 deletions(-) diff --git a/requirements.txt b/requirements.txt index dc2d6ab..cf7fd04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ bs4 -coffeehouse cowpy deethon emoji @@ -14,7 +13,7 @@ pillow psycopg2-binary pybase64 pylast -pyrogram==1.2.6 +pyrogram==1.2.9 python-barcode python-dotenv qrcode diff --git a/sample_config.env b/sample_config.env index 3e0adb2..4326a36 100644 --- a/sample_config.env +++ b/sample_config.env @@ -57,8 +57,4 @@ PM_UNAPPROVED='' LASTFM_API='' LASTFM_SECRET='' LASTFM_USERNAME='' # Last.fm Username -LASTFM_PASSWORD='' # Last.fm Password - -# Lydia module -# Get from https://coffeehouse.intellivoid.info/dashboard -LYDIA_APIKEY='' +LASTFM_PASSWORD='' # Last.fm Password \ No newline at end of file diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 331cf61..811ce2b 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -132,9 +132,6 @@ def set_logger(): # Genius module GENIUS_TOKEN = environ.get('GENIUS_TOKEN', None) or environ.get('GENIUS', None) -# Lydia API -LYDIA_APIKEY = environ.get('LYDIA_APIKEY', None) - # Change Alive Message ALIVE_MSG = environ.get('ALIVE_MSG', None) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 7a86049..8ae838c 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -309,8 +309,6 @@ "muteProcess": "Gets a tape!", "muteResult": "%1[%2](tg://user?id=%3)%1 %4muted!%4", "nameOk": "Your name was succesfully changed.", - "nasaResult": "Connecting..", - "nasaResult2": "Signal Lost....", "neofetchNotFound": "Please install neofetch.", "noFilter": "There are no filters in this chat.", "noNote": "No notes found in this chat", @@ -495,7 +493,6 @@ "sedenZeroResults": "Nothing found.", "shutdown": "Goodbye *Windows XP shutdown sound*...", "shutdownLog": "#SHUTDOWN\nBot shut down", - "smallingResult": "Getting smaller...", "snipChats": "Available snips:", "snipError": "Message couldn't be forwarded and snip couldn't be added.", "snipError2": "There was a problem fetching a snip!", @@ -508,7 +505,6 @@ "snipsRemoved": "%2Snip%2 $%1%3%1 %2removed%2", "snipsSqlLog": "Unable to run snips module, no SQL connection found", "snipsUpdated": "%1Snip successfully updated. You can call the snip with .call $%2%1", - "solarResult": "Moon and sun", "spamInfo": ".tspam <text>\nUsage: Spam the text letter by letter\n\n.spam <count> <text>\nUsage: Floods text in the chat\n\n.picspam <count> <url>\nUsage: As if text spam was not enough\n\n.delayspam <delay> <count> <text>\nUsage: .spam but with custom delay\n\n\nNOTE: Spam at your own risk ! !", "spamLog": "#SPAM\nSpam was executed successfully", "spamWrong": "Something seems missing / wrong.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 93db226..74fa45b 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -311,8 +311,6 @@ "muteProcess": "Sessize al\u0131n\u0131yor...", "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4", "nameOk": "Ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", - "nasaResult": "Ba\u011flan\u0131yor..", - "nasaResult2": "Sinyal Kaybedildi....", "neofetchNotFound": "L\u00fctfen neofetch y\u00fckleyin.", "noFilter": "Bu sohbette hi\u00e7 filtre yok.", "noNote": "Bu sohbette kaydedilmi\u015f not bulunamad\u0131", @@ -495,7 +493,6 @@ "sedenZeroResults": "Hi\u00e7bir \u015fey bulunamad\u0131.", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz ...", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", - "smallingResult": "K\u00fc\u00e7\u00fcl\u00fcyor...", "snipChats": "Mevcut snipler:", "snipError": "Mesaj y\u00f6nlendirilemedi ve k\u00fcresel not eklenemedi.", "snipError2": "K\u00fcresel not getirilirken bir sorun olu\u015ftu!", @@ -508,7 +505,6 @@ "snipsRemoved": "%2Genel not%2 $%1%3%1 %2kald\u0131r\u0131ld\u0131%2", "snipsSqlLog": "Snips mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "snipsUpdated": "%1Genel not ba\u015far\u0131yla g\u00fcncellendi. Notu .call $%2 yazarak \u00e7a\u011f\u0131rabilirsiniz%1", - "solarResult": "Ay ve G\u00fcne\u015f", "spamInfo": ".tspam <metin>\nKullan\u0131m: Verilen mesaj\u0131 tek tek g\u00f6ndererek spam yapar\n\n.spam <miktar> <metin>\nKullan\u0131m: Verilen miktarda spam g\u00f6nderir\n\n.picspam <miktar> <link>\nKullan\u0131m: Verilen miktarda resimli spam g\u00f6nderir\n\n.delayspam <gecikme> <miktar> <metin>\nKullan\u0131m: Verilen miktar ve verilen gecikme ile gecikmeli spam yapar\n\n\nNOT: Sorumluluk size aittir !", "spamLog": "#SPAM\nSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "spamWrong": "Bir \u015feyler eksik/yanl\u0131\u015f gibi g\u00f6r\u00fcn\u00fcyor.", From f60e3522fc69349da2709fc2a52b6749a3e1fffd Mon Sep 17 00:00:00 2001 From: NaytSeyd <NaytSeyd@yandex.com> Date: Mon, 26 Apr 2021 14:43:51 +0300 Subject: [PATCH 040/242] seden: Move admin helper Signed-off-by: NaytSeyd <NaytSeyd@yandex.com> --- sedenbot/modules/admin/__init__.py | 10 ---------- sedenbot/modules/admin/helpers.py | 20 -------------------- sedenbot/modules/ban.py | 2 +- sedenecem/core/misc.py | 9 +++++++++ sedenecem/core/sedenify.py | 15 +++------------ 5 files changed, 13 insertions(+), 43 deletions(-) delete mode 100644 sedenbot/modules/admin/__init__.py delete mode 100644 sedenbot/modules/admin/helpers.py diff --git a/sedenbot/modules/admin/__init__.py b/sedenbot/modules/admin/__init__.py deleted file mode 100644 index d3d44d8..0000000 --- a/sedenbot/modules/admin/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from .helpers import * diff --git a/sedenbot/modules/admin/helpers.py b/sedenbot/modules/admin/helpers.py deleted file mode 100644 index 8f27feb..0000000 --- a/sedenbot/modules/admin/helpers.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from sedenbot import app - -_admin_status_list = ['creator', 'administrator'] - - -def is_admin(message): - if not 'group' in message.chat.type: - return True - - user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) - return user.status in _admin_status_list diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 96aab30..3770748 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -12,7 +12,6 @@ from pyrogram.errors import MessageTooLong, UserAdminInvalid from pyrogram.types import ChatPermissions from sedenbot import BRAIN, HELP -from sedenbot.modules.admin.helpers import is_admin from sedenecem.core import ( edit, extract_args, @@ -20,6 +19,7 @@ reply_doc, sedenify, send_log, + is_admin ) from sedenecem.sql import mute_sql as sql diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index dd7dbba..2cafed6 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -15,6 +15,7 @@ MARKDOWN_FIX_CHAR = '\u2064' SPAM_COUNT = [0] _parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r'\.' +_admin_status_list = ['creator', 'administrator'] def reply( @@ -154,3 +155,11 @@ def parse_cmd(text): cmd = cmd.split()[0] cmd = cmd.split(_parsed_prefix)[-1] if BOT_PREFIX else cmd[1:] return cmd + + +def is_admin(message): + if not 'group' in message.chat.type: + return True + + user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) + return user.status in _admin_status_list \ No newline at end of file diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index aef9d5f..557b596 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -15,18 +15,9 @@ from pyrogram import ContinuePropagation, StopPropagation, filters from pyrogram.handlers import MessageHandler -from sedenbot import ( - BLACKLIST, - BOT_VERSION, - BRAIN, - SUPPORT_GROUP, - TEMP_SETTINGS, - app, - get_translation, -) -from sedenbot.modules.admin import is_admin - -from .misc import _parsed_prefix, edit, get_cmd +from sedenbot import BLACKLIST, BOT_VERSION, BRAIN, TEMP_SETTINGS, app, get_translation + +from .misc import _parsed_prefix, edit, get_cmd, is_admin from .sedenlog import send_log_doc From b47190af1faf3e3bcec68f3fc76e1de358b49ba9 Mon Sep 17 00:00:00 2001 From: musfay <33374965+musfay@users.noreply.github.com> Date: Wed, 28 Apr 2021 00:19:52 +0300 Subject: [PATCH 041/242] Add support for Nix and NixOS (#4) * add shell.nix * launch bot if config.env exists in shell.nix --- shell.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 shell.nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..27004dd --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +with import <nixpkgs> {}; + +stdenv.mkDerivation { + name = "sedenbot-environment"; + buildInputs = [ + pkgs.python3 + pkgs.python3.pkgs.setuptools + pkgs.python3.pkgs.pip + ]; + shellHook = '' + export PIP_PREFIX="$(pwd)/_build/pip_packages" + export PYTHONPATH="$PIP_PREFIX/${pkgs.python3.sitePackages}:$PYTHONPATH" + export PATH="$PIP_PREFIX/bin:$PATH" + export PIP="${pkgs.python3.pkgs.pip.outPath}/bin/pip" + export PYTHON="${pkgs.python3.outPath}/bin/python" + unset SOURCE_DATE_EPOCH + + $PIP install -r requirements.txt + + if [ -f "config.env" ]; then + $PYTHON seden.py + fi + ''; +} From 206f204616a4111da60d8ea939d160f2fdc1edca Mon Sep 17 00:00:00 2001 From: musfay <33374965+musfay@users.noreply.github.com> Date: Wed, 28 Apr 2021 13:28:17 +0300 Subject: [PATCH 042/242] Improve shell.nix and add instructions to README.md (#5) * shell.nix: update seden before bootstrap * shell.nix: implement first run * Add nix instructions to README.md * shell.nix: be verbose * shell.nix: wait user input to open config.env file --- README.md | 2 ++ shell.nix | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6b9d3e..2f24aea 100755 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ mv sample_config.env config.env # Run bot python3 seden.py ``` +### Nix/NixOS +Just type `nix-shell` command in bot folder. ## Heroku Deploy [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TeamDerUntergang/Telegram-SedenUserBot/tree/seden) diff --git a/shell.nix b/shell.nix index 27004dd..4aa8aff 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,8 @@ with import <nixpkgs> {}; stdenv.mkDerivation { name = "sedenbot-environment"; buildInputs = [ + pkgs.git + pkgs.nano pkgs.python3 pkgs.python3.pkgs.setuptools pkgs.python3.pkgs.pip @@ -11,14 +13,26 @@ stdenv.mkDerivation { export PIP_PREFIX="$(pwd)/_build/pip_packages" export PYTHONPATH="$PIP_PREFIX/${pkgs.python3.sitePackages}:$PYTHONPATH" export PATH="$PIP_PREFIX/bin:$PATH" + # use nix binaries because we don't want to invoke host configs + export GIT="${pkgs.git.outPath}/bin/git" export PIP="${pkgs.python3.pkgs.pip.outPath}/bin/pip" export PYTHON="${pkgs.python3.outPath}/bin/python" unset SOURCE_DATE_EPOCH - + + # update bot + $GIT pull && $GIT checkout seden + echo "Fetching dependencies..." $PIP install -r requirements.txt - if [ -f "config.env" ]; then + if [ -f "config.env" -a -f "sedenuserbot.session" ]; then $PYTHON seden.py + else + # first run + $PYTHON session.py + mv sample_config.env config.env + echo "Press enter to edit config.env file..." && read >> /dev/null + nano config.env + echo "Type "nix-shell" to start SedenUserBot!" fi ''; } From 03755779b1dc29cd0ed9e7deff72c7d4e3dddf40 Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Sun, 2 May 2021 18:21:35 +0300 Subject: [PATCH 043/242] seden: Fix regex Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenecem/core/sedenify.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 557b596..95b562e 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -38,6 +38,9 @@ def sedenify(**args): if pattern and '.' in pattern[:2]: args['pattern'] = pattern = pattern.replace('.', _parsed_prefix, 1) + if pattern and pattern[-1:] != '$': + args['pattern'] = pattern = f'{pattern}(?: |$)' + def msg_decorator(func): def wrap(client, message): if message.empty or not message.from_user: From 530059bdf0449f0042d04ee2ac5e69e6a8bd66fd Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Fri, 7 May 2021 23:12:01 +0300 Subject: [PATCH 044/242] some optimizations and add .stopall --- sedenbot/modules/filters.py | 14 +++++++ sedenbot/modules/globals.py | 77 +++++++++++++++++++++++++++++++------ sedenbot/modules/system.py | 15 +++++--- 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index 95af984..19ca963 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -129,6 +129,20 @@ def stop_filter(message): edit(message, get_translation('filterRemoved', ['**', '`', filt])) +@sedenify(pattern='^.stopall$') +def stop_filter_all(message): + try: + from sedenecem.sql.filters_sql import get_filters, remove_filter + except BaseException: + edit(message, f'`{get_translation("nonSqlMode")}`') + return + filters = get_filters(message.chat.id) + for filt in filters: + remove_filter(message.chat.id, filt.keyword) + filtwords = [i.keyword for i in filters] + edit(message, get_translation('filterRemoved', ['**', '`', ', '.join(filtwords)])) + + @sedenify(pattern='^.filters$') def filters(message): try: diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index b1a716c..e2241ca 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -10,7 +10,7 @@ from time import sleep from pyrogram.types import ChatPermissions -from sedenbot import BRAIN, HELP, LOGS +from sedenbot import BRAIN, HELP, LOGS, TEMP_SETTINGS from sedenecem.core import edit, extract_args, get_translation, sedenify, send_log @@ -59,7 +59,8 @@ def gban_user(client, message): if user.id in BRAIN: return edit( message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), + get_translation( + 'brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -68,8 +69,15 @@ def gban_user(client, message): sql.gban(user.id) edit( message, - get_translation('gbanResult', ['**', user.first_name, user.id, '`']), + get_translation( + 'gbanResult', ['**', user.first_name, user.id, '`']), ) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.kick_member(user.id) + except BaseException: + pass sleep(1) send_log(get_translation('gbanLog', [user.first_name, user.id])) except Exception as e: @@ -77,7 +85,7 @@ def gban_user(client, message): return -@sedenify(pattern='^.ungban', compat=False) +@sedenify(pattern='^.(ung|gun)ban', compat=False) def ungban_user(client, message): args = extract_args(message) reply = message.reply_to_message @@ -105,12 +113,42 @@ def ungban_user(client, message): try: if not sql.is_gbanned(user.id): return edit(message, f'`{get_translation("alreadyUnbanned")}`') - chat_id = message.chat.id sql.ungban(user.id) - client.unban_chat_member(chat_id, user.id) + + def find_me(): + try: + return dialog.chat.get_member(me_id).can_restrict_members + except BaseException: + return False + + def find_member(): + try: + return (dialog.chat.get_member(user.id) + and dialog.chat.get_member(user.id).restricted_by + and dialog.chat.get_member(user.id).restricted_by.id == me_id) + except BaseException: + return False + + try: + dialogs = client.iter_dialogs() + me_id = TEMP_SETTINGS['ME'].id + chats = [ + dialog.chat + for dialog in dialogs + if ( + 'group' in dialog.chat.type + and find_me() + and find_member() + ) + ] + for chat in chats: + chat.unban_member(user.id) + except BaseException: + pass edit( message, - get_translation('unbanResult', ['**', user.first_name, user.id, '`']), + get_translation( + 'unbanResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) @@ -137,8 +175,8 @@ def gban_check(client, message): user_id = message.from_user.id chat_id = message.chat.id client.kick_chat_member(chat_id, user_id) - except BaseException as e: - send_log(get_translation('banError', ['`', '**', e])) + except BaseException: + pass message.continue_propagation() @@ -171,7 +209,8 @@ def gmute_user(client, message): if user.id in BRAIN: return edit( message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), + get_translation( + 'brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -180,8 +219,15 @@ def gmute_user(client, message): sql2.gmute(user.id) edit( message, - get_translation('gmuteResult', ['**', user.first_name, user.id, '`']), + get_translation( + 'gmuteResult', ['**', user.first_name, user.id, '`']), ) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.restrict_member(user.id, ChatPermissions()) + except BaseException: + pass sleep(1) send_log(get_translation('gmuteLog', [user.first_name, user.id])) except Exception as e: @@ -218,9 +264,16 @@ def ungmute_user(client, message): if not sql2.is_gmuted(user.id): return edit(message, f'`{get_translation("alreadyUnmuted")}`') sql2.ungmute(user.id) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.unban_member(user.id) + except BaseException: + pass edit( message, - get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), + get_translation('unmuteResult', [ + '**', user.first_name, user.id, '`']), ) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index a99a67d..0a57591 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -93,7 +93,8 @@ def pip3(message): file.close() reply_doc(message, 'pip3.txt', delete_after_send=True) return - edit(message, get_translation('sedenQuery', ['**', '`', pipsorgu, sonuc])) + edit(message, get_translation( + 'sedenQuery', ['**', '`', pipsorgu, sonuc])) else: edit( message, @@ -186,7 +187,8 @@ def terminal(message): ) return - edit(message, f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{sonuc}`') + edit( + message, f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{sonuc}`') send_log(get_translation('termLog', [command])) @@ -215,17 +217,20 @@ def eval(message): return edit( message, - get_translation('sedenQuery', ['**', '`', args, evaluation]), + get_translation( + 'sedenQuery', ['**', '`', args, evaluation]), ) else: edit( message, get_translation( - 'sedenQuery', ['**', '`', args, get_translation('sedenErrorResult')] + 'sedenQuery', ['**', '`', args, + get_translation('sedenErrorResult')] ), ) except Exception as err: - edit(message, get_translation('sedenQuery', ['**', '`', args, str(err)])) + edit(message, get_translation( + 'sedenQuery', ['**', '`', args, str(err)])) send_log(get_translation('evalLog', [args])) From b1c3e506c272f18eef40f51ab86b1cf6fbf183db Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Wed, 12 May 2021 16:31:47 +0300 Subject: [PATCH 045/242] seden: Move amogus to memes module Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/amogus.py | 60 ------------------------------------ sedenbot/modules/memes.py | 52 ++++++++++++++++++++++++++++++- sedenecem/translator/en.json | 3 +- sedenecem/translator/tr.json | 3 +- 4 files changed, 53 insertions(+), 65 deletions(-) delete mode 100644 sedenbot/modules/amogus.py diff --git a/sedenbot/modules/amogus.py b/sedenbot/modules/amogus.py deleted file mode 100644 index f999e78..0000000 --- a/sedenbot/modules/amogus.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from io import BytesIO -from random import randint -from textwrap import wrap - -from PIL import Image, ImageDraw, ImageFont -from requests import get -from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, sedenify, send_sticker - - -@sedenify(pattern='^.(amogu|su)s', compat=False) -def amogus(client, message): - args = extract_args(message) - if len(args) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - - edit(message, f"`{get_translation('processing')}`") - - arr = randint(1, 12) - fontsize = 100 - FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' - url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' # Thanks - font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) - - imposter = Image.open(BytesIO(get(f'{url}{arr}.png').content)) - text_ = '\n'.join(['\n'.join(wrap(part, 30)) for part in args.split('\n')]) - w, h = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textsize( - text_, font, stroke_width=2 - ) - text = Image.new('RGBA', (w + 40, h + 40)) - ImageDraw.Draw(text).multiline_text( - (15, 15), text_, '#FFF', font, stroke_width=2, stroke_fill='#000' - ) - w = imposter.width + text.width + 30 - h = max(imposter.height, text.height) - image = Image.new('RGBA', (w, h)) - image.paste(imposter, (0, h - imposter.height), imposter) - image.paste(text, (w - text.width, 0), text) - image.thumbnail((512, 512)) - - output = BytesIO() - output.name = 'sus.webp' - image.save(output, 'WebP') - output.seek(0) - - send_sticker(client, message.chat, output) - message.delete() - - -HELP.update({'amogus': get_translation('amogusInfo')}) \ No newline at end of file diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index e42a643..991ac3f 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -7,14 +7,24 @@ # All rights reserved. See COPYING, AUTHORS. # +from io import BytesIO from random import choice, getrandbits, randint from re import sub +from textwrap import wrap from time import sleep from cowpy import cow +from PIL import Image, ImageDraw, ImageFont from requests import get from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, parse_cmd, sedenify +from sedenecem.core import ( + edit, + extract_args, + get_translation, + parse_cmd, + sedenify, + send_sticker, +) # ================= CONSTANT ================= ZALGS = [ @@ -777,6 +787,46 @@ def h(message): ) +@sedenify(pattern='^.(amogu|su)s', compat=False) +def amogus(client, message): + args = extract_args(message) + if len(args) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + + edit(message, f"`{get_translation('processing')}`") + + arr = randint(1, 12) + fontsize = 100 + FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' + url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' # Thanks + font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) + + imposter = Image.open(BytesIO(get(f'{url}{arr}.png').content)) + text_ = '\n'.join(['\n'.join(wrap(part, 30)) for part in args.split('\n')]) + w, h = ImageDraw.Draw(Image.new('RGB', (1, 1))).multiline_textsize( + text_, font, stroke_width=2 + ) + text = Image.new('RGBA', (w + 40, h + 40)) + ImageDraw.Draw(text).multiline_text( + (15, 15), text_, '#FFF', font, stroke_width=2, stroke_fill='#000' + ) + w = imposter.width + text.width + 30 + h = max(imposter.height, text.height) + image = Image.new('RGBA', (w, h)) + image.paste(imposter, (0, h - imposter.height), imposter) + image.paste(text, (w - text.width, 0), text) + image.thumbnail((512, 512)) + + output = BytesIO() + output.name = 'sus.webp' + image.save(output, 'WebP') + output.seek(0) + + send_sticker(client, message.chat, output) + message.delete() + + @sedenify(pattern='^.gay') def gay_calculator(message): args = extract_args(message) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 8ae838c..da5651e 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -41,7 +41,6 @@ "alwaysOnline2": "Always online mode is already enabled!", "alwaysOnlineOff": "Always online mode disabled!", "alwaysOnlineOff2": "Always online mode is already disabled!", - "amogusInfo": ".amogus\nUsage: Converts typed text to amogus", "androidInfo": ".magisk\nGet latest Magisk releases\n\n.device <codename>\nUsage: Get info about android device codename or model.\n\n.codename <brand> <device>\nUsage: Search for android device codename.\n\n.specs <brand> <device>\nUsage: Get device specifications info.\n\n.twrp <codename>\nUsage: Get latest twrp download for android device.\n\n.orangefox <codename>\nUsage: Get latest OrangeFox download for android device.\n\n.phh <ABI>\nGet latest Phh releases", "androidPhhHeader": "%1Latest Phh %2AOSP Releases%1", "answerFromBot": "I didn't get an answer from bot!", @@ -301,7 +300,7 @@ "makeqrInfo": ".makeqr <text>\nUsage: Make a QR code from provided content.\nEg: .makeqr https://devotag.com\n\nNote: Use .decode command to get decoded content.", "makeqrUsage": "%1Usage:%1 %2.makeqr <text>%2", "mediaInvalid": "Invalid media.", - "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.", + "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", "mirrorError": "Error: Different mirror not found for the link", "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 74fa45b..0ef98e3 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -41,7 +41,6 @@ "alwaysOnline2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten etkinle\u015ftirildi!", "alwaysOnlineOff": "S\u00fcrekli \u00e7evrimi\u00e7i modu devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", "alwaysOnlineOff2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", - "amogusInfo": ".amogus\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", "androidInfo": ".magisk\nG\u00fcncel Magisk s\u00fcr\u00fcmleri\n\n.device <kod ad\u0131>\nKullan\u0131m: Android cihaz\u0131 hakk\u0131nda bilgi\n\n.codename <marka> <cihaz>\nKullan\u0131m: Android cihaz kod adlar\u0131n\u0131 aray\u0131n.\n\nspecs <marka> <cihaz>\nKullan\u0131m: Cihaz \u00f6zellikleri hakk\u0131nda bilgi al\u0131n.\n\n.twrp <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel TWRP s\u00fcr\u00fcmlerini al\u0131n.\n\n.orangefox <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel OrangeFox Recovery s\u00fcr\u00fcmlerini al\u0131n.\n\n.phh <mimari>\nG\u00fcncel Phh AOSP s\u00fcr\u00fcmlerini al\u0131n.", "androidPhhHeader": "%1G\u00fcncel PHH %2AOSP S\u00fcr\u00fcmleri%1", "answerFromBot": "Botdan cevap alamad\u0131m!", @@ -303,7 +302,7 @@ "makeqrInfo": ".makeqr <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir QR kodu yap\u0131n.\n\u00d6rnek: .makeqr https://devotag.com\n\nNot: \u00c7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", "makeqrUsage": "%1Kullan\u0131m:%1 %2.makeqr <metin>%2", "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", - "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.", + "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.\n\n.amogus <metin>\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", "mockUsage": "bANa bIr mETin vEr!", From e36ff7d551044f096a9dfabba81b49dc4a098cb0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Wed, 12 May 2021 16:32:44 +0300 Subject: [PATCH 046/242] seden: lyrics: Fix bug Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/lyrics.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/lyrics.py index e4de102..6dfc371 100644 --- a/sedenbot/modules/lyrics.py +++ b/sedenbot/modules/lyrics.py @@ -15,14 +15,13 @@ @sedenify(pattern='^.lyrics') def lyrics(message): args = extract_args(message) - if r"-" in args: + if r'-' in args: pass else: - edit(message, f'`{get_translation("lyricsError")}`') - return + return edit(message, f'`{get_translation("lyricsError")}`') if not GENIUS_TOKEN: - edit(message, f'`{get_translation("geniusToken")}`') + return edit(message, f'`{get_translation("geniusToken")}`') else: genius = Genius(GENIUS_TOKEN) try: From 7ec8a5e6267fa5c3bc4cf4c2b520114d5f159f7e Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Wed, 12 May 2021 16:36:27 +0300 Subject: [PATCH 047/242] seden: Fix stickers module Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/__init__.py | 2 +- sedenbot/modules/stickers.py | 68 ++++++++++++++++++++++-------------- sedenecem/core/misc.py | 4 +-- sedenecem/core/sedenify.py | 2 +- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 811ce2b..413ea8f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -87,7 +87,7 @@ def set_local_env(key: str, value: str): def unset_local_env(key: str): if key in environ: del environ[key] - return unset_key('config.env', key) + return unset_key(PurePath('config.env'), key) def set_logger(): diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 08fdd0d..414e5c2 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -23,6 +23,7 @@ sedenify, ) from sedenecem.core import sticker_resize as resizer +from time import time # ================= CONSTANT ================= DIZCILIK = [get_translation(f'kangstr{i+1}') for i in range(0, 12)] @@ -67,35 +68,47 @@ def kang(client, message): emoji = item.replace('e=', '') args = args.replace(item, '').strip() - pname = ( + ptime = time() + pname = f'PNAME_{ptime}' + pnick = f'PNICK_{ptime}' + + if anim: + pname += '_anim' + pnick += ' (Animated)' + + TEMP_SETTINGS[pname] = ( PACKNAME.replace(' ', '') if PACKNAME else f'a{myacc.id}_by_{myacc.username}_{pack}' ) - pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" + TEMP_SETTINGS[f'{pname}_TEMPLATE'] = f'a{myacc.id}_by_{myacc.username}_' + TEMP_SETTINGS[pnick] = PACKNICK or f'{kanger}\'s UserBot pack {pack}' + TEMP_SETTINGS[f'{pnick}_TEMPLATE'] = f'{kanger}\'s UserBot pack ' limit = '50' if anim else '120' def pack_created(name): try: - set_name = InputStickerSetShortName(short_name=name) + set_name = InputStickerSetShortName(short_name=TEMP_SETTINGS[pname]) set = GetStickerSet(stickerset=set_name) client.send(data=set) return True except BaseException as e: return False - def create_new(conv, pname, pnick): + def create_new(conv, pack, pname, pnick): cmd = f'/new{"animated" if anim else "pack"}' try: send_recv(conv, cmd) except Exception as e: raise e - msg = send_recv(conv, pnick) + msg = send_recv(conv, TEMP_SETTINGS[pnick]) if msg.text == 'Invalid pack selected.': pack += 1 - return create_new(conv) + TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" + TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" + return create_new(conv, pack, pname, pnick) msg = send_recv(conv, media, doc=True) if 'Sorry, the file type is invalid.' in msg.text: edit(message, f'`{get_translation("stickerError")}`') @@ -103,9 +116,15 @@ def create_new(conv, pname, pnick): send_recv(conv, emoji) send_recv(conv, '/publish') if anim: - send_recv(conv, f'<{pnick}>') + send_recv(conv, f'<{TEMP_SETTINGS[pnick]}>') send_recv(conv, '/skip') - send_recv(conv, pname) + ret = send_recv(conv, TEMP_SETTINGS[pname]) + while "already taken" in ret.text: + pack += 1 + TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" + TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" + ret = send_recv(conv, TEMP_SETTINGS[pname]) + return True def add_exist(conv, pack, pname, pnick): try: @@ -113,33 +132,25 @@ def add_exist(conv, pack, pname, pnick): except Exception as e: raise e - status = send_recv(conv, pname) + status = send_recv(conv, TEMP_SETTINGS[pname]) if limit in status.text: pack += 1 - pname = ( - PACKNAME.replace(' ', '') - if PACKNAME - else f'a{myacc.id}_by_{myacc.username}_{pack}' - ) - pnick = PACKNICK or f"{kanger}'s UserBot pack {pack}" + TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" + TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" edit(message, get_translation('packFull', ['`', '**', str(pack)])) if pack_created(pname): return add_exist(conv, pack, pname, pnick) else: - return create_new(conv, pname, pnick) + return create_new(conv, pack, pname, pnick) send_recv(conv, media, doc=True) send_recv(conv, emoji) send_recv(conv, '/done') return True - if anim: - pname += '_anim' - pnick += ' (Animated)' - else: - if not reply.sticker: - media = resizer(media) + if not (anim and reply.sticker): + media = resizer(media) with PyroConversation(client, 'Stickers') as conv: send_recv(conv, '/cancel') @@ -148,9 +159,13 @@ def add_exist(conv, pack, pname, pnick): if not ret: return else: - create_new(conv, pname, pnick) + create_new(conv, pack, pname, pnick) - edit(message, get_translation('stickerAdded', ['`', pname])) + edit(message, get_translation('stickerAdded', ['`', TEMP_SETTINGS[pname]])) + del TEMP_SETTINGS[pname] + del TEMP_SETTINGS[pnick] + del TEMP_SETTINGS[f'{pname}_TEMPLATE'] + del TEMP_SETTINGS[f'{pnick}_TEMPLATE'] def send_recv(conv, msg, doc=False): @@ -171,13 +186,14 @@ def getsticker(message): photo = download_media_wc(reply) reply_doc( - message, + reply, photo, caption=f'**Sticker ID:** `{reply.sticker.file_id}' f'`\n**Emoji**: `{reply.sticker.emoji}`', delete_after_send=True, - delete_orig=True, + delete_orig=False, ) + message.delete() @sedenify(pattern='.packinfo$', compat=False) diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 2cafed6..5f2c8a8 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -96,7 +96,7 @@ def download_media(client, data, file_name=None, progress=None, sticker_orig=Tru elif data.video_note: file_name = f'{data.video_note.file_id}.mp4' elif data.sticker: - file_name = f'sticker.{("tgs" if sticker_orig else "TGS") if data.sticker.is_animated else ("webp" if sticker_orig else "png")}' + file_name = f'sticker.{("tgs" if sticker_orig else "json.gz") if data.sticker.is_animated else ("webp" if sticker_orig else "png")}' else: return None @@ -162,4 +162,4 @@ def is_admin(message): return True user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) - return user.status in _admin_status_list \ No newline at end of file + return user.status in _admin_status_list diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 95b562e..81af5c5 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -47,7 +47,7 @@ def wrap(client, message): return try: - if not TEMP_SETTINGS.get('ME', None): + if 'ME' not in TEMP_SETTINGS: me = app.get_me() TEMP_SETTINGS['ME'] = me From f93ab23204397eddf37c8b98fbc994bbb3794bc4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Wed, 12 May 2021 17:15:57 +0300 Subject: [PATCH 048/242] seden: ytdl: Improve video upload Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/stickers.py | 1 - sedenbot/modules/youtubedl.py | 12 ++++++------ sedenecem/core/replier.py | 15 +++++++++++++++ sedenecem/translator/en.json | 4 ++-- sedenecem/translator/tr.json | 4 ++-- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 414e5c2..023c422 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -191,7 +191,6 @@ def getsticker(message): caption=f'**Sticker ID:** `{reply.sticker.file_id}' f'`\n**Emoji**: `{reply.sticker.emoji}`', delete_after_send=True, - delete_orig=False, ) message.delete() diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 6933063..2e79ea9 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -16,7 +16,7 @@ extract_args, get_translation, reply_audio, - reply_doc, + reply_video, sedenify, ) from youtube_dl import YoutubeDL @@ -50,13 +50,13 @@ def youtubedl(message): } with YoutubeDL(ydl_opts) as ydl: ydl.download([url]) - edit(message, f'{get_translation("uploadMedia")}') - reply_doc( + edit(message, f'`{get_translation("uploadMedia")}`') + reply_video( message, f'{title}.mp4', - caption=f"{get_translation('title', ['**' , ':'])} {title}`\n`{get_translation('uploader',['**',':'])} {uploader}", - delete_after_send=True, + caption=f"**{get_translation('videoTitle')}** `{title}`\n**{get_translation('videoUploader')}** `{uploader}`", delete_orig=True, + delete_file=True, ) elif util == 'mp3': @@ -78,7 +78,7 @@ def youtubedl(message): reply_audio( message, f'{title}.mp3', - caption=f"{get_translation('uploader',['**',':'])} {uploader}", + caption=f"**{get_translation('videoUploader')}** `{uploader}`", delete_orig=True, ) remove(f'{title}.mp3') diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 06037a4..2e636b7 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -45,6 +45,21 @@ def reply_audio(message, audio, caption='', fix_markdown=False, delete_orig=Fals pass +def reply_video( + message, video, caption='', fix_markdown=False, delete_orig=False, delete_file=False +): + try: + if len(caption) > 0 and fix_markdown: + caption += MARKDOWN_FIX_CHAR + message.reply_video(video, caption=caption.strip()) + if delete_orig: + message.delete() + if delete_file: + remove(video) + except BaseException: + pass + + def reply_voice(message, voice, caption='', fix_markdown=False, delete_orig=False): try: if len(caption) > 0 and fix_markdown: diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index da5651e..207c3aa 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -541,7 +541,6 @@ "termUsage": "Please write a command.", "testException": "This is a test, don't submit a bug log.", "testLogId": "Testing LOG_ID value ...", - "title": "%1Title%2%1", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", "trtError": "Invalid destination language.", "trtInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)", @@ -595,13 +594,14 @@ "uploadInfo": ".download reply to media \nUsage: Downloads file to server.\n\n.upload <path in server>\nUsage: Uploads a locally stored file to chat.", "uploadMedia": "Uploading media...", "uploadReply": "I cant upload nothing here.", - "uploader": "%1Uploader%2%1", "urlError": "Error: Unable to extract link", "useridResult": "%1Username:%1 %2\n%1User ID:%1 %3%4%3", "userlist": "%1%2 Users in%1 %3%4%3%1:%1", "usernameSuccess": "Your username was succesfully changed.", "usernameTaken": "This username is already taken.", "vaporUsage": "\uff27\uff49\uff56\uff45 \uff53\uff4f\uff4d\uff45 \uff54\uff45\uff58\uff54 \uff46\uff4f\uff52 \uff56\uff41\uff50\uff4f\uff52\uff01", + "videoTitle": "Title:", + "videoUploader": "Uploader:", "weatherErrorCity": "Specify a city as default with the WEATHER variable, or specify which city you want the weather when typing the command!", "weatherErrorServer": "Weather information couldn't be retrieved.", "whoisError": "Failed to fetching user..", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 0ef98e3..226c9fd 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -541,7 +541,6 @@ "termUsage": "L\u00fctfen bir komut yaz\u0131n.", "testException": "Bu bir test, hata kayd\u0131 g\u00f6ndermeyin.", "testLogId": "LOG_ID de\u011feri test ediliyor ...", - "title": "%1Ba\u015fl\u0131k%2%1", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", "trtInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", @@ -595,13 +594,14 @@ "uploadInfo": ".download <bir \u015feye cevap vererek>\nKullan\u0131m: Sunucuya dosyay\u0131 indirir.\n\n.upload <sunucudaki dosya yolu>\nKullan\u0131m: Sunucunuzdaki bir dosyay\u0131 sohbete upload eder.", "uploadMedia": "Medya y\u00fckleniyor...", "uploadReply": "Buraya hi\u00e7li\u011fi y\u00fckleyemem.", - "uploader": "%1Y\u00fckleyen%2%1", "urlError": "Hata: link \u00e7\u0131kar\u0131lam\u0131yor", "useridResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2\n%1Kullan\u0131c\u0131 ID:%1 %3%4%3", "userlist": "%3%4%3 %1i\u00e7erisinde bulunan %2 kullan\u0131c\u0131lar:%1", "usernameSuccess": "Kullan\u0131c\u0131 ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", "usernameTaken": "Kullan\u0131c\u0131 ad\u0131 m\u00fcsait de\u011fil.", "vaporUsage": "\uff22\uff41\uff4e\uff41 \uff42\uff49\uff52 \uff4d\uff45\uff54\uff49\uff4e \uff56\uff45\uff52!", + "videoTitle": "Ba\u015fl\u0131k:", + "videoUploader": "Y\u00fckleyen:", "weatherErrorCity": "WEATHER de\u011fi\u015fkeniyle bir \u015fehri varsay\u0131lan olarak belirt, ya da komutu yazarken hangi \u015fehrin hava durumunu istedi\u011fini de belirt!", "weatherErrorServer": "Hava durumu bilgisi al\u0131namad\u0131.", "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", From 10fb0b7d8067d1eedeaf4cad13c1cea4a8cae0e8 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Wed, 12 May 2021 23:21:49 +0300 Subject: [PATCH 049/242] seden: Heroku: Fix shutdown Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/horeke.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index 86b92a2..595dbb8 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -214,7 +214,6 @@ def std_off(): std_off() return - std_off() heroku_app.scale_formation_process('seden', 0) From 3af27cc0cbfc268d4ab089136859648be0f4bac8 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Thu, 13 May 2021 22:48:23 +0300 Subject: [PATCH 050/242] seden: Unnecessary optimization Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/deepfry.py | 3 +- sedenbot/modules/dogbin.py | 65 ++++++++------------ sedenbot/modules/effects.py | 54 +++++++++-------- sedenbot/modules/memes.py | 17 ++++-- sedenbot/modules/qrcode.py | 62 +++++-------------- sedenbot/modules/remove_bg.py | 8 ++- sedenbot/modules/reverse.py | 4 +- sedenbot/modules/rgb.py | 14 ++--- sedenbot/modules/scrapers.py | 99 ++++++++++++++++--------------- sedenbot/modules/screencapture.py | 8 ++- sedenbot/modules/sed.py | 2 +- sedenbot/modules/spammer.py | 42 ++++++------- sedenbot/modules/system.py | 93 +++++++++-------------------- sedenbot/modules/updater.py | 2 +- sedenbot/modules/whois.py | 2 +- sedenbot/modules/youtubedl.py | 4 +- sedenecem/core/replier.py | 17 ++++-- sedenecem/translator/en.json | 8 +-- sedenecem/translator/tr.json | 8 +-- 19 files changed, 222 insertions(+), 290 deletions(-) diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index 4954884..8dcd002 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -72,7 +72,8 @@ def deepfry(message): image.save(fried_io, 'JPEG') fried_io.close() - reply_img(message, 'image.jpeg', delete_file=True, delete_orig=True) + reply_img(reply if reply else message, 'image.jpeg', delete_file=True) + message.delete() def deepfry_media(img: Image, fry: bool) -> Image: diff --git a/sedenbot/modules/dogbin.py b/sedenbot/modules/dogbin.py index cd1ba30..0f56e2d 100644 --- a/sedenbot/modules/dogbin.py +++ b/sedenbot/modules/dogbin.py @@ -7,51 +7,36 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove - from requests import exceptions, get, post -from sedenbot import DOWNLOAD_DIRECTORY, HELP +from sedenbot import HELP from sedenecem.core import edit, extract_args, get_translation, sedenify DOGBIN_URL = 'https://del.dog/' -@sedenify(pattern=r'^.paste', compat=False) -def paste(client, message): - dogbin_final_url = '' +@sedenify(pattern='^.paste') +def paste(message): match = extract_args(message) - reply_id = message.reply_to_message + reply = message.reply_to_message - if not match and not reply_id: + if match: + pass + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("dogbinUsage")}`') + match = reply.text + else: edit(message, f'`{get_translation("dogbinUsage")}`') return - if match: - dogbin = match - elif reply_id: - dogbin = message.reply_to_message - if dogbin.media: - downloaded_file_name = client.download_media( - dogbin, - DOWNLOAD_DIRECTORY, - ) - m_list = None - with open(downloaded_file_name, 'rb') as fd: - m_list = fd.readlines() - dogbin = '' - for m in m_list: - dogbin += m.decode('UTF-8') + '\r' - remove(downloaded_file_name) - else: - dogbin = dogbin.dogbin - edit(message, f'`{get_translation("dogbinPasting")}`') - resp = post(DOGBIN_URL + 'documents', data=dogbin.encode('utf-8')) + resp = post(f'{DOGBIN_URL}documents', data=match.encode('utf-8')) + dogbin_final_url = '' if resp.status_code == 200: response = resp.json() key = response['key'] - dogbin_final_url = DOGBIN_URL + key + dogbin_final_url = f'{DOGBIN_URL}{key}' if response['isUrl']: reply_text = get_translation( @@ -67,22 +52,22 @@ def paste(client, message): @sedenify(outgoing=True, pattern="^.getpaste") def getpaste(message): - textx = message.reply_to_message - dogbin = extract_args(message) + reply = message.reply_to_message + match = extract_args(message) edit(message, f'`{get_translation("dogbinContent")}`') - if textx: - dogbin = str(textx.dogbin) + if reply: + match = str(reply.text) format_normal = f'{DOGBIN_URL}' format_view = f'{DOGBIN_URL}v/' - if dogbin.startswith(format_view): - dogbin = dogbin[len(format_view) :] - elif dogbin.startswith(format_normal): - dogbin = dogbin[len(format_normal) :] - elif dogbin.startswith('del.dog/'): - dogbin = dogbin[len('del.dog/') :] + if match.startswith(format_view): + dogbin = match[len(format_view) :] + elif match.startswith(format_normal): + dogbin = match[len(format_normal) :] + elif match.startswith('del.dog/'): + dogbin = match[len('del.dog/') :] else: edit(message, f'`{get_translation("dogbinUrlError")}`') return @@ -103,7 +88,7 @@ def getpaste(message): reply_text = get_translation('dogbinResult', ['`', resp.text]) - edit(message, reply_text) + edit(message, reply_text, preview=False) HELP.update({'dogbin': get_translation('dogbinInfo')}) diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 192f067..1ca8f97 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -7,7 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import path, remove +from os import remove from subprocess import Popen from sedenbot import HELP @@ -16,7 +16,7 @@ edit, extract_args, get_translation, - reply_doc, + reply_video, reply_voice, sedenify, ) @@ -26,9 +26,6 @@ def earrape(message): args = extract_args(message).split(' ', 1) reply = message.reply_to_message - earrape = 'earrape' - if path.isfile(earrape): - remove(earrape) util = args[0].lower() if util == 'mp4': @@ -40,7 +37,7 @@ def earrape(message): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyEarrape")}`') - media = download_media_wc(reply, earrape) + media = download_media_wc(reply) process = Popen( [ 'ffmpeg', @@ -53,8 +50,13 @@ def earrape(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_doc(message, f'{media}.mp4', delete_after_send=True, delete_orig=True) + reply_video( + reply if reply else message, + f'{media}.mp4', + delete_file=True, + ) remove(media) + message.delete() elif util == 'mp3': if not ( reply.video @@ -68,7 +70,7 @@ def earrape(message): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyEarrape")}`') - media = download_media_wc(reply, earrape) + media = download_media_wc(reply) process = Popen( [ 'ffmpeg', @@ -81,9 +83,13 @@ def earrape(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(message, f'{media}.mp3', delete_orig=True) + reply_voice( + reply if reply else message, + f'{media}.mp3', + delete_file=True, + ) remove(media) - remove(f'{media}.mp3') + message.delete() else: edit(message, f'`{get_translation("wrongCommand")}`') return @@ -93,9 +99,6 @@ def earrape(message): def nightcore(message): # Copyright (c) @kisekinopureya | 2021 reply = message.reply_to_message - nightcore = 'nightcore' - if path.isfile(nightcore): - remove(nightcore) if not ( reply.audio @@ -105,12 +108,12 @@ def nightcore(message): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applyNightcore")}`') - media = download_media_wc(reply, file_name=nightcore) + media = download_media_wc(reply) process = Popen( [ 'ffmpeg', '-i', - f'{media}', + media, '-af', 'asetrate=44100*1.16,aresample=44100,atempo=1', f'{media}.mp3', @@ -118,9 +121,12 @@ def nightcore(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(message, f'{media}.mp3') + reply_voice( + reply if reply else message, + f'{media}.mp3', + delete_file=True, + ) remove(media) - remove(f'{media}.mp3') message.delete() @@ -128,9 +134,6 @@ def nightcore(message): def slowedtoperfection(message): # Copyright (c) @kisekinopureya | 2021 reply = message.reply_to_message - slowedtoperfection = 'slowedtoperfection' - if path.isfile(slowedtoperfection): - remove(slowedtoperfection) if not ( reply.audio @@ -140,12 +143,12 @@ def slowedtoperfection(message): edit(message, f'`{get_translation("wrongMedia")}`') else: edit(message, f'`{get_translation("applySlowedtoperfection")}`') - media = download_media_wc(reply, file_name=slowedtoperfection) + media = download_media_wc(reply) process = Popen( [ 'ffmpeg', '-i', - f'{media}', + media, '-af', 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', f'{media}.mp3', @@ -153,9 +156,12 @@ def slowedtoperfection(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(message, f'{media}.mp3') + reply_voice( + reply if reply else message, + f'{media}.mp3', + delete_file=True, + ) remove(media) - remove(f'{media}.mp3') message.delete() diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index 991ac3f..0bed06e 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -22,8 +22,8 @@ extract_args, get_translation, parse_cmd, + reply_sticker, sedenify, - send_sticker, ) # ================= CONSTANT ================= @@ -787,10 +787,17 @@ def h(message): ) -@sedenify(pattern='^.(amogu|su)s', compat=False) -def amogus(client, message): +@sedenify(pattern='^.(amogu|su)s') +def amogus(message): args = extract_args(message) - if len(args) < 1: + reply = message.reply_to_message + if args: + pass + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("wrongCommand")}`') + args = reply.text + else: edit(message, f'`{get_translation("wrongCommand")}`') return @@ -823,7 +830,7 @@ def amogus(client, message): image.save(output, 'WebP') output.seek(0) - send_sticker(client, message.chat, output) + reply_sticker(reply if reply else message, output) message.delete() diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index f3e1214..0ef1b7d 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -19,6 +19,7 @@ extract_args, get_translation, reply_doc, + reply_sticker, sedenify, ) from urllib3 import PoolManager @@ -26,7 +27,7 @@ from qrcode import QRCode, constants -@sedenify(pattern=r'^.decode$') +@sedenify(pattern='^.decode$') def parseqr(message): reply = message.reply_to_message if not reply: @@ -67,78 +68,45 @@ def parseqr(message): edit(message, f'`{get_translation("decodeFail")}`') -@sedenify(pattern=r'^.barcode') +@sedenify(pattern='^.barcode') def barcode(message): input_str = extract_args(message) - usage = get_translation('barcodeUsage', ['**', '`']) reply = message.reply_to_message - if len(input_str) < 1 and not reply: - edit(message, usage) + if len(input_str) < 1: + edit(message, get_translation('barcodeUsage', ['**', '`'])) return edit(message, f'`{get_translation("processing")}`') - if reply: - if reply.media: - downloaded_file_name = download_media_wc(reply) - media_list = None - with open(downloaded_file_name, 'rb') as file: - media_list = file.readlines() - qrmsg = '' - for media in media_list: - qrmsg += media.decode('UTF-8') + '\r\n' - remove(downloaded_file_name) - else: - qrmsg = reply - else: - qrmsg = input_str - - bar_code_type = 'code128' try: - bar_code_mode_f = get(bar_code_type, qrmsg, writer=ImageWriter()) - filename = bar_code_mode_f.save(bar_code_type) - reply_doc(message, filename, delete_after_send=True) + bar_code_mode_f = get('code128', input_str, writer=ImageWriter()) + filename = bar_code_mode_f.save('code128') + reply_doc(reply if reply else message, filename, delete_after_send=True) + message.delete() except Exception as e: edit(message, str(e)) return - message.delete() -@sedenify(pattern=r'^.makeqr') +@sedenify(pattern='^.makeqr') def makeqr(message): input_str = extract_args(message) - usage = get_translation('makeqrUsage', ['**', '`']) reply = message.reply_to_message - if len(input_str) < 1 and not reply: - edit(message, usage) + if len(input_str) < 1: + edit(message, get_translation('makeqrUsage', ['**', '`'])) return edit(message, f'`{get_translation("processing")}`') - if reply: - if reply.media: - downloaded_file_name = download_media_wc(reply) - media_list = None - with open(downloaded_file_name, 'rb') as file: - media_list = file.readlines() - qrmsg = '' - for media in media_list: - qrmsg += media.decode('UTF-8') + '\r\n' - remove(downloaded_file_name) - else: - qrmsg = reply - else: - qrmsg = input_str - try: qr = QRCode( version=1, error_correction=constants.ERROR_CORRECT_L, box_size=10, border=4 ) - qr.add_data(qrmsg) + qr.add_data(input_str) qr.make(fit=True) img = qr.make_image(fill_color='black', back_color='white') img.save('img_file.webp', 'PNG') - reply_doc(message, 'img_file.webp', delete_after_send=True) + reply_sticker(reply if reply else message, 'img_file.webp', delete_file=True) + message.delete() except Exception as e: edit(message, str(e)) return - message.delete() HELP.update({'qrcode': get_translation('makeqrInfo')}) diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 1f6fb07..695fb0c 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -39,10 +39,12 @@ def rbg(message): download_media_wc(reply, IMG_PATH) edit(message, f'`{get_translation("rbgProcessing")}`') try: - remove_bg = RemoveBg(RBG_APIKEY, f'{get_translation("rbgLog")}') + remove_bg = RemoveBg(RBG_APIKEY, get_translation('rbgLog')) remove_bg.remove_background_from_img_file(IMG_PATH) - rbg_img = IMG_PATH + '_no_bg.png' - reply_doc(reply, rbg_img, caption=get_translation('rbgResult')) + rbg_img = f'{IMG_PATH}_no_bg.png' + reply_doc( + reply, rbg_img, caption=get_translation('rbgResult'), delete_after_send=True + ) message.delete() except Exception as e: return edit(message, get_translation('banError', ['`', '**', e])) diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index c212164..1a0bcd8 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -26,11 +26,11 @@ ) opener = request.build_opener() -useragent = 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.70 Mobile Safari/537.36' +useragent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4464.5 Safari/537.36' opener.addheaders = [('User-agent', useragent)] -@sedenify(pattern=r'^.reverse$') +@sedenify(pattern='^.reverse$') def reverse(message): photo = 'reverse.png' if path.isfile(photo): diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index e082d13..df48c34 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -8,22 +8,22 @@ # from io import BytesIO -from os import remove from random import randint from textwrap import wrap from PIL import Image, ImageChops, ImageDraw, ImageFont from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, sedenify, send_sticker +from sedenecem.core import edit, extract_args, get_translation, reply_sticker, sedenify -@sedenify(pattern='^.rgb', compat=False) -def sticklet(client, message): +@sedenify(pattern='^.rgb') +def sticklet(message): R = randint(0, 256) G = randint(0, 256) B = randint(0, 256) sticktext = extract_args(message) + reply = message.reply_to_message if len(sticktext) < 1: edit(message, f'`{get_translation("wrongCommand")}`') @@ -68,12 +68,8 @@ def trim(im): image.save(image_stream, 'WebP') image_stream.seek(0) - send_sticker(client, message.chat, image_stream) + reply_sticker(reply if reply else message, image_stream, delete_file=True) message.delete() - try: - remove(image_stream.name) - except BaseException: - pass HELP.update({'rgb': get_translation('rgbInfo')}) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 114ec54..a345957 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -56,12 +56,12 @@ def carbon(message): edit(message, f'`{get_translation("wrongCommand")}`') return edit(message, f'`{get_translation("processing")}`') - textx = message.reply_to_message + reply = message.reply_to_message pcode = message.text if pcode[8:]: pcode = str(pcode[8:]) - elif textx: - pcode = str(textx.message) + elif reply: + pcode = str(reply.message) code = quote_plus(pcode) global CARBONLANG CARBON = f'https://carbon.now.sh/?l={CARBONLANG}&code={code}' @@ -83,12 +83,12 @@ def carbon(message): file = './carbon.png' edit(message, f'`{get_translation("carbonUpload")}`') reply_doc( - message, + reply if reply else message, file, caption=get_translation('carbonResult'), - delete_orig=True, delete_after_send=True, ) + message.delete() driver.quit() @@ -318,12 +318,11 @@ def splitter(res): @sedenify(pattern='^.ud') def urbandictionary(message): - match = extract_args(message) - if len(match) < 1: + query = extract_args(message) + if len(query) < 1: edit(message, f'`{get_translation("wrongCommand")}`') return edit(message, f'`{get_translation("processing")}`') - query = extract_args(message) try: define(query) except HTTPError: @@ -366,50 +365,53 @@ def urbandictionary(message): edit(message, get_translation('udNoResult', ['**', query])) -@sedenify(pattern=r'^.wiki') +@sedenify(pattern='^.wiki') def wiki(message): - match = extract_args(message) - if len(match) < 1: + args = extract_args(message) + if len(args) < 1: edit(message, f'`{get_translation("wrongCommand")}`') return set_lang(SEDEN_LANG) - match = extract_args(message) try: - summary(match) + summary(args) except DisambiguationError as error: edit(message, get_translation('wikiError', [error])) return except PageError as pageerror: edit(message, get_translation('wikiError2', [pageerror])) return - result = summary(match) + result = summary(args) if len(result) >= 4096: file = open('wiki.txt', 'w+') file.write(result) file.close() - reply_doc(message, 'wiki.txt', caption=f'`{get_translation("outputTooLarge")}`') - if path.exists('wiki.txt'): - remove('wiki.txt') - return - edit(message, get_translation('sedenQuery', ['**', '`', match, result])) + reply_doc( + message, + 'wiki.txt', + caption=f'`{get_translation("outputTooLarge")}`', + delete_after_send=True, + ) + edit(message, get_translation('sedenQuery', ['**', '`', args, result])) - send_log(get_translation('wikiLog', ['`', match])) + send_log(get_translation('wikiLog', ['`', args])) -@sedenify(pattern=r'^.tts') -def tts(message): - textx = message.reply_to_message - ttsx = extract_args(message) - if ttsx: +@sedenify(pattern='^.tts') +def text_to_speech(message): + reply = message.reply_to_message + args = extract_args(message) + if args: pass - elif textx: - ttsx = textx.text + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("ttsUsage")}`') + args = reply.text else: edit(message, f'`{get_translation("ttsUsage")}`') return try: - gTTS(ttsx, lang=TTS_LANG) + gTTS(args, lang=TTS_LANG) except AssertionError: edit(message, f'`{get_translation("ttsBlank")}`') return @@ -417,45 +419,47 @@ def tts(message): edit(message, f'`{get_translation("ttsNoSupport")}`') return except RuntimeError: - edit(message, f'{get_translation("ttsError")}') + edit(message, f'`{get_translation("ttsError")}`') return - tts = gTTS(ttsx, lang=TTS_LANG) + tts = gTTS(args, lang=TTS_LANG) tts.save('h.mp3') with open('h.mp3', 'rb') as audio: linelist = list(audio) linecount = len(linelist) if linecount == 1: - tts = gTTS(ttsx, lang=TTS_LANG) + tts = gTTS(args, lang=TTS_LANG) tts.save('h.mp3') with open('h.mp3', 'r'): - reply_voice(message, 'h.mp3', delete_orig=True) - remove('h.mp3') + reply_voice(reply if reply else message, 'h.mp3', delete_file=True) + message.delete() send_log(get_translation('ttsLog')) -@sedenify(pattern=r'^.trt') -def trt(message): +@sedenify(pattern='^.trt') +def translate(message): translator = Translator() - textx = message.reply_to_message - trt = extract_args(message) - if trt: + reply = message.reply_to_message + args = extract_args(message) + if args: pass - elif textx: - trt = textx.text + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("trtUsage")}`') + args = reply.text else: - edit(message, f'{get_translation("trtUsage")}') + edit(message, f'`{get_translation("trtUsage")}`') return try: - reply_text = translator.translate(deEmojify(trt), dest=TRT_LANG) + reply_text = translator.translate(deEmojify(args), dest=TRT_LANG) except ValueError: - edit(message, f'{get_translation("trtError")}') + edit(message, f'`{get_translation("trtError")}`') return - source_lan = LANGUAGES[f'{reply_text.src.lower()}'] - transl_lan = LANGUAGES[f'{reply_text.dest.lower()}'] - reply_text = '{}\n\n{}'.format( + source_lan = LANGUAGES[reply_text.src.lower()] + transl_lan = LANGUAGES[reply_text.dest.lower()] + reply_text = '{}\n{}'.format( get_translation( 'transHeader', ['**', '`', source_lan.title(), transl_lan.title()] ), @@ -547,5 +551,4 @@ def currency(message): HELP.update({'duckduckgo': get_translation('ddgoInfo')}) HELP.update({'wiki': get_translation('wikiInfo')}) HELP.update({'ud': get_translation('udInfo')}) -HELP.update({'tts': get_translation('ttsInfo')}) -HELP.update({'trt': get_translation('trtInfo')}) +HELP.update({'translator': get_translation('translatorInfo')}) diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index 9ef9ede..d45d007 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -22,9 +22,10 @@ ) -@sedenify(pattern=r'^.ss') -def ss(message): +@sedenify(pattern='^.ss') +def screenshot(message): input_str = extract_args(message) + reply = message.reply_to_message link_match = match(r'\bhttp(.*)?://.*\.\S+', input_str) if link_match: link = link_match.group() @@ -59,8 +60,9 @@ def ss(message): out.write(b64decode(im_png)) edit(message, f'`{get_translation("ssUpload")}`') reply_doc( - message, name, caption=input_str, delete_after_send=True, delete_orig=True + reply if reply else message, name, caption=input_str, delete_after_send=True ) + message.delete() HELP.update({'ss': get_translation('ssInfo')}) diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/sed.py index 6079079..e48d71a 100644 --- a/sedenbot/modules/sed.py +++ b/sedenbot/modules/sed.py @@ -95,7 +95,7 @@ def sed(message): else: text = sub(repl, repl_with, to_fix, count=1).strip() except sre_err: - edit(message, f'{get_translation("sedLearn")}') + edit(message, f'`{get_translation("sedLearn")}`') return if text: edit(message, get_translation('sedResult', [text])) diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index d6f30e4..ade3efb 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -35,8 +35,8 @@ def tspam(message): if not spam_allowed(): return - for metin in tspam.replace(' ', ''): - reply(message, metin) + for text in tspam.replace(' ', ''): + reply(message, text) count = increment_spam_count() if not count: break @@ -60,12 +60,12 @@ def spam(message): if not spam_allowed(): return - miktar = int(arr[0]) - metin = spam.replace(arr[0], '', 1).strip() - for i in range(0, miktar): - reply(message, metin) - count = increment_spam_count() - if not count: + count = int(arr[0]) + text = spam.replace(arr[0], '', 1).strip() + for i in range(0, count): + reply(message, text) + limit = increment_spam_count() + if not limit: break send_log(get_translation('spamLog')) @@ -82,12 +82,12 @@ def picspam(message): if not spam_allowed(): return - miktar = int(arr[0]) - link = arr[1] - for i in range(0, miktar): - reply_img(message, link) - count = increment_spam_count() - if not count: + count = int(arr[0]) + url = arr[1] + for i in range(0, count): + reply_img(message, url) + limit = increment_spam_count() + if not limit: break send_log(get_translation('picspamLog')) @@ -95,14 +95,14 @@ def picspam(message): @sedenify(pattern='^.delayspam') def delayspam(message): - # Copyright (c) @ReversedPosix | 2020 + # Copyright (c) @ReversedPosix | 2020-2021 delayspam = extract_args(message) arr = delayspam.split() if len(arr) < 3 or not arr[0].isdigit() or not arr[1].isdigit(): edit(message, f'`{get_translation("spamWrong")}`') return - gecikme = int(arr[0]) - miktar = int(arr[1]) + delay = int(arr[0]) + count = int(arr[1]) spam_message = delayspam.replace(arr[0], '', 1) spam_message = spam_message.replace(arr[1], '', 1).strip() message.delete() @@ -111,12 +111,12 @@ def delayspam(message): return delaySpamEvent = Event() - for i in range(0, miktar): + for i in range(0, count): if i != 0: - delaySpamEvent.wait(gecikme) + delaySpamEvent.wait(delay) reply(message, spam_message) - count = increment_spam_count() - if not count: + limit = increment_spam_count() + if not limit: break send_log(get_translation('delayspamLog')) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 0a57591..3b15b5a 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -27,7 +27,7 @@ ) # ================= CONSTANT ================= -KULLANICIMESAJI = ALIVE_MSG or f"`{get_translation('sedenAlive')}`" +CUSTOM_MSG = ALIVE_MSG or f"`{get_translation('sedenAlive')}`" # ============================================ @@ -36,35 +36,35 @@ def neofetch(message): try: from subprocess import PIPE, Popen - islem = Popen( + process = Popen( ['neofetch', f'HOSTNAME={HOSTNAME}', f'USER={USER}', '--stdout'], stdout=PIPE, stderr=PIPE, ) - sonuc, _ = islem.communicate() - edit(message, f'`{sonuc.decode()}`') + result, _ = process.communicate() + edit(message, f'`{result.decode()}`') except BaseException: edit(message, f'`{get_translation("neofetchNotFound")}`') @sedenify(pattern='^.botver$') -def botver(message): +def bot_version(message): if which('git'): from subprocess import PIPE, Popen - degisiklik = Popen( + changes = Popen( ['git', 'rev-list', '--all', '--count'], stdout=PIPE, stderr=PIPE, universal_newlines=True, ) - sonuc, _ = degisiklik.communicate() + result, _ = changes.communicate() edit( message, get_translation( 'sedenShowBotVersion', - ['**', '`', 'Seden UserBot', CHANNEL, BOT_VERSION, sonuc], + ['**', '`', CHANNEL, BOT_VERSION, result], ), preview=False, ) @@ -72,60 +72,25 @@ def botver(message): edit(message, f'`{get_translation("sedenGitNotFound")}`') -@sedenify(pattern='^.pip') -def pip3(message): - pipmodule = extract_args(message) - if len(pipmodule) > 0: - edit(message, f'`{get_translation("pipSearch")}`') - pipsorgu = f"pip3 search {pipmodule}" - from subprocess import PIPE, Popen - - islem = Popen( - pipsorgu.split(), stdout=PIPE, stderr=PIPE, universal_newlines=True - ) - sonuc, _ = islem.communicate() - - if sonuc: - if len(sonuc) > 4096: - edit(message, f'`{get_translation("outputTooLarge")}`') - file = open('pip3.txt', 'w+') - file.write(sonuc) - file.close() - reply_doc(message, 'pip3.txt', delete_after_send=True) - return - edit(message, get_translation( - 'sedenQuery', ['**', '`', pipsorgu, sonuc])) - else: - edit( - message, - get_translation( - 'sedenQuery', - ['**', '`', pipsorgu, get_translation('sedenZeroResults')], - ), - ) - else: - edit(message, f'`{get_translation("pipHelp")}`') - - @sedenify(pattern='^.ping$') def ping(message): - basla = datetime.now() - edit(message, '`Pong!`') - bitir = datetime.now() - sure = (bitir - basla).microseconds / 1000 - edit(message, f'`Pong!\n{sure}ms`') + start = datetime.now() + edit(message, '**Pong!**') + finish = datetime.now() + time = (finish - start).microseconds / 1000 + edit(message, f'**Pong!**\n`{time}ms`') @sedenify(pattern='^.alive$') def alive(message): - if KULLANICIMESAJI.lower() == 'ecem': + if CUSTOM_MSG.lower() == 'ecem': ecem(message) return - edit(message, f'{KULLANICIMESAJI}') + edit(message, f'{CUSTOM_MSG}') @sedenify(pattern='^.echo') -def echo(message): +def test_echo(message): args = extract_args(message) if len(args) > 0: message.delete() @@ -135,14 +100,14 @@ def echo(message): @sedenify(pattern='^.dc$', compat=False) -def dc(client, message): - sonuc = client.send(GetNearestDc()) +def data_center(client, message): + result = client.send(GetNearestDc()) edit( message, get_translation( 'sedenNearestDC', - ['**', '`', sonuc.country, sonuc.nearest_dc, sonuc.this_dc], + ['**', '`', result.country, result.nearest_dc, result.this_dc], ), ) @@ -167,17 +132,17 @@ def terminal(message): edit(message, f'`{get_translation("termHelp")}`') return - sonuc = f'`{get_translation("termNoResult")}`' + result = f'`{get_translation("termNoResult")}`' try: from subprocess import getoutput - sonuc = getoutput(command) + result = getoutput(command) except BaseException: pass - if len(sonuc) > 4096: + if len(result) > 4096: output = open('output.txt', 'w+') - output.write(sonuc) + output.write(result) output.close() reply_doc( message, @@ -187,8 +152,7 @@ def terminal(message): ) return - edit( - message, f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{sonuc}`') + edit(message, f'`{curruser}:~{"#" if uid == 0 else "$"} {command}\n{result}`') send_log(get_translation('termLog', [command])) @@ -217,20 +181,17 @@ def eval(message): return edit( message, - get_translation( - 'sedenQuery', ['**', '`', args, evaluation]), + get_translation('sedenQuery', ['**', '`', args, evaluation]), ) else: edit( message, get_translation( - 'sedenQuery', ['**', '`', args, - get_translation('sedenErrorResult')] + 'sedenQuery', ['**', '`', args, get_translation('sedenErrorResult')] ), ) except Exception as err: - edit(message, get_translation( - 'sedenQuery', ['**', '`', args, str(err)])) + edit(message, get_translation('sedenQuery', ['**', '`', args, str(err)])) send_log(get_translation('evalLog', [args])) diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index 411ea4b..82d971a 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -105,7 +105,7 @@ def upstream(ups): file = open('changelog.txt', 'w+') file.write(changelog) file.close() - reply_doc(ups, ups.chat.id, 'changelog.txt', delete_after_send=True) + reply_doc(ups, 'changelog.txt', delete_after_send=True) else: edit( ups, get_translation('updaterHasUpdate', ['**', '`', ac_br, changelog]) diff --git a/sedenbot/modules/whois.py b/sedenbot/modules/whois.py index a88e460..f312787 100644 --- a/sedenbot/modules/whois.py +++ b/sedenbot/modules/whois.py @@ -93,7 +93,7 @@ def who_is(client, message): if photo and media_perm: reply_img( - message, photo, caption=caption, delete_file=True, delete_orig=True + reply if reply else message, photo, caption=caption, delete_file=True ) else: return edit(message, caption) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 2e79ea9..e2a68fe 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -46,7 +46,7 @@ def youtubedl(message): edit(message, get_translation('downloadYTVideo', ['**', title, '`'])) ydl_opts = { 'outtmpl': f'{title}.%(ext)s', - 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', + 'format': 'bestvideo[ext=mp4][height<=?1080]+bestaudio[ext=mp3]/best', } with YoutubeDL(ydl_opts) as ydl: ydl.download([url]) @@ -80,8 +80,8 @@ def youtubedl(message): f'{title}.mp3', caption=f"**{get_translation('videoUploader')}** `{uploader}`", delete_orig=True, + delete_file=True, ) - remove(f'{title}.mp3') HELP.update({'youtubedl': get_translation('youtubedlInfo')}) diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 2e636b7..5188cac 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -27,20 +27,23 @@ def reply_img( message.reply_photo(photo, caption=caption.strip(), parse_mode=parse) if delete_orig: message.delete() - if delete_file: remove(photo) except BaseException: pass -def reply_audio(message, audio, caption='', fix_markdown=False, delete_orig=False): +def reply_audio( + message, audio, caption='', fix_markdown=False, delete_orig=False, delete_file=False +): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR message.reply_audio(audio, caption=caption.strip()) if delete_orig: message.delete() + if delete_file: + remove(audio) except BaseException: pass @@ -60,13 +63,17 @@ def reply_video( pass -def reply_voice(message, voice, caption='', fix_markdown=False, delete_orig=False): +def reply_voice( + message, voice, caption='', fix_markdown=False, delete_orig=False, delete_file=False +): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR message.reply_voice(voice, caption=caption.strip()) if delete_orig: message.delete() + if delete_file: + remove(voice) except BaseException: pass @@ -98,11 +105,13 @@ def reply_doc( raise e -def reply_sticker(message, sticker, delete_orig=False): +def reply_sticker(message, sticker, delete_orig=False, delete_file=False): try: message.reply_sticker(sticker) if delete_orig: message.delete() + if delete_file: + remove(sticker) except BaseException: pass diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 207c3aa..fcf0fda 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -349,8 +349,6 @@ "picspamLog": "#PICSPAM\nPicSpam was executed successfully", "pinLog": "#PIN\nGROUP: %1 (%2%3%2)", "pinResult": "Pinned Successfully!", - "pipHelp": "Use .seden system command to see an example.", - "pipSearch": "Searching...", "pmApproveError": "You're not currently in a PM and you haven't reply someone's message.", "pmApproveError2": "User may already be approved.", "pmApproveLog": "#APPROVED\nUser: [%1](tg://user?id=%2)", @@ -484,12 +482,11 @@ "sedenQuery": "%1Query:%1\n%2%3%2\n%1Result:%1\n%2%4%2\n", "sedenQueryUd": "%1Query:%1\n%2%3%2\n%1Meaning:%1\n%2%4%2\n\n%1Example:%1\n%2%5%2", "sedenSetAlive": "New alive message is %1", - "sedenShowBotVersion": "%1[%3](https://t.me/%4)%1\n%1Version:%1 %2v%5%2\n%1Commits:%1 %2%6%2", + "sedenShowBotVersion": "%1[Seden UserBot](https://t.me/%3)%1\n%1Version:%1 %2v%4%2\n%1Commits:%1 %2%5%2", "sedenShowLoadedModules": "%1Loaded modules:%1 %2%3%2\n%1Modules:%1", "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", - "sedenZeroResults": "Nothing found.", "shutdown": "Goodbye *Windows XP shutdown sound*...", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", @@ -542,14 +539,13 @@ "testException": "This is a test, don't submit a bug log.", "testLogId": "Testing LOG_ID value ...", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", + "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", "trtError": "Invalid destination language.", - "trtInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)", "trtLog": "Translated some %1 stuff to %2 just now.", "trtUsage": "Give a text or reply to a message to translate!", "tspamLog": "#TSPAM\nTSpam was executed successfully", "ttsBlank": "Text is blank.\nAfter preprocessing, tokenization and cleaning, there is nothing left to talk about.", "ttsError": "An error has occurred in viewing the language's dictionary.", - "ttsInfo": ".tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", "ttsLog": "Text has been successfully converted to speech!", "ttsNoSupport": "This language is not yet supported.", "ttsUsage": "Enter some text to translate from text to speech.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 226c9fd..36183d5 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -350,8 +350,6 @@ "picspamLog": "#PICSPAM\nPicSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "pinLog": "#PIN\nGRUP: %1 (%2%3%2)", "pinResult": "Ba\u015far\u0131yla sabitlendi!", - "pipHelp": "Bir \u00f6rnek g\u00f6rmek i\u00e7in .seden system komutunu kullan\u0131n.", - "pipSearch": "Aran\u0131yor...", "pmApproveError": "\u015eu an bir PM'de de\u011filsin ve birinin mesaj\u0131n\u0131 al\u0131nt\u0131lamad\u0131n.", "pmApproveError2": "Kullan\u0131c\u0131 halihaz\u0131rda PM g\u00f6nderebiliyor olmal\u0131d\u0131r.", "pmApproveLog": "#ONAYLANDI\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", @@ -484,12 +482,11 @@ "sedenQuery": "%1Sorgu:%1\n%2%3%2\n%1Sonu\u00e7:%1\n%2%4%2\n", "sedenQueryUd": "%1Sorgu:%1\n%2%3%2\n%1S\u00f6yleyi\u015f:%1\n%2%4%2\n\n%1\u00d6rnek:%1\n%2%5%2", "sedenSetAlive": "Alive mesaj\u0131, %1 olarak ayarland\u0131", - "sedenShowBotVersion": "%1[%3](https://t.me/%4)%1\n%1S\u00fcr\u00fcm:%1 %2v%5%2\n%1De\u011fi\u015fiklikler:%1 %2%6%2", + "sedenShowBotVersion": "%1[Seden UserBot](https://t.me/%3)%1\n%1S\u00fcr\u00fcm:%1 %2v%4%2\n%1De\u011fi\u015fiklikler:%1 %2%5%2", "sedenShowLoadedModules": "%1Y\u00fcklenen mod\u00fcl say\u0131s\u0131:%1 %2%3%2\n%1Mod\u00fcller:%1", "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", - "sedenZeroResults": "Hi\u00e7bir \u015fey bulunamad\u0131.", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz ...", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", @@ -542,14 +539,13 @@ "testException": "Bu bir test, hata kayd\u0131 g\u00f6ndermeyin.", "testLogId": "LOG_ID de\u011feri test ediliyor ...", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", + "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", - "trtInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "trtLog": "Birka\u00e7 %1 kelime az \u00f6nce %2 diline \u00e7evirildi.", "trtUsage": "Bana \u00e7evirilecek bir metin ver!", "tspamLog": "#TSPAM\nTSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "ttsBlank": "Metin bo\u015f.\n\u00d6n i\u015fleme, tokenizasyon ve temizlikten sonra konu\u015facak hi\u00e7bir \u015fey kalmad\u0131.", "ttsError": "Dilin s\u00f6zl\u00fc\u011f\u00fcn\u00fc g\u00f6r\u00fcnt\u00fclemede bir hata ger\u00e7ekle\u015fti.", - "ttsInfo": ".tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "ttsLog": "Metin ba\u015far\u0131yla sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcld\u00fc!", "ttsNoSupport": "Bu dil hen\u00fcz desteklenmiyor.", "ttsUsage": "Yaz\u0131dan sese \u00e7evirmek i\u00e7in bir metin gir.", From 5d8d41c3c621cf6548ddc9a3bde042d01406e04d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Tue, 18 May 2021 14:20:45 +0300 Subject: [PATCH 051/242] seden: Unnecessary optimization [2/2] Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/ban.py | 4 +++- sedenbot/modules/deepfry.py | 2 +- sedenbot/modules/effects.py | 24 ++++-------------------- sedenbot/modules/memes.py | 2 +- sedenbot/modules/qrcode.py | 4 ++-- sedenbot/modules/rgb.py | 2 +- sedenbot/modules/screencapture.py | 4 +--- sedenbot/modules/whois.py | 4 +--- sedenecem/core/replier.py | 10 ++++++++-- sedenecem/translator/en.json | 3 ++- sedenecem/translator/tr.json | 3 ++- 11 files changed, 26 insertions(+), 36 deletions(-) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 3770748..1282448 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -19,7 +19,7 @@ reply_doc, sedenify, send_log, - is_admin + is_admin, ) from sedenecem.sql import mute_sql as sql @@ -68,6 +68,8 @@ def ban_user(client, message): [user.first_name, user.id, message.chat.title, '`', chat_id], ) ) + except UserAdminInvalid: + edit(message, f'`{get_translation("banAdminError")}`') except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) return diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index 8dcd002..22dc8cc 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -72,7 +72,7 @@ def deepfry(message): image.save(fried_io, 'JPEG') fried_io.close() - reply_img(reply if reply else message, 'image.jpeg', delete_file=True) + reply_img(reply or message, 'image.jpeg', delete_file=True) message.delete() diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 1ca8f97..4d23b34 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -50,11 +50,7 @@ def earrape(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_video( - reply if reply else message, - f'{media}.mp4', - delete_file=True, - ) + reply_video(reply or message, f'{media}.mp4', delete_file=True) remove(media) message.delete() elif util == 'mp3': @@ -83,11 +79,7 @@ def earrape(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice( - reply if reply else message, - f'{media}.mp3', - delete_file=True, - ) + reply_voice(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() else: @@ -121,11 +113,7 @@ def nightcore(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice( - reply if reply else message, - f'{media}.mp3', - delete_file=True, - ) + reply_voice(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() @@ -156,11 +144,7 @@ def slowedtoperfection(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice( - reply if reply else message, - f'{media}.mp3', - delete_file=True, - ) + reply_voice(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index 0bed06e..eaafa3a 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -830,7 +830,7 @@ def amogus(message): image.save(output, 'WebP') output.seek(0) - reply_sticker(reply if reply else message, output) + reply_sticker(reply or message, output) message.delete() diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index 0ef1b7d..48de608 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -79,7 +79,7 @@ def barcode(message): try: bar_code_mode_f = get('code128', input_str, writer=ImageWriter()) filename = bar_code_mode_f.save('code128') - reply_doc(reply if reply else message, filename, delete_after_send=True) + reply_doc(reply or message, filename, delete_after_send=True) message.delete() except Exception as e: edit(message, str(e)) @@ -102,7 +102,7 @@ def makeqr(message): qr.make(fit=True) img = qr.make_image(fill_color='black', back_color='white') img.save('img_file.webp', 'PNG') - reply_sticker(reply if reply else message, 'img_file.webp', delete_file=True) + reply_sticker(reply or message, 'img_file.webp', delete_file=True) message.delete() except Exception as e: edit(message, str(e)) diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index df48c34..468a352 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -68,7 +68,7 @@ def trim(im): image.save(image_stream, 'WebP') image_stream.seek(0) - reply_sticker(reply if reply else message, image_stream, delete_file=True) + reply_sticker(reply or message, image_stream, delete_file=True) message.delete() diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index d45d007..3d950e6 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -59,9 +59,7 @@ def screenshot(message): with open(name, 'wb') as out: out.write(b64decode(im_png)) edit(message, f'`{get_translation("ssUpload")}`') - reply_doc( - reply if reply else message, name, caption=input_str, delete_after_send=True - ) + reply_doc(reply or message, name, caption=input_str, delete_after_send=True) message.delete() diff --git a/sedenbot/modules/whois.py b/sedenbot/modules/whois.py index f312787..8d9a198 100644 --- a/sedenbot/modules/whois.py +++ b/sedenbot/modules/whois.py @@ -92,9 +92,7 @@ def who_is(client, message): ) if photo and media_perm: - reply_img( - reply if reply else message, photo, caption=caption, delete_file=True - ) + reply_img(reply or message, photo, caption=caption, delete_file=True) else: return edit(message, caption) diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 5188cac..58bc8fd 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -49,12 +49,18 @@ def reply_audio( def reply_video( - message, video, caption='', fix_markdown=False, delete_orig=False, delete_file=False + message, + video, + caption='', + fix_markdown=False, + delete_orig=False, + delete_file=False, + parse='md', ): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - message.reply_video(video, caption=caption.strip()) + message.reply_video(video, caption=caption.strip(), parse_mode=parse) if delete_orig: message.delete() if delete_file: diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index fcf0fda..b925f2d 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -57,6 +57,7 @@ "autoppLog": "[AUTOPP] File already exists, skipping download ...", "autoppProcess": "Setting up profile photo ...", "autoppResult": "Profile photo has been updated!", + "banAdminError": "I guess i haven't enough perm for this.", "banError": "%1Something went wrong!%1\n\n%2%3%2", "banFailUser": "Please specify a valid user!", "banLog": "#BAN\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", @@ -531,7 +532,7 @@ "sudoCheck": "This person is Seden admin!", "supportResult": "You can reach our support group [here](http://t.me/%1).", "syntaxError": "Syntax error.", - "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.pip <module name>\nUsage: Searches in pip modules.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound... but you don/'t.", + "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound... but you don/'t.", "termHelp": "You can look at example by typing .seden system for help.", "termLog": "Terminal command %1 executed successfully", "termNoResult": "No result", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 36183d5..bd57272 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -57,6 +57,7 @@ "autoppLog": "[AUTOPP] Dosya zaten mevcut, indirme atlan\u0131yor ...", "autoppProcess": "Profil foto\u011fraf\u0131n\u0131z ayarlan\u0131yor ...", "autoppResult": "Profil foto\u011fraf\u0131n\u0131z ayarland\u0131!", + "banAdminError": "San\u0131r\u0131m bunun i\u00e7in yeterli yetkim olmayabilir.", "banError": "%1Bir hata olu\u015ftu!%1\n\n%2%3%2", "banFailUser": "Ge\u00e7erli bir kullan\u0131c\u0131 belirtiniz!", "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", @@ -531,7 +532,7 @@ "sudoCheck": "Bu ki\u015fi Seden yetkilisi!", "supportResult": "[Buradan](http://t.me/%1) destek grubumuza ula\u015fabilirsiniz.", "syntaxError": "S\u00f6zdizimi hatas\u0131.", - "systemInfo": ".alive\nKullan\u0131m: Seden botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.pip <mod\u00fcl ismi>\nKullan\u0131m: Pip mod\u00fcllerinde arama yapar.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin...", + "systemInfo": ".alive\nKullan\u0131m: Seden botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin...", "termHelp": "Yard\u0131m almak i\u00e7in .seden system yazarak \u00f6rne\u011fe bakabilirsin.", "termLog": "Terminal komutu %1 ba\u015far\u0131yla \u00e7al\u0131\u015ft\u0131r\u0131ld\u0131", "termNoResult": "Komutun sonucu al\u0131namad\u0131.", From 1ad94a22671707c33ebb535a8be7c57c3f4462ac Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Tue, 18 May 2021 14:49:07 +0300 Subject: [PATCH 052/242] seden: Proxy, random google domains Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/android.py | 78 +------------- sedenbot/modules/scrapers.py | 44 +++++--- sedenecem/core/__init__.py | 1 + sedenecem/core/misc.py | 191 +++++++++++++++++++++++++++++++++++ sedenecem/core/proxy.py | 80 +++++++++++++++ 5 files changed, 307 insertions(+), 87 deletions(-) create mode 100644 sedenecem/core/proxy.py diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 389acf2..7aa5b72 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -14,10 +14,8 @@ from bs4 import BeautifulSoup from requests import get -from sedenbot import HELP, TEMP_SETTINGS -from sedenecem.core import edit, extract_args, get_translation, sedenify - -GITHUB = 'https://github.com' +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify, use_proxy @sedenify(pattern='^.magisk$') @@ -215,9 +213,7 @@ def specs(message): edit(message, f'`{get_translation("specsUsage")}`') return - edit(message, f'`{get_translation("fetchProxy")}`') - proxy = get_random_proxy() - edit(message, f'`{get_translation("providedProxy")}`') + proxy = use_proxy(message) link = find_device(args, proxy) if not link: @@ -344,74 +340,6 @@ def replace_query(query): return None -def get_stored_proxy(): - return TEMP_SETTINGS.get('VALID_PROXY_URL', '') - - -def put_stored_proxy(proxy): - TEMP_SETTINGS['VALID_PROXY_URL'] = proxy - - -def _xget_random_proxy(): - proxy = get_stored_proxy() - try_valid = tuple(proxy.split(":")) if len(proxy) else None - if try_valid: - valid = _try_proxy(try_valid) - if valid[0] == 200 and "<title>Too" not in valid[1]: - return try_valid - - head = { - 'Accept-Encoding': 'gzip, deflate, sdch', - 'Accept-Language': 'en-US,en;q=0.8', - 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', - 'Referer': 'https://www.google.com/search?q=sslproxies', - } - - req = get('https://sslproxies.org/', headers=head) - soup = BeautifulSoup(req.text, 'html.parser') - res = soup.find('table', {'id': 'proxylisttable'}).find('tbody') - res = res.findAll('tr') - for item in res: - infos = item.findAll('td') - ip = infos[0].text - port = infos[1].text - proxy = (ip, port) - if _try_proxy(proxy)[0] == 200: - return proxy - - return None - - -def _try_proxy(proxy): - try: - prxy = f'http://{proxy[0]}:{proxy[1]}' - req = get( - 'https://www.gsmarena.com/', - proxies={'http': prxy, 'https': prxy}, - timeout=1, - ) - if req.status_code == 200: - return (200, req.text) - raise Exception - except BaseException: - return (404, None) - - -def get_random_proxy(): - proxy = _xget_random_proxy() - if not proxy: - return None - proxy = f'http://{proxy[0]}:{proxy[1]}' - put_stored_proxy(proxy) - - proxy_dict = { - 'https': proxy, - 'http': proxy, - } - - return proxy_dict - - class OFRPDeviceInfo: def __init__(self, json, releases): if not json: diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index a345957..8958c67 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -9,6 +9,7 @@ from mimetypes import guess_type from os import path, remove +from random import choice from re import findall, sub from time import sleep from traceback import format_exc @@ -28,6 +29,7 @@ extract_args, get_translation, get_webdriver, + google_domains, reply_doc, reply_voice, sedenify, @@ -111,9 +113,7 @@ def img(message): return edit(message, f'`{get_translation("processing")}`') - url = ( - f'https://www.google.com/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' - ) + url = f'https://{choice(google_domains)}/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' driver = get_webdriver() driver.get(url) count = 1 @@ -208,11 +208,18 @@ def link_replacer(link): return link def get_result(res): - link = res.findAll('a', {'class': ['fuLhoc', 'ZWRArf']})[0] - href = f"https://google.com{link_replacer(link['href'])}" - title = link.findAll('span', {'class': ['CVA68e', 'qXLe6d']})[0].text + link = res.findAll('a') + for a in link: + if a.find('h3', {'class': ['zBAuLc']}): + link = a + break + href = f"https://{choice(google_domains)}{link_replacer(link['href'])}" + title = link.find('h3', {'class': ['zBAuLc']}).text title = replacer(title) - desc = res.findAll('span', {'class': ['qXLe6d', 'FrIlee']})[-1].text + desc = res.contents[-1].findAll( + 'div', {'class': ['BNeawe', 's3v9rd', 'AP7Wnd']} + ) + desc = [d.text for d in desc if len(d.text)][0] desc = replacer(desc) if len(desc.strip()) < 1: desc = get_translation('googleDesc') @@ -220,25 +227,38 @@ def get_result(res): query = parse_key(query) page = find_page(page) + temp = f'/search?q={query}&gbv=1&sei=o9ybYJmOFojssAfep7rADQ&start={find_page(page)}' + req = get( - f'https://www.google.com/search?q={query}&gbv=1&sei=2oR3X4nhGY611fAP_5-EkAw&start={find_page(page)}', + f'https://{choice(google_domains)}{temp}', headers={ - 'User-Agent': 'Mozilla/5.0 (compatible; Konqueror/2.2-12; Linux)', + 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', 'Content-Type': 'text/html', }, ) + retries = 0 + while req.status_code != 200 and retries < 10: + retries += 1 + req = get( + f'https://{choice(google_domains)}{temp}', + headers={ + 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', + 'Content-Type': 'text/html', + }, + ) + soup = BeautifulSoup(req.text, 'html.parser') - res1 = soup.findAll('div', {'class': ['ezO2md']}) + res1 = soup.findAll('div', {'class': ['ZINbbc', 'xpd', 'O9g5cc', 'uUPGi']}) def is_right_class(res): - ret = res.find('span', {'class': ['qXLe6d', 'dXDvrc']}) + ret = res.find('h3', {'class': ['zBAuLc']}) if not ret: return False ret = ret.parent - return ret.name == 'a' and 'fuLhoc' in ret['class'] + return ret.name == 'a' res1 = [res for res in res1 if is_right_class(res)] diff --git a/sedenecem/core/__init__.py b/sedenecem/core/__init__.py index 6a81194..1aad87d 100644 --- a/sedenecem/core/__init__.py +++ b/sedenecem/core/__init__.py @@ -10,6 +10,7 @@ from .conv import * from .image import * from .misc import * +from .proxy import * from .replier import * from .sedenify import * from .sedenlog import * diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 5f2c8a8..18bf24c 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -16,6 +16,197 @@ SPAM_COUNT = [0] _parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r'\.' _admin_status_list = ['creator', 'administrator'] +google_domains = [ + 'www.google.com', + 'www.google.ad', + 'www.google.ae', + 'www.google.com.af', + 'www.google.com.ag', + 'www.google.com.ai', + 'www.google.am', + 'www.google.co.ao', + 'www.google.com.ar', + 'www.google.as', + 'www.google.at', + 'www.google.com.au', + 'www.google.az', + 'www.google.ba', + 'www.google.com.bd', + 'www.google.be', + 'www.google.bf', + 'www.google.bg', + 'www.google.com.bh', + 'www.google.bi', + 'www.google.bj', + 'www.google.com.bn', + 'www.google.com.bo', + 'www.google.com.br', + 'www.google.bs', + 'www.google.co.bw', + 'www.google.by', + 'www.google.com.bz', + 'www.google.ca', + 'www.google.cd', + 'www.google.cf', + 'www.google.cg', + 'www.google.ch', + 'www.google.ci', + 'www.google.co.ck', + 'www.google.cl', + 'www.google.cm', + 'www.google.cn', + 'www.google.com.co', + 'www.google.co.cr', + 'www.google.com.cu', + 'www.google.cv', + 'www.google.com.cy', + 'www.google.cz', + 'www.google.de', + 'www.google.dj', + 'www.google.dk', + 'www.google.dm', + 'www.google.com.do', + 'www.google.dz', + 'www.google.com.ec', + 'www.google.ee', + 'www.google.com.eg', + 'www.google.es', + 'www.google.com.et', + 'www.google.fi', + 'www.google.com.fj', + 'www.google.fm', + 'www.google.fr', + 'www.google.ga', + 'www.google.ge', + 'www.google.gg', + 'www.google.com.gh', + 'www.google.com.gi', + 'www.google.gl', + 'www.google.gm', + 'www.google.gp', + 'www.google.gr', + 'www.google.com.gt', + 'www.google.gy', + 'www.google.com.hk', + 'www.google.hn', + 'www.google.hr', + 'www.google.ht', + 'www.google.hu', + 'www.google.co.id', + 'www.google.ie', + 'www.google.co.il', + 'www.google.im', + 'www.google.co.in', + 'www.google.iq', + 'www.google.is', + 'www.google.it', + 'www.google.je', + 'www.google.com.jm', + 'www.google.jo', + 'www.google.co.jp', + 'www.google.co.ke', + 'www.google.com.kh', + 'www.google.ki', + 'www.google.kg', + 'www.google.co.kr', + 'www.google.com.kw', + 'www.google.kz', + 'www.google.la', + 'www.google.com.lb', + 'www.google.li', + 'www.google.lk', + 'www.google.co.ls', + 'www.google.lt', + 'www.google.lu', + 'www.google.lv', + 'www.google.com.ly', + 'www.google.co.ma', + 'www.google.md', + 'www.google.me', + 'www.google.mg', + 'www.google.mk', + 'www.google.ml', + 'www.google.mn', + 'www.google.ms', + 'www.google.com.mt', + 'www.google.mu', + 'www.google.mv', + 'www.google.mw', + 'www.google.com.mx', + 'www.google.com.my', + 'www.google.co.mz', + 'www.google.com.na', + 'www.google.com.nf', + 'www.google.com.ng', + 'www.google.com.ni', + 'www.google.ne', + 'www.google.nl', + 'www.google.no', + 'www.google.com.np', + 'www.google.nr', + 'www.google.nu', + 'www.google.co.nz', + 'www.google.com.om', + 'www.google.com.pa', + 'www.google.com.pe', + 'www.google.com.ph', + 'www.google.com.pk', + 'www.google.pl', + 'www.google.pn', + 'www.google.com.pr', + 'www.google.ps', + 'www.google.pt', + 'www.google.com.py', + 'www.google.com.qa', + 'www.google.ro', + 'www.google.ru', + 'www.google.rw', + 'www.google.com.sa', + 'www.google.com.sb', + 'www.google.sc', + 'www.google.se', + 'www.google.com.sg', + 'www.google.sh', + 'www.google.si', + 'www.google.sk', + 'www.google.com.sl', + 'www.google.sn', + 'www.google.so', + 'www.google.sm', + 'www.google.st', + 'www.google.com.sv', + 'www.google.td', + 'www.google.tg', + 'www.google.co.th', + 'www.google.com.tj', + 'www.google.tk', + 'www.google.tl', + 'www.google.tm', + 'www.google.tn', + 'www.google.to', + 'www.google.com.tr', + 'www.google.tt', + 'www.google.com.tw', + 'www.google.co.tz', + 'www.google.com.ua', + 'www.google.co.ug', + 'www.google.co.uk', + 'www.google.com.uy', + 'www.google.co.uz', + 'www.google.com.vc', + 'www.google.co.ve', + 'www.google.vg', + 'www.google.co.vi', + 'www.google.com.vn', + 'www.google.vu', + 'www.google.ws', + 'www.google.rs', + 'www.google.co.za', + 'www.google.co.zm', + 'www.google.co.zw', + 'www.google.cat', + 'www.google.xxx', +] def reply( diff --git a/sedenecem/core/proxy.py b/sedenecem/core/proxy.py new file mode 100644 index 0000000..5a740a5 --- /dev/null +++ b/sedenecem/core/proxy.py @@ -0,0 +1,80 @@ +from bs4 import BeautifulSoup +from requests import get +from sedenbot import TEMP_SETTINGS, get_translation + +from .misc import edit + + +def use_proxy(message) -> None: + edit(message, f'`{get_translation("fetchProxy")}`') + proxy = get_random_proxy() + edit(message, f'`{get_translation("providedProxy")}`') + return proxy + + +def get_stored_proxy(): + return TEMP_SETTINGS.get('VALID_PROXY_URL', '') + + +def put_stored_proxy(proxy): + TEMP_SETTINGS['VALID_PROXY_URL'] = proxy + + +def _xget_random_proxy(): + proxy = get_stored_proxy() + try_valid = tuple(proxy.split(":")) if len(proxy) else None + if try_valid: + valid = _try_proxy(try_valid) + if valid[0] == 200 and "<title>Too" not in valid[1]: + return try_valid + + head = { + 'Accept-Encoding': 'gzip, deflate, sdch', + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', + 'Referer': 'https://www.google.com/search?q=sslproxies', + } + + req = get('https://sslproxies.org/', headers=head) + soup = BeautifulSoup(req.text, 'html.parser') + res = soup.find('table', {'id': 'proxylisttable'}).find('tbody') + res = res.findAll('tr') + for item in res: + infos = item.findAll('td') + ip = infos[0].text + port = infos[1].text + proxy = (ip, port) + if _try_proxy(proxy)[0] == 200: + return proxy + + return None + + +def _try_proxy(proxy): + try: + prxy = f'http://{proxy[0]}:{proxy[1]}' + req = get( + 'https://www.gsmarena.com/', + proxies={'http': prxy, 'https': prxy}, + timeout=1, + ) + if req.status_code == 200: + return (200, req.text) + raise Exception + except BaseException: + return (404, None) + + +def get_random_proxy(): + proxy = _xget_random_proxy() + if not proxy: + return None + proxy = f'http://{proxy[0]}:{proxy[1]}' + put_stored_proxy(proxy) + + proxy_dict = { + 'https': proxy, + 'http': proxy, + } + + return proxy_dict From 7cd057338d540f1287febce82a1fe721c0e5bc68 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sat, 22 May 2021 12:56:23 +0300 Subject: [PATCH 053/242] seden: youtubedl: Improve video & audio metadata Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/youtubedl.py | 81 +++++++++++++++++++++++++++-------- sedenecem/core/replier.py | 30 +++++++++++-- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index e2a68fe..e479759 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -8,8 +8,11 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove +from io import BytesIO + +from PIL import Image +from requests import get from sedenbot import HELP from sedenecem.core import ( edit, @@ -20,7 +23,6 @@ sedenify, ) from youtube_dl import YoutubeDL -from youtube_dl.utils import DownloadError @sedenify(pattern='^.(youtube|yt)dl') @@ -34,51 +36,96 @@ def youtubedl(message): util = args[0].lower() url = args[1] - try: - video_info = YoutubeDL().extract_info(url, False) - except DownloadError as e: - return edit(message, get_translation('banError', ['`', '**', e])) - - title = video_info.get('title') - uploader = video_info.get('uploader') - if util == 'mp4': - edit(message, get_translation('downloadYTVideo', ['**', title, '`'])) ydl_opts = { - 'outtmpl': f'{title}.%(ext)s', - 'format': 'bestvideo[ext=mp4][height<=?1080]+bestaudio[ext=mp3]/best', + 'outtmpl': f'%(id)s.%(ext)s', + 'format': 'bestvideo[ext=mp4][height<=?1080]+bestaudio[ext=m4a]/best', + 'addmetadata': True, + 'prefer_ffmpeg': True, + 'geo_bypass': True, + 'nocheckcertificate': True, + 'postprocessors': [ + {'key': 'FFmpegMetadata'}, + {'key': 'FFmpegVideoConvertor', 'preferedformat': 'mp4'}, + ], + 'quiet': True, + 'logtostderr': False, } + thumb_path = None with YoutubeDL(ydl_opts) as ydl: + try: + video_info = ydl.extract_info(url, False) + title = video_info['title'] + uploader = video_info['uploader'] + except BaseException as e: + return edit(message, get_translation('banError', ['`', '**', e])) + + edit(message, get_translation('downloadYTVideo', ['**', title, '`'])) + + try: + temp = BytesIO() + with get(video_info['thumbnail']) as req: + temp.name = 'file.webp' + temp.write(req.content) + temp.seek(0) + im = Image.open(temp) + imc = im.convert('RGB') + imc.save(thumb_path := f'downloads/{video_info["id"]}.jpg') + except BaseException as e: + thumb_path = None + ydl.download([url]) + edit(message, f'`{get_translation("uploadMedia")}`') reply_video( message, - f'{title}.mp4', + f'{video_info["id"]}.mp4', + thumb=thumb_path, caption=f"**{get_translation('videoTitle')}** `{title}`\n**{get_translation('videoUploader')}** `{uploader}`", + duration=int(video_info['duration']), delete_orig=True, delete_file=True, ) elif util == 'mp3': - edit(message, get_translation('downloadYTAudio', ['**', title, '`'])) ydl_opts = { - 'outtmpl': f'{title}.%(ext)s', + 'outtmpl': f'%(title)s.%(ext)s', 'format': 'bestaudio/best', + 'addmetadata': True, + 'writethumbnail': True, + 'prefer_ffmpeg': True, + "extractaudio": True, + 'geo_bypass': True, + 'nocheckcertificate': True, 'postprocessors': [ { 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '320', - } + }, + {'key': 'EmbedThumbnail'}, + {'key': 'FFmpegMetadata'}, ], + 'quiet': True, + 'logtostderr': False, } with YoutubeDL(ydl_opts) as ydl: + try: + video_info = ydl.extract_info(url, False) + title = video_info['title'] + uploader = video_info['uploader'] + except BaseException as e: + return edit(message, get_translation('banError', ['`', '**', e])) + + edit(message, get_translation('downloadYTAudio', ['**', title, '`'])) + ydl.download([url]) edit(message, f'`{get_translation("uploadMedia")}`') reply_audio( message, f'{title}.mp3', caption=f"**{get_translation('videoUploader')}** `{uploader}`", + duration=int(video_info['duration']), delete_orig=True, delete_file=True, ) diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 58bc8fd..ec4f0ed 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -34,12 +34,21 @@ def reply_img( def reply_audio( - message, audio, caption='', fix_markdown=False, delete_orig=False, delete_file=False + message, + audio, + caption='', + duration='', + fix_markdown=False, + delete_orig=False, + delete_file=False, ): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - message.reply_audio(audio, caption=caption.strip()) + if not duration: + message.reply_audio(audio, caption=caption.strip()) + else: + message.reply_audio(audio, caption=caption.strip(), duration=int(duration)) if delete_orig: message.delete() if delete_file: @@ -52,6 +61,8 @@ def reply_video( message, video, caption='', + duration='', + thumb=None, fix_markdown=False, delete_orig=False, delete_file=False, @@ -60,12 +71,23 @@ def reply_video( try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - message.reply_video(video, caption=caption.strip(), parse_mode=parse) + if not duration: + message.reply_video( + video, caption=caption.strip(), parse_mode=parse, thumb=thumb + ) + else: + message.reply_video( + video, + caption=caption.strip(), + duration=int(duration), + parse_mode=parse, + thumb=thumb, + ) if delete_orig: message.delete() if delete_file: remove(video) - except BaseException: + except BaseException as e: pass From ebc73d47a8f55e13bb66e983b7801e06072c552f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sat, 22 May 2021 13:33:14 +0300 Subject: [PATCH 054/242] seden: youtubedl: Fix mistake Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/youtubedl.py | 7 ++++--- sedenecem/core/replier.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index e479759..71656f0 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -8,8 +8,8 @@ # All rights reserved. See COPYING, AUTHORS. # - from io import BytesIO +from os import remove from PIL import Image from requests import get @@ -70,8 +70,8 @@ def youtubedl(message): temp.seek(0) im = Image.open(temp) imc = im.convert('RGB') - imc.save(thumb_path := f'downloads/{video_info["id"]}.jpg') - except BaseException as e: + imc.save(thumb_path := f'{video_info["id"]}.jpg') + except BaseException: thumb_path = None ydl.download([url]) @@ -86,6 +86,7 @@ def youtubedl(message): delete_orig=True, delete_file=True, ) + remove(f'{video_info["id"]}.jpg') elif util == 'mp3': ydl_opts = { diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index ec4f0ed..2d3b451 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -87,7 +87,7 @@ def reply_video( message.delete() if delete_file: remove(video) - except BaseException as e: + except BaseException: pass From 7b9e0c2e32e4fc4e43546d6ff6093cc9808477bb Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sun, 23 May 2021 15:48:58 +0300 Subject: [PATCH 055/242] seden: Create downloads dir * In this way, our issues with download directory will be fixed Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/__init__.py | 5 +---- sedenbot/modules/profile.py | 7 ++++--- sedenbot/modules/remove_bg.py | 13 ++++++++++--- sedenbot/modules/updater.py | 4 ---- sedenbot/modules/youtubedl.py | 5 +++-- sedenecem/core/misc.py | 7 +++++++ 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 413ea8f..e847811 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -161,9 +161,6 @@ def set_logger(): # SQL Database URL DATABASE_URL = environ.get('DATABASE_URL', None) -# Download directory -DOWNLOAD_DIRECTORY = environ.get('DOWNLOAD_DIRECTORY', './downloads') - # SedenBot Session SESSION = environ.get('SESSION', 'sedenuserbot') @@ -274,7 +271,7 @@ def export_session_string(self): SESSION, api_id=API_ID, api_hash=API_HASH, - app_version=f'Seden UserBot', + app_version='Seden UserBot', device_model='DerUntergang', system_version=f'v{BOT_VERSION}', lang_code='tr', diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 12b3fc9..328a7b3 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -18,6 +18,7 @@ download_media_wc, edit, extract_args, + get_download_dir, get_translation, sedenify, send_log, @@ -80,7 +81,7 @@ def set_profilepic(client, message): maxSize = (640, 640) ratio = min(maxSize[0] / width, maxSize[1] / height) image = image.resize((int(width * ratio), int(height * ratio))) - new_photo = 'downloads/profile_photo_new.png' + new_photo = f'{get_download_dir()}/profile_photo_new.png' image.save(new_photo) client.set_profile_photo(photo=new_photo) remove(photo) @@ -90,9 +91,9 @@ def set_profilepic(client, message): edit(message, f'`{PP_ERROR}`') -@sedenify(pattern=r'^.delpfp', compat=False) +@sedenify(pattern='^.delpfp', compat=False) def remove_profilepic(client, message): - group = message.text[8:] + group = extract_args(message) if group == 'all': lim = 0 elif group.isdigit(): diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 695fb0c..dc14f7f 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -10,8 +10,15 @@ from os import path, remove from removebg import RemoveBg -from sedenbot import DOWNLOAD_DIRECTORY, HELP, RBG_APIKEY -from sedenecem.core import download_media_wc, edit, get_translation, reply_doc, sedenify +from sedenbot import HELP, RBG_APIKEY +from sedenecem.core import ( + download_media_wc, + edit, + get_download_dir, + get_translation, + reply_doc, + sedenify, +) @sedenify(pattern='^.rbg$') @@ -32,7 +39,7 @@ def rbg(message): edit(message, f'`{get_translation("rbgUsage")}`') return - IMG_PATH = f'{DOWNLOAD_DIRECTORY}/image.png' + IMG_PATH = f'{get_download_dir()}/image.png' if path.exists(IMG_PATH): remove(IMG_PATH) diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index 82d971a..dfb556a 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -152,10 +152,6 @@ def upstream(ups): repo.__del__() return edit(ups, f'`{get_translation("updateComplete")}`') - try: - heroku_app.scale_formation_process('seden', 1) - except BaseException: - pass else: try: ups_rem.pull(ac_br) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 71656f0..5b7df06 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -17,6 +17,7 @@ from sedenecem.core import ( edit, extract_args, + get_download_dir, get_translation, reply_audio, reply_video, @@ -70,7 +71,7 @@ def youtubedl(message): temp.seek(0) im = Image.open(temp) imc = im.convert('RGB') - imc.save(thumb_path := f'{video_info["id"]}.jpg') + imc.save(thumb_path := f'{get_download_dir()}/{video_info["id"]}.jpg') except BaseException: thumb_path = None @@ -86,7 +87,7 @@ def youtubedl(message): delete_orig=True, delete_file=True, ) - remove(f'{video_info["id"]}.jpg') + remove(thumb_path) elif util == 'mp3': ydl_opts = { diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 18bf24c..86ca257 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -7,6 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # +from os import makedirs from re import escape, sub from pyrogram.types import Message @@ -354,3 +355,9 @@ def is_admin(message): user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) return user.status in _admin_status_list + + +def get_download_dir() -> str: + dir = './downloads' + makedirs(dir, exist_ok=True) + return dir From 1171803ddce7fce9ae6f167313ea5a90371dd69e Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Wed, 26 May 2021 01:52:31 +0300 Subject: [PATCH 056/242] seden: youtubedl: Add Exception Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/youtubedl.py | 17 ++++++++++++++--- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 5b7df06..d413fbd 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -58,6 +58,10 @@ def youtubedl(message): video_info = ydl.extract_info(url, False) title = video_info['title'] uploader = video_info['uploader'] + duration = int(video_info['duration']) + except KeyError: + uploader = get_translation('notFound') + duration = None except BaseException as e: return edit(message, get_translation('banError', ['`', '**', e])) @@ -83,11 +87,14 @@ def youtubedl(message): f'{video_info["id"]}.mp4', thumb=thumb_path, caption=f"**{get_translation('videoTitle')}** `{title}`\n**{get_translation('videoUploader')}** `{uploader}`", - duration=int(video_info['duration']), + duration=duration, delete_orig=True, delete_file=True, ) - remove(thumb_path) + try: + remove(thumb_path) + except BaseException: + pass elif util == 'mp3': ydl_opts = { @@ -116,6 +123,10 @@ def youtubedl(message): video_info = ydl.extract_info(url, False) title = video_info['title'] uploader = video_info['uploader'] + duration = int(video_info['duration']) + except KeyError: + uploader = get_translation('notFound') + duration = None except BaseException as e: return edit(message, get_translation('banError', ['`', '**', e])) @@ -127,7 +138,7 @@ def youtubedl(message): message, f'{title}.mp3', caption=f"**{get_translation('videoUploader')}** `{uploader}`", - duration=int(video_info['duration']), + duration=duration, delete_orig=True, delete_file=True, ) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index b925f2d..85fbca3 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -314,6 +314,7 @@ "noNote": "No notes found in this chat", "noSnip": "No snip is currently available.", "nonSqlMode": "Running on Non-SQL mode!", + "notFound": "Not Found", "notHeroku": "Bot is not running on Heroku", "notSet": "Not set!", "noteError": "Message couldn't be forwarded and note couldn't be added.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index bd57272..3f5ee06 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -316,6 +316,7 @@ "noNote": "Bu sohbette kaydedilmi\u015f not bulunamad\u0131", "noSnip": "No snip is currently available.", "nonSqlMode": "SQL d\u0131\u015f\u0131 modda \u00e7al\u0131\u015f\u0131yorum, bunu ger\u00e7ekle\u015ftiremem", + "notFound": "Bulunamad\u0131", "notHeroku": "Bot \u015fu an Heroku'da \u00e7al\u0131\u015fm\u0131yor", "notSet": "Ayarlanmam\u0131\u015f!", "noteError": "Mesaj y\u00f6nlendirilemedi ve not eklenemedi.", From b4daea14df327798a7f67142823eacbe6074b950 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sun, 30 May 2021 23:54:43 +0300 Subject: [PATCH 057/242] seden: Minor changes Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/__init__.py | 1 - sedenbot/modules/stickers.py | 50 +++++++++++++++++++++--------------- sedenecem/core/image.py | 4 ++- sedenecem/translator/en.json | 3 +-- sedenecem/translator/tr.json | 3 +-- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index e847811..a4d6ef9 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -300,7 +300,6 @@ def __import_modules(): LOGS.info(get_translation('loadedModules', [modules])) for module in modules: try: - LOGS.info(get_translation('loadedModules2', [module])) import_module(f'sedenbot.modules.{module}') except Exception: if LOG_VERBOSE: diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 023c422..d161688 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -11,7 +11,6 @@ from pyrogram.raw.functions.messages import GetStickerSet from pyrogram.raw.types import InputStickerSetShortName -from requests import get from sedenbot import HELP, PACKNAME, PACKNICK, TEMP_SETTINGS from sedenecem.core import ( PyroConversation, @@ -57,43 +56,42 @@ def kang(client, message): if len(args) < 1: args = '1' - emoji = '🤤' + emoji = reply.sticker.emoji if reply.sticker and reply.sticker.emoji else '🤤' pack = 1 for item in args.split(): if item.isdigit(): pack = int(item) args = args.replace(item, '').strip() - elif 'e=' in item.lower(): - emoji = item.replace('e=', '') - args = args.replace(item, '').strip() + else: + emoji = args.strip() ptime = time() pname = f'PNAME_{ptime}' pnick = f'PNICK_{ptime}' - if anim: - pname += '_anim' - pnick += ' (Animated)' + name_suffix = ('_anim', ' (Animated)') if anim else ('', '') TEMP_SETTINGS[pname] = ( PACKNAME.replace(' ', '') if PACKNAME - else f'a{myacc.id}_by_{myacc.username}_{pack}' + else f'a{myacc.id}_by_{myacc.username}_{pack}{name_suffix[0]}' ) TEMP_SETTINGS[f'{pname}_TEMPLATE'] = f'a{myacc.id}_by_{myacc.username}_' - TEMP_SETTINGS[pnick] = PACKNICK or f'{kanger}\'s UserBot pack {pack}' + TEMP_SETTINGS[pnick] = ( + PACKNICK or f'{kanger}\'s UserBot pack {pack}{name_suffix[1]}' + ) TEMP_SETTINGS[f'{pnick}_TEMPLATE'] = f'{kanger}\'s UserBot pack ' limit = '50' if anim else '120' - def pack_created(name): + def pack_created(pname): try: set_name = InputStickerSetShortName(short_name=TEMP_SETTINGS[pname]) set = GetStickerSet(stickerset=set_name) client.send(data=set) return True - except BaseException as e: + except BaseException: return False def create_new(conv, pack, pname, pnick): @@ -106,8 +104,12 @@ def create_new(conv, pack, pname, pnick): msg = send_recv(conv, TEMP_SETTINGS[pnick]) if msg.text == 'Invalid pack selected.': pack += 1 - TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" - TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" + TEMP_SETTINGS[ + pname + ] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}{name_suffix[0]}" + TEMP_SETTINGS[ + pnick + ] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}{name_suffix[1]}" return create_new(conv, pack, pname, pnick) msg = send_recv(conv, media, doc=True) if 'Sorry, the file type is invalid.' in msg.text: @@ -119,10 +121,14 @@ def create_new(conv, pack, pname, pnick): send_recv(conv, f'<{TEMP_SETTINGS[pnick]}>') send_recv(conv, '/skip') ret = send_recv(conv, TEMP_SETTINGS[pname]) - while "already taken" in ret.text: + while 'already taken' in ret.text: pack += 1 - TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" - TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" + TEMP_SETTINGS[ + pname + ] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}{name_suffix[0]}" + TEMP_SETTINGS[ + pnick + ] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}{name_suffix[1]}" ret = send_recv(conv, TEMP_SETTINGS[pname]) return True @@ -136,8 +142,12 @@ def add_exist(conv, pack, pname, pnick): if limit in status.text: pack += 1 - TEMP_SETTINGS[pname] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}" - TEMP_SETTINGS[pnick] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}" + TEMP_SETTINGS[ + pname + ] = f"{TEMP_SETTINGS[f'{pname}_TEMPLATE']}{pack}{name_suffix[0]}" + TEMP_SETTINGS[ + pnick + ] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}{name_suffix[1]}" edit(message, get_translation('packFull', ['`', '**', str(pack)])) if pack_created(pname): return add_exist(conv, pack, pname, pnick) @@ -149,7 +159,7 @@ def add_exist(conv, pack, pname, pnick): send_recv(conv, '/done') return True - if not (anim and reply.sticker): + if not reply.sticker: media = resizer(media) with PyroConversation(client, 'Stickers') as conv: diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index f4cf32e..10de538 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -11,6 +11,8 @@ from PIL import Image +from .misc import get_download_dir + def sticker_resize(photo): image = Image.open(photo) @@ -33,6 +35,6 @@ def sticker_resize(photo): maxsize = (512, 512) image.thumbnail(maxsize) - temp = 'temp.png' + temp = f'{get_download_dir()}/temp.png' image.save(temp, 'PNG') return temp diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 85fbca3..d3716ca 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -267,8 +267,7 @@ "lastfmProcess2": "[%1](%2) %3was last listening to:%3\n\n", "lfyResult": "Here you are, help yourself.", "listEmpty": "The list is empty!", - "loadedModules": "Modules to be loaded: %1", - "loadedModules2": "Loaded module: %1", + "loadedModules": "Modules to be loaded:\n%1", "loadedModulesError": "An error occurred while loading %1", "lockAll": "all", "lockError": "%1Invalid media type:%1 %2", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 3f5ee06..c117192 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -269,8 +269,7 @@ "lastfmProcess2": "[%1](%2) %3en son \u015funu dinledi:%3\n\n", "lfyResult": "\u0130\u015fte, keyfine bak.", "listEmpty": "Liste bo\u015f!", - "loadedModules": "Y\u00fcklenecek mod\u00fcller: %1", - "loadedModules2": "Y\u00fcklenen mod\u00fcl: %1", + "loadedModules": "Y\u00fcklenecek mod\u00fcller:\n%1", "loadedModulesError": "%1 mod\u00fcl\u00fc y\u00fcklenirken bir hata olu\u015ftu.", "lockAll": "her \u015fey", "lockError": "%1Ge\u00e7ersiz medya tipi:%1 %2", From 241e6c3383d2b042b346e5c0a2ff756621942b0b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Tue, 1 Jun 2021 16:50:54 +0300 Subject: [PATCH 058/242] seden: stickers: Add exception Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/stickers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index d161688..ece48a4 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -10,6 +10,7 @@ from random import choice from pyrogram.raw.functions.messages import GetStickerSet +from pyrogram.errors import YouBlockedUser from pyrogram.raw.types import InputStickerSetShortName from sedenbot import HELP, PACKNAME, PACKNICK, TEMP_SETTINGS from sedenecem.core import ( @@ -44,6 +45,7 @@ def kang(client, message): anim = False media = None + chat = 'Stickers' if reply.photo or reply.document or reply.sticker: edit(message, f'`{choice(DIZCILIK)}`') @@ -162,8 +164,11 @@ def add_exist(conv, pack, pname, pnick): if not reply.sticker: media = resizer(media) - with PyroConversation(client, 'Stickers') as conv: - send_recv(conv, '/cancel') + with PyroConversation(client, chat) as conv: + try: + send_recv(conv, '/cancel') + except YouBlockedUser: + return edit(message, get_translation('unblockChat', ['**', '`', chat])) if pack_created(pname): ret = add_exist(conv, pack, pname, pnick) if not ret: From 83b57a956799b853d4f6d43fd7ffbe64b41c3a06 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Thu, 3 Jun 2021 22:17:00 +0300 Subject: [PATCH 059/242] seden: stickers: Fix sticker error func Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/stickers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index ece48a4..9eeb37f 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -104,7 +104,7 @@ def create_new(conv, pack, pname, pnick): except Exception as e: raise e msg = send_recv(conv, TEMP_SETTINGS[pnick]) - if msg.text == 'Invalid pack selected.': + if 'Invalid pack selected.' in msg.text: pack += 1 TEMP_SETTINGS[ pname @@ -114,7 +114,7 @@ def create_new(conv, pack, pname, pnick): ] = f"{TEMP_SETTINGS[f'{pnick}_TEMPLATE']}{pack}{name_suffix[1]}" return create_new(conv, pack, pname, pnick) msg = send_recv(conv, media, doc=True) - if 'Sorry, the file type is invalid.' in msg.text: + if 'Sorry' in msg.text: edit(message, f'`{get_translation("stickerError")}`') return send_recv(conv, emoji) @@ -156,7 +156,10 @@ def add_exist(conv, pack, pname, pnick): else: return create_new(conv, pack, pname, pnick) - send_recv(conv, media, doc=True) + status = send_recv(conv, media, doc=True) + if 'Sorry' in status.text: + edit(message, f'`{get_translation("stickerError")}`') + return send_recv(conv, emoji) send_recv(conv, '/done') return True @@ -204,7 +207,7 @@ def getsticker(message): reply, photo, caption=f'**Sticker ID:** `{reply.sticker.file_id}' - f'`\n**Emoji**: `{reply.sticker.emoji}`', + f'`\n**Emoji**: `{reply.sticker.emoji or get_translation("notSet")}`', delete_after_send=True, ) message.delete() From 50e485f5c038fda8231a47411df95afdb2bd4722 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Thu, 3 Jun 2021 22:38:26 +0300 Subject: [PATCH 060/242] seden: Boredom * setgpic command added * invitelink command added * ginfo command added * whois module merged into info I don't remember if there is any other change(s) Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/ban.py | 55 ++++++++++++++++- sedenbot/modules/{whois.py => info.py} | 82 +++++++++++++++++++++++++- sedenbot/modules/misc.py | 11 +++- sedenbot/modules/profile.py | 42 ++++++------- sedenbot/modules/sangmata.py | 2 +- sedenecem/translator/en.json | 10 +++- sedenecem/translator/tr.json | 10 +++- 7 files changed, 178 insertions(+), 34 deletions(-) rename sedenbot/modules/{whois.py => info.py} (60%) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 1282448..126f5fb 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -7,19 +7,28 @@ # All rights reserved. See COPYING, AUTHORS. # +from os import remove from time import sleep -from pyrogram.errors import MessageTooLong, UserAdminInvalid +from PIL import Image +from pyrogram.errors import ( + ImageProcessFailed, + MessageTooLong, + PhotoCropSizeSmall, + UserAdminInvalid, +) from pyrogram.types import ChatPermissions from sedenbot import BRAIN, HELP from sedenecem.core import ( + download_media_wc, edit, extract_args, + get_download_dir, get_translation, + is_admin, reply_doc, sedenify, send_log, - is_admin, ) from sedenecem.sql import mute_sql as sql @@ -518,6 +527,48 @@ def zombie_accounts(client, message): ) +@sedenify(pattern='^.setgpic$', compat=False, admin=True, private=False) +def set_group_photo(client, message): + reply = message.reply_to_message + photo = None + if ( + reply + and reply.media + and ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ) + ): + photo = download_media_wc(reply, 'group_photo.jpg') + else: + edit(message, f'{get_translation("mediaInvalid")}`') + return + + if photo: + image = Image.open(photo) + width, height = image.size + maxSize = (640, 640) + ratio = min(maxSize[0] / width, maxSize[1] / height) + image = image.resize((int(width * ratio), int(height * ratio))) + new_photo = f'{get_download_dir()}/group_photo_new.png' + image.save(new_photo) + try: + client.set_chat_photo(chat_id=message.chat.id, photo=new_photo) + remove(photo) + remove(new_photo) + edit(message, f'`{get_translation("groupPicChanged")}`') + except PhotoCropSizeSmall: + edit(message, f'`{get_translation("ppSmall")}`') + except ImageProcessFailed: + edit(message, f'`{get_translation("ppError")}`') + except BaseException as e: + edit(message, get_translation('banError', ['`', '**', e])) + return + else: + edit(message, f'`{get_translation("ppError")}`') + + @sedenify(incoming=True, outgoing=False, compat=False) def mute_check(client, message): muted = sql.is_muted(message.chat.id, message.from_user.id) diff --git a/sedenbot/modules/whois.py b/sedenbot/modules/info.py similarity index 60% rename from sedenbot/modules/whois.py rename to sedenbot/modules/info.py index 8d9a198..7643022 100644 --- a/sedenbot/modules/whois.py +++ b/sedenbot/modules/info.py @@ -7,6 +7,8 @@ # All rights reserved. See COPYING, AUTHORS. # +from pyrogram.errors import PeerIdInvalid +from pyrogram.raw.functions.messages import GetOnlines from sedenbot import BLACKLIST, BRAIN, HELP from sedenecem.core import ( download_media_wc, @@ -93,6 +95,7 @@ def who_is(client, message): if photo and media_perm: reply_img(reply or message, photo, caption=caption, delete_file=True) + message.delete() else: return edit(message, caption) @@ -122,4 +125,81 @@ def BlacklistCheck(user_id): return get_translation('blacklistCheck') -HELP.update({'whois': get_translation('whoisInfo')}) +@sedenify(pattern='^.ginfo', compat=False) +def get_chat_info(client, message): + args = extract_args(message) + reply = message.reply_to_message + group_id = message.chat.id + edit(message, f'`{get_translation("processing")}`') + + try: + reply_chat = client.get_chat(args or group_id) + peer = client.resolve_peer(args or group_id) + except PeerIdInvalid: + edit(message, f'`{get_translation("groupNotFound")}`') + return + + media_perm = True + if 'group' in message.chat.type: + perm = message.chat.permissions + media_perm = perm.can_send_media_messages + + try: + online_users = client.send(GetOnlines(peer=peer)) + online = online_users.onlines + except PeerIdInvalid: + edit(message, f'`{get_translation("groupNotFound")}`') + return + + try: + group_photo = reply_chat.photo.big_file_id + photo = download_media_wc(group_photo, 'photo.png') + except BaseException: + photo = None + pass + + title = reply_chat.title or get_translation('notSet') + username = ( + f'**@{reply_chat.username}**' + if reply_chat.username + else f'`{get_translation("notFound")}`' + ) + chat_id = reply_chat.id + dc_id = reply_chat.dc_id or get_translation('notFound') + group_type = reply_chat.type + sticker_pack = ( + f'**[Pack](https://t.me/addstickers/{reply_chat.sticker_set_name})**' + if reply_chat.sticker_set_name + else f'`{get_translation("notSet")}`' + ) + members = reply_chat.members_count + description = ( + f'\n{reply_chat.description}' + if reply_chat.description + else get_translation('notSet') + ) + + caption = get_translation( + 'groupinfoResult', + [ + '**', + '`', + title, + chat_id, + dc_id, + group_type, + members, + online, + sticker_pack, + username, + description, + ], + ) + if photo and media_perm: + reply_img(reply or message, photo, caption=caption, delete_file=True) + message.delete() + else: + edit(message, caption, preview=False) + + +HELP.update({'info': get_translation('groupInfo')}) diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index d531cf7..78cf418 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -14,7 +14,6 @@ from image_to_ascii import ImageToAscii from pybase64 import b64decode, b64encode -from requests import post from sedenbot import HELP, SUPPORT_GROUP from sedenecem.core import ( download_media_wc, @@ -43,6 +42,16 @@ def chatid(message): edit(message, get_translation('chatidResult', ['`', str(message.chat.id)])) +@sedenify(pattern='^.invitelink$', compat=False, admin=True, private=False) +def get_invite_link(client, message): + chat = client.get_chat(message.chat.id) + try: + url = chat.invite_link + edit(message, url, preview=False) + except BaseException: + pass + + @sedenify(pattern='^.id$') def userid(message): reply = message.reply_to_message diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 328a7b3..d8425cd 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -12,7 +12,8 @@ from PIL import Image from pyrogram.errors import UsernameOccupied -from pyrogram.raw.functions import account, channels +from pyrogram.raw.functions.account import UpdateProfile, UpdateStatus, UpdateUsername +from pyrogram.raw.functions.channels import GetAdminedPublicChannels from sedenbot import HELP, TEMP_SETTINGS from sedenecem.core import ( download_media_wc, @@ -25,22 +26,13 @@ ) # ====================== CONSTANT =============================== -INVALID_MEDIA = get_translation('mediaInvalid') -PP_CHANGED = get_translation('ppChanged') -PP_ERROR = get_translation('ppError') - -BIO_SUCCESS = get_translation('bioSuccess') -NAME_OK = get_translation('nameOk') - -USERNAME_SUCCESS = get_translation('usernameSuccess') -USERNAME_TAKEN = get_translation('usernameTaken') ALWAYS_ONLINE = 'offline' # =============================================================== @sedenify(pattern='^.reserved$', compat=False) def reserved(client, message): - sonuc = client.send(channels.GetAdminedPublicChannels()) + sonuc = client.send(GetAdminedPublicChannels()) mesaj = '' for channel_obj in sonuc.chats: mesaj += f'{channel_obj.title}\n@{channel_obj.username}\n\n' @@ -58,8 +50,8 @@ def name(client, message): firstname = namesplit[0] lastname = namesplit[1] - client.send(account.UpdateProfile(first_name=firstname, last_name=lastname)) - edit(message, f'`{NAME_OK}`') + client.send(UpdateProfile(first_name=firstname, last_name=lastname)) + edit(message, f'`{get_translation("nameOk")}`') @sedenify(pattern='^.setpfp$', compat=False) @@ -69,11 +61,15 @@ def set_profilepic(client, message): if ( reply and reply.media - and (reply.photo or (reply.document and 'image' in reply.document.mime_type)) + and ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ) ): photo = download_media_wc(reply, 'profile_photo.jpg') else: - edit(message, f'`{INVALID_MEDIA}`') + edit(message, f'`{get_translation("mediaInvalid")}`') if photo: image = Image.open(photo) @@ -86,9 +82,9 @@ def set_profilepic(client, message): client.set_profile_photo(photo=new_photo) remove(photo) remove(new_photo) - edit(message, f'`{PP_CHANGED}`') + edit(message, f'`{get_translation("ppChanged")}`') else: - edit(message, f'`{PP_ERROR}`') + edit(message, f'`{get_translation("ppError")}`') @sedenify(pattern='^.delpfp', compat=False) @@ -111,18 +107,18 @@ def remove_profilepic(client, message): @sedenify(pattern='^.setbio', compat=False) def setbio(client, message): newbio = extract_args(message) - client.send(account.UpdateProfile(about=newbio)) - edit(message, BIO_SUCCESS) + client.send(UpdateProfile(about=newbio)) + edit(message, f'`{get_translation("bioSuccess")}`') @sedenify(pattern='^.username', compat=False) def username(client, message): newusername = extract_args(message) try: - client.send(account.UpdateUsername(username=newusername)) - edit(message, f'`{USERNAME_SUCCESS}`') + client.send(UpdateUsername(username=newusername)) + edit(message, f'`{get_translation("usernameSuccess")}`') except UsernameOccupied: - edit(message, f'`{USERNAME_TAKEN}`') + edit(message, f'`{get_translation("usernameTaken")}`') @sedenify(pattern='^.block$', compat=False) @@ -198,7 +194,7 @@ def online(client, message): while ALWAYS_ONLINE in TEMP_SETTINGS: try: - client.send(account.UpdateStatus(offline=False)) + client.send(UpdateStatus(offline=False)) sleep(5) except BaseException: return diff --git a/sedenbot/modules/sangmata.py b/sedenbot/modules/sangmata.py index 32b90a2..0fe61f1 100644 --- a/sedenbot/modules/sangmata.py +++ b/sedenbot/modules/sangmata.py @@ -36,7 +36,7 @@ def sangmata(client, message): if not response: edit(message, f'`{get_translation("answerFromBot")}`') - elif response.text and response.text.startswith('Forward'): + elif 'Forward' in response.text: edit(message, f'`{get_translation("privacySettings")}`') else: edit(message, response.text) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index d3716ca..a93c602 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -1,6 +1,6 @@ { "added": "added", - "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.", + "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.\n\n.setgpic\nUsage: Changes group picture.", "adminUsage": "I'm not admin!", "adminlist": "%1Admins in%1 %2%3%2%1:%1", "afkEnd": "I'm no longer AFK.", @@ -237,7 +237,11 @@ "googleInfo": ".google <query>\nUsage: Does a search on Google.", "googleLog": "Google Search query %1 was executed successfully", "googleResult": "%1Search Query:%1\n%2%3%2\n\n%1Results:%1\n%4", + "groupInfo": ".whois <username/user id>\nUsage: Retrieves user's information.\n\n.ginfo [optional: <group id/group link (with @)>]\nUsage: Gives information about the group.", + "groupNotFound": "Chat not found!", + "groupPicChanged": "Group picture changed successfully..", "groupUsage": "This command can only be used on groups.", + "groupinfoResult": "%1GROUP INFO\n\nGroup Name:%1 %2%3%2\n%1Group ID:%1 %2%4%2\n%1DC ID:%1 %2%5%2\n%1Group Type:%1 %2%6%2\n%1Members:%1 %2%7%2\n%1Online Members:%1 %2%8%2\n%1Group Sticker Pack:%1 %9\n\n%1Group Link:%1 %10\n%1Group Description:%1 %2%11%2", "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", @@ -302,7 +306,7 @@ "mediaInvalid": "Invalid media.", "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", @@ -371,6 +375,7 @@ "ppChanged": "Profile picture changed successfully..", "ppDeleted": "%1 profile photos were deleted", "ppError": "Failure occured while processing image.", + "ppSmall": "This image is too small!", "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", "processing": "Processing...", @@ -602,7 +607,6 @@ "weatherErrorCity": "Specify a city as default with the WEATHER variable, or specify which city you want the weather when typing the command!", "weatherErrorServer": "Weather information couldn't be retrieved.", "whoisError": "Failed to fetching user..", - "whoisInfo": ".whois\nUsage: Retrieves user's information.", "whoisProcess": "Fetching user information..", "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Is Bot:%1 %2%9%2\n%1Is Restricted:%1 %2%10%2\n%1Is Verified by Telegram:%1 %2%11%2\n%1Common chats:%1 %2%12%2\n\n%1Bio:%1 %2%13%2\n%1Last Seen:%1 %2%14%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%15\n%16%1", "wikiError": "Disambiguated page found.\n\n%1", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index c117192..09f25e9 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -1,6 +1,6 @@ { "added": "eklendi", - "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.", + "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.\n\n.setgpic\nKullan\u0131m: Grubun foto\u011fraf\u0131n\u0131 de\u011fi\u015ftirir.", "adminUsage": "Y\u00f6netici de\u011filim!", "adminlist": "%2%3%2 %1sohbetinde bulunan y\u00f6neticiler:%1", "afkEnd": "Art\u0131k AFK de\u011filim.", @@ -238,7 +238,11 @@ "googleInfo": ".google <kelime>\nKullan\u0131m: H\u0131zl\u0131 bir Google aramas\u0131 yapar.", "googleLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla Google'da arat\u0131ld\u0131!", "googleResult": "%1Arama Sorgusu:%1\n%2%3%2\n\n%1Sonu\u00e7lar:%1\n%4", + "groupInfo": ".whois <kullan\u0131c\u0131 ad\u0131/kullan\u0131c\u0131 id>\nKullan\u0131m: Kullan\u0131c\u0131 hakk\u0131nda bilgi verir.\n\n.ginfo [iste\u011fe ba\u011fl\u0131: <grup id/grup linki (@ ile)>]\nKullan\u0131m: Verilen grup hakk\u0131nda bilgi verir.", + "groupNotFound": "Grup bulunamad\u0131!", + "groupPicChanged": "Grup resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", "groupUsage": "Bu komut sadece gruplarda kullan\u0131labilir.", + "groupinfoResult": "%1GRUP BILGISI\n\nGrup \u0130smi:%1 %2%3%2\n%1Grup ID:%1 %2%4%2\n%1DC ID:%1 %2%5%2\n%1Grup T\u00fcr\u00fc:%1 %2%6%2\n%1\u00dcye Say\u0131s\u0131:%1 %2%7%2\n%1\u00c7evrimi\u00e7i \u00dcye Say\u0131s\u0131:%1 %2%8%2\n%1Grup \u00c7\u0131kartma Paketi:%1 %9\n\n%1Grup Ba\u011flant\u0131s\u0131:%1 %10\n%1Grup A\u00e7\u0131klamas\u0131:%1 %2%11%2", "hashInfo": ".hash\nKullan\u0131m: Bir txt dosyas\u0131 yaz\u0131ld\u0131\u011f\u0131nda md5, sha1, sha256, sha512 dizelerini bulun.", "herokuInfo": ".kota\nKullan\u0131m: Heroku kaynak kullan\u0131m\u0131n\u0131z\u0131 g\u00f6sterir.\n\n.drestart\nKullan\u0131m: Heroku dynosunu yeniden ba\u015flat\u0131r.\n\n.logs\nKullan\u0131m: Heroku uygulama g\u00fcnl\u00fc\u011f\u00fc g\u00f6nderir.", "herokuQuotaInHM": "%1s %2d", @@ -304,7 +308,7 @@ "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.\n\n.amogus <metin>\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", - "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.", + "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.\n\n.invitelink\nKullan\u0131m: Mevcut grubun davet ba\u011flant\u0131s\u0131 verir.", "mockUsage": "bANa bIr mETin vEr!", "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", "muteProcess": "Sessize al\u0131n\u0131yor...", @@ -372,6 +376,7 @@ "ppChanged": "Profil resmi ba\u015far\u0131yla de\u011fi\u015ftirildi.", "ppDeleted": "%1 adet profil foto\u011fraf\u0131 silindi.", "ppError": "Resim i\u015flenirken bir hata olu\u015ftu.", + "ppSmall": "G\u00f6r\u00fcnt\u00fc \u00e7ok k\u00fc\u00e7\u00fck!", "privacySettings": "Gizlilik ayarlar\u0131 bunu yapmama engel oldu.", "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", "processing": "\u0130\u015fleniyor...", @@ -602,7 +607,6 @@ "weatherErrorCity": "WEATHER de\u011fi\u015fkeniyle bir \u015fehri varsay\u0131lan olarak belirt, ya da komutu yazarken hangi \u015fehrin hava durumunu istedi\u011fini de belirt!", "weatherErrorServer": "Hava durumu bilgisi al\u0131namad\u0131.", "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", - "whoisInfo": ".whois\nKullan\u0131m: Kullan\u0131c\u0131n\u0131n bilgilerini al\u0131r.", "whoisProcess": "Kullan\u0131c\u0131 bilgisi getiriliyor..", "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Bot Mu:%1 %2%9%2\n%1K\u0131s\u0131tl\u0131 M\u0131:%1 %2%10%2\n%1Telegram Taraf\u0131ndan Onayl\u0131 M\u0131:%1 %2%11%2\n%1Ortak Sohbetler:%1 %2%12%2\n\n%1Biyografi:%1 %2%13%2\n%1Son G\u00f6r\u00fclme:%1 %2%14%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%15\n%16%1", "wikiError": "Belirsiz bir sayfa bulundu.\n\n%1", From dc916c8731010860b5bc8855659059e5be8ec7c9 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Tue, 8 Jun 2021 20:18:04 +0300 Subject: [PATCH 061/242] seden: Minor changes * Improved video upload on updown module * RemoveBG now support stickers * A little fix for youtubedl module * Updated translations Also Docker updated Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/remove_bg.py | 24 ++++++++++++++++++++---- sedenbot/modules/updown.py | 6 +++++- sedenbot/modules/youtubedl.py | 4 ++-- sedenecem/core/replier.py | 8 +++++++- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index dc14f7f..9942b28 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -9,6 +9,7 @@ from os import path, remove +from PIL import Image from removebg import RemoveBg from sedenbot import HELP, RBG_APIKEY from sedenecem.core import ( @@ -31,8 +32,14 @@ def rbg(message): ) reply = message.reply_to_message - if reply and ( - reply.photo or (reply.document and 'image' in reply.document.mime_type) + if ( + reply + and reply.media + and ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ) ): edit(message, f'`{get_translation("processing")}`') else: @@ -45,14 +52,23 @@ def rbg(message): remove(IMG_PATH) download_media_wc(reply, IMG_PATH) edit(message, f'`{get_translation("rbgProcessing")}`') + try: + image = Image.open(IMG_PATH) + width, height = image.size + maxSize = (1920, 1080) + ratio = min(maxSize[0] / width, maxSize[1] / height) + image = image.resize((int(width * ratio), int(height * ratio))) + new_photo = f'{get_download_dir()}/photo.png' + image.save(new_photo) remove_bg = RemoveBg(RBG_APIKEY, get_translation('rbgLog')) - remove_bg.remove_background_from_img_file(IMG_PATH) - rbg_img = f'{IMG_PATH}_no_bg.png' + remove_bg.remove_background_from_img_file(new_photo) + rbg_img = f'{new_photo}_no_bg.png' reply_doc( reply, rbg_img, caption=get_translation('rbgResult'), delete_after_send=True ) message.delete() + remove(new_photo) except Exception as e: return edit(message, get_translation('banError', ['`', '**', e])) diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index ff136f2..55b14cc 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -16,6 +16,7 @@ edit, extract_args, get_translation, + reply_video, reply_doc, sedenify, ) @@ -77,7 +78,10 @@ def progress(current, total): if isfile(args): try: edit(message, get_translation('updownUpload', ['`', '', args])) - reply_doc(message, args, progress=progress) + if args.endswith('.mp4'): + reply_video(message, args, progress=progress) + else: + reply_doc(message, args, progress=progress) edit(message, f'`{get_translation("uploadFinish")}`') except Exception as e: edit(message, f'`{get_translation("uploadError")}`') diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index d413fbd..5fef80b 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> # Copyright (C) 2021 kisekinopureya <https://github.com/kisekinopureya> # # This file is part of TeamDerUntergang project, @@ -58,7 +58,7 @@ def youtubedl(message): video_info = ydl.extract_info(url, False) title = video_info['title'] uploader = video_info['uploader'] - duration = int(video_info['duration']) + duration = video_info['duration'] except KeyError: uploader = get_translation('notFound') duration = None diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 2d3b451..b2caa52 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -64,6 +64,7 @@ def reply_video( duration='', thumb=None, fix_markdown=False, + progress=None, delete_orig=False, delete_file=False, parse='md', @@ -73,7 +74,11 @@ def reply_video( caption += MARKDOWN_FIX_CHAR if not duration: message.reply_video( - video, caption=caption.strip(), parse_mode=parse, thumb=thumb + video, + caption=caption.strip(), + parse_mode=parse, + thumb=thumb, + progress=progress, ) else: message.reply_video( @@ -82,6 +87,7 @@ def reply_video( duration=int(duration), parse_mode=parse, thumb=thumb, + progress=progress, ) if delete_orig: message.delete() diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index a93c602..e59813f 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -530,7 +530,7 @@ "statusWeek": "Within the last week", "stickerAdded": "%1Sticker kanged successfully!%1\nPack can be found [here](https://t.me/addstickers/%2)", "stickerError": "I can't kang that", - "stickerInfo": ".kang\nUsage: Reply .kang to a sticker or an image to kang it to your sticker pack.\n\n.kang [number]\nUsage: Kang's the sticker/image to the specified pack but uses \ud83e\udd24 as emoji.\n\n.getsticker\nUsage: Sends the replied sticker in file format.\n\n.packinfo\nUsage: Gets info about the sticker pack.", + "stickerInfo": ".kang [Optional: <Pack Number> <Emoji>]\nUsage: Reply .kang to a sticker or an image to kang it to your sticker pack. Default emoji is \ud83e\udd24\n\n.getsticker\nUsage: Sends the replied sticker in file format.\n\n.packinfo\nUsage: Gets info about the sticker pack.", "stickerPackFull": "Pack %1 is full.", "stickerUsage": "Give me something..", "strUsage": "GiiiiiiiB sooooooomeeeeeee teeeeeeext!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 09f25e9..2b88001 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -530,7 +530,7 @@ "statusWeek": "Bir hafta i\u00e7inde", "stickerAdded": "%1Ba\u015far\u0131yla d\u0131zland\u0131.%1\nEri\u015fmek i\u00e7in [buraya](https://t.me/addstickers/%2) dokunun.", "stickerError": "Bunu d\u0131zlayamam.", - "stickerInfo": ".d\u0131zla\nKullan\u0131m: .d\u0131zla ile bir \u00e7\u0131kartmaya ya da resme yan\u0131tlayarak kendi \u00e7\u0131kartma paketinize \u00e7\u0131kartma olarak ekleyebilirsiniz.\n\n.d\u0131zla [numara]\nKullan\u0131m: \u00c7\u0131kartmay\u0131 ya da resmi belirtilen pakete ekler fakat emoji olarak \u015fu kullan\u0131l\u0131r: \ud83e\udd24\n\n.getsticker\nKullan\u0131m: Yan\u0131tlanan \u00e7\u0131kartmay\u0131 dosya bi\u00e7iminde g\u00f6nderir.\n\n.packinfo\nKullan\u0131m: \u00c7\u0131kartma paketi hakk\u0131nda bilgi verir.", + "stickerInfo": ".d\u0131zla [\u0130ste\u011fe ba\u011fl\u0131: <Paket Numaras\u0131> <Emoji>]\nKullan\u0131m: .d\u0131zla ile bir \u00e7\u0131kartmaya ya da resme yan\u0131tlayarak kendi \u00e7\u0131kartma paketinize \u00e7\u0131kartma olarak ekleyebilirsiniz. Varsay\u0131lan emoji \ud83e\udd24 kullan\u0131l\u0131r.\n\n.getsticker\nKullan\u0131m: Yan\u0131tlanan \u00e7\u0131kartmay\u0131 dosya bi\u00e7iminde g\u00f6nderir.\n\n.packinfo\nKullan\u0131m: \u00c7\u0131kartma paketi hakk\u0131nda bilgi verir.", "stickerPackFull": "%1 numaral\u0131 paket dolu.", "stickerUsage": "Bana d\u0131zlayabilece\u011fim bir \u015fey ver.", "strUsage": "Baaaaanaaaaa biiiiir meeeeetiiiiin veeeeer!", From 89c2a159202274f33a10f1d0576091d296941f2b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Thu, 10 Jun 2021 11:07:39 +0300 Subject: [PATCH 062/242] seden: little improvements Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- README.md | 5 +++-- sedenbot/modules/qrcode.py | 42 +++++++++++++++++++++++------------ sedenbot/modules/remove_bg.py | 17 ++++++-------- sedenbot/modules/stickers.py | 11 ++++++--- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2f24aea..a38f2ee 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ Seden UserBot == -![GitHub repo size](https://img.shields.io/github/repo-size/TeamDerUntergang/Telegram-SedenUserBot?color=red&style=plastic) -![GitHub](https://img.shields.io/github/license/TeamDerUntergang/Telegram-SedenUserBot?color=red&style=plastic) +![GitHub repo size](https://img.shields.io/github/repo-size/TeamDerUntergang/Telegram-SedenUserBot?color=brightgreen) +![GitHub](https://img.shields.io/github/license/TeamDerUntergang/Telegram-SedenUserBot?color=red) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It is an modular and simple to use bot. diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index 48de608..17c7c13 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -12,13 +12,14 @@ from barcode import get from barcode.writer import ImageWriter from bs4 import BeautifulSoup +from PIL import Image from sedenbot import HELP from sedenecem.core import ( download_media_wc, edit, extract_args, + get_download_dir, get_translation, - reply_doc, reply_sticker, sedenify, ) @@ -30,20 +31,28 @@ @sedenify(pattern='^.decode$') def parseqr(message): reply = message.reply_to_message - if not reply: - return edit(message, f'`{get_translation("wrongCommand")}`') - - if not ( - reply.photo - or reply.sticker - or (reply.document and 'image' in reply.document.mime_type) + if ( + reply + and reply.media + and ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ) ): + edit(message, f'`{get_translation("processing")}`') + else: edit(message, f'`{get_translation("wrongCommand")}`') return - downloaded_file_name = download_media_wc(reply) + output = download_media_wc(reply, f'{get_download_dir()}/decode.png') + + if reply.sticker and not reply.sticker.is_animated: + image = Image.open(output) + output = f'{get_download_dir()}/decode.png' + image.save(output) - dw = open(downloaded_file_name, 'rb') + dw = open(output, 'rb') files = {'f': dw.read()} t_response = None @@ -56,7 +65,7 @@ def parseqr(message): except BaseException: pass - remove(downloaded_file_name) + remove(output) if not t_response: edit(message, f'`{get_translation("decodeFail")}`') return @@ -79,8 +88,12 @@ def barcode(message): try: bar_code_mode_f = get('code128', input_str, writer=ImageWriter()) filename = bar_code_mode_f.save('code128') - reply_doc(reply or message, filename, delete_after_send=True) + image = Image.open(filename) + filename = f'{get_download_dir()}/barcode.webp' + image.save(filename) + reply_sticker(reply or message, filename, delete_file=True) message.delete() + remove('code128.png') except Exception as e: edit(message, str(e)) return @@ -101,8 +114,9 @@ def makeqr(message): qr.add_data(input_str) qr.make(fit=True) img = qr.make_image(fill_color='black', back_color='white') - img.save('img_file.webp', 'PNG') - reply_sticker(reply or message, 'img_file.webp', delete_file=True) + output = f'{get_download_dir()}/qrcode.webp' + img.save(output) + reply_sticker(reply or message, output, delete_file=True) message.delete() except Exception as e: edit(message, str(e)) diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 9942b28..814db07 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -53,22 +53,19 @@ def rbg(message): download_media_wc(reply, IMG_PATH) edit(message, f'`{get_translation("rbgProcessing")}`') - try: + if reply.sticker and not reply.sticker.is_animated: image = Image.open(IMG_PATH) - width, height = image.size - maxSize = (1920, 1080) - ratio = min(maxSize[0] / width, maxSize[1] / height) - image = image.resize((int(width * ratio), int(height * ratio))) - new_photo = f'{get_download_dir()}/photo.png' - image.save(new_photo) + IMG_PATH = f'{get_download_dir()}/image.png' + image.save(IMG_PATH) + + try: remove_bg = RemoveBg(RBG_APIKEY, get_translation('rbgLog')) - remove_bg.remove_background_from_img_file(new_photo) - rbg_img = f'{new_photo}_no_bg.png' + remove_bg.remove_background_from_img_file(IMG_PATH) + rbg_img = f'{IMG_PATH}_no_bg.png' reply_doc( reply, rbg_img, caption=get_translation('rbgResult'), delete_after_send=True ) message.delete() - remove(new_photo) except Exception as e: return edit(message, get_translation('banError', ['`', '**', e])) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 9eeb37f..c1300c3 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -8,9 +8,11 @@ # from random import choice +from time import time -from pyrogram.raw.functions.messages import GetStickerSet +from PIL import Image from pyrogram.errors import YouBlockedUser +from pyrogram.raw.functions.messages import GetStickerSet from pyrogram.raw.types import InputStickerSetShortName from sedenbot import HELP, PACKNAME, PACKNICK, TEMP_SETTINGS from sedenecem.core import ( @@ -18,12 +20,12 @@ download_media_wc, edit, extract_args, + get_download_dir, get_translation, reply_doc, sedenify, ) from sedenecem.core import sticker_resize as resizer -from time import time # ================= CONSTANT ================= DIZCILIK = [get_translation(f'kangstr{i+1}') for i in range(0, 12)] @@ -201,7 +203,10 @@ def getsticker(message): edit(message, f'`{get_translation("replySticker")}`') return - photo = download_media_wc(reply) + photo = download_media_wc(reply, f'{get_download_dir()}/sticker.png') + image = Image.open(photo) + photo = f'{get_download_dir()}/sticker.png' + image.save(photo) reply_doc( reply, From 914957a6a018595def35695351c2cd23c3de21bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= <33374965+musfay@users.noreply.github.com> Date: Mon, 21 Jun 2021 12:32:18 +0300 Subject: [PATCH 063/242] shell.nix: handle chromedriver path (#7) --- shell.nix | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/shell.nix b/shell.nix index 4aa8aff..a3cce9c 100644 --- a/shell.nix +++ b/shell.nix @@ -2,13 +2,12 @@ with import <nixpkgs> {}; stdenv.mkDerivation { name = "sedenbot-environment"; - buildInputs = [ - pkgs.git - pkgs.nano - pkgs.python3 - pkgs.python3.pkgs.setuptools - pkgs.python3.pkgs.pip - ]; + buildInputs = with pkgs; [ + git nano python3 chromedriver gnused + python3.pkgs.setuptools + python3.pkgs.pip + ]; + shellHook = '' export PIP_PREFIX="$(pwd)/_build/pip_packages" export PYTHONPATH="$PIP_PREFIX/${pkgs.python3.sitePackages}:$PYTHONPATH" @@ -25,6 +24,9 @@ stdenv.mkDerivation { $PIP install -r requirements.txt if [ -f "config.env" -a -f "sedenuserbot.session" ]; then + # update chromedriver path + sed '/CHROME_DRIVER/d' config.env + echo "CHROME_DRIVER='${pkgs.chromedriver.outPath}/bin/chromedriver'" >> config.env $PYTHON seden.py else # first run @@ -32,7 +34,7 @@ stdenv.mkDerivation { mv sample_config.env config.env echo "Press enter to edit config.env file..." && read >> /dev/null nano config.env - echo "Type "nix-shell" to start SedenUserBot!" + echo "Type 'nix-shell' to start SedenUserBot!" fi ''; -} +} \ No newline at end of file From 678cb2fe0667ed539dcb38048224b5fc75c840e0 Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Fri, 2 Jul 2021 10:33:27 +0300 Subject: [PATCH 064/242] seden: Fix known bugs Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- README.md | 1 + requirements.txt | 2 +- sedenbot/modules/ban.py | 18 ++++++++++- sedenbot/modules/globals.py | 8 ++--- sedenbot/modules/memes.py | 3 +- sedenbot/modules/pmpermit.py | 6 ++-- sedenbot/modules/scrapers.py | 63 +++++++++++++----------------------- 7 files changed, 49 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index a38f2ee..2ac9469 100755 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Please go to our [GitHub.io](https://teamderuntergang.github.io/pyrogram.html) p * [@Delivrance](https://github.com/pyrogram/pyrogram) - Pyrogram Library * [@Skittles9823](https://github.com/skittles9823) - Memes * [@RaphielGang](https://github.com/raphielgang) - Other Modules +* [All Contributors](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/graphs/contributors) ## License diff --git a/requirements.txt b/requirements.txt index cf7fd04..cd8374f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ humanize image_to_ascii lyricsgenius pillow -psycopg2-binary +git+https://github.com/frknkrc44/psycopg2@try-switch-pkg-config pybase64 pylast pyrogram==1.2.9 diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 126f5fb..6cf8702 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -30,7 +30,6 @@ sedenify, send_log, ) -from sedenecem.sql import mute_sql as sql @sedenify(pattern='^.ban', compat=False, private=False, admin=True) @@ -180,6 +179,12 @@ def kick_user(client, message): @sedenify(pattern='^.mute', compat=False, private=False, admin=True) def mute_user(client, message): + try: + from sedenecem.sql import mute_sql as sql + except BaseException: + edit(message,f'`{get_translation("nonSqlMode")}`') + return + args = extract_args(message) reply = message.reply_to_message edit(message, f'`{get_translation("muteProcess")}`') @@ -232,6 +237,12 @@ def mute_user(client, message): @sedenify(pattern='^.unmute', compat=False, private=False, admin=True) def unmute_user(client, message): + try: + from sedenecem.sql import mute_sql as sql + except BaseException: + edit(message,f'`{get_translation("nonSqlMode")}`') + return + args = extract_args(message) reply = message.reply_to_message edit(message, f'`{get_translation("unmuteProcess")}`') @@ -571,6 +582,11 @@ def set_group_photo(client, message): @sedenify(incoming=True, outgoing=False, compat=False) def mute_check(client, message): + try: + from sedenecem.sql import mute_sql as sql + except BaseException: + return + muted = sql.is_muted(message.chat.id, message.from_user.id) if muted: diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index e2241ca..11151b3 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -115,13 +115,13 @@ def ungban_user(client, message): return edit(message, f'`{get_translation("alreadyUnbanned")}`') sql.ungban(user.id) - def find_me(): + def find_me(dialog): try: return dialog.chat.get_member(me_id).can_restrict_members except BaseException: return False - def find_member(): + def find_member(dialog): try: return (dialog.chat.get_member(user.id) and dialog.chat.get_member(user.id).restricted_by @@ -137,8 +137,8 @@ def find_member(): for dialog in dialogs if ( 'group' in dialog.chat.type - and find_me() - and find_member() + and find_me(dialog) + and find_member(dialog) ) ] for chat in chats: diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index eaafa3a..9e965a2 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -806,7 +806,8 @@ def amogus(message): arr = randint(1, 12) fontsize = 100 FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' - url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' # Thanks + # https://github.com/KeyZenD/AmongUs + url = 'https://raw.githubusercontent.com/KeyZenD/AmongUs/master/' font = ImageFont.truetype(FONT_FILE, size=int(fontsize)) imposter = Image.open(BytesIO(get(f'{url}{arr}.png').content)) diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 1fcf068..58c005c 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -201,16 +201,14 @@ def approvepm(client, message): try: approve(uid) + edit(message, get_translation('pmApproveSuccess', [name0, uid, '`'])) + send_log(get_translation('pmApproveLog', [name0, uid])) for message in _find_unapproved_msg(client, message.chat.id): message.delete() except IntegrityError: edit(message, f'`{get_translation("pmApproveError2")}`') return - edit(message, get_translation('pmApproveSuccess', [name0, uid, '`'])) - - send_log(get_translation('pmApproveLog', [name0, uid])) - @sedenify(outgoing=True, pattern="^.disapprove$") def disapprovepm(message): diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 8958c67..7c53fc5 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -201,72 +201,53 @@ def replacer(st): .strip() ) - def link_replacer(link): - rep = {'(': '%28', ')': '%29', '[': '%5B', ']': '%5D', '%': '½'} - for i in rep.keys(): - link = link.replace(i, rep[i]) - return link - def get_result(res): - link = res.findAll('a') - for a in link: - if a.find('h3', {'class': ['zBAuLc']}): - link = a - break - href = f"https://{choice(google_domains)}{link_replacer(link['href'])}" - title = link.find('h3', {'class': ['zBAuLc']}).text - title = replacer(title) - desc = res.contents[-1].findAll( - 'div', {'class': ['BNeawe', 's3v9rd', 'AP7Wnd']} - ) - desc = [d.text for d in desc if len(d.text)][0] - desc = replacer(desc) - if len(desc.strip()) < 1: - desc = get_translation('googleDesc') - return f'[{title}]({href})\n{desc}' + link = res.find('a', href=True) + title = res.find('h3') + if title: + title = title.text + desc = res.find('div', attrs= {'class': ['VwiC3b', 'yXK7lf', 'MUxGbd', 'yDYNvb', 'lyLwlc']}) + if desc: + desc = desc.text + + if link and title and desc: + return f'[{replacer(title)}]({link["href"]})\n{desc or ""}' query = parse_key(query) page = find_page(page) - temp = f'/search?q={query}&gbv=1&sei=o9ybYJmOFojssAfep7rADQ&start={find_page(page)}' + temp = f'/search?q={query}&start={find_page(page)}&hl={SEDEN_LANG}' req = get( f'https://{choice(google_domains)}{temp}', headers={ - 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', 'Content-Type': 'text/html', }, ) + retries = 0 while req.status_code != 200 and retries < 10: retries += 1 req = get( f'https://{choice(google_domains)}{temp}', headers={ - 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', 'Content-Type': 'text/html', }, ) soup = BeautifulSoup(req.text, 'html.parser') - res1 = soup.findAll('div', {'class': ['ZINbbc', 'xpd', 'O9g5cc', 'uUPGi']}) - - def is_right_class(res): - ret = res.find('h3', {'class': ['zBAuLc']}) - - if not ret: - return False - - ret = ret.parent - - return ret.name == 'a' - - res1 = [res for res in res1 if is_right_class(res)] + + res1 = soup.find_all('div', attrs={'class': 'g'}) out = '' - for i in range(0, len(res1)): - res = res1[i] + count = 0 + for res in res1: try: - out += f'{i+1} - {get_result(res)}\n\n' + result = get_result(res) + if result: + count += 1 + out += f'{count} - {result}\n\n' except Exception: print(format_exc()) print(res) From cf7c22b91d2bbe3706d9ac93786aa75aacadece4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Fri, 2 Jul 2021 10:40:38 +0300 Subject: [PATCH 065/242] seden: Release v1.4.5 Beta * Fixed Google issues on Heroku * Fixed ungban command * Fixed approve command * Fixed ban module issues Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index a4d6ef9..4aefee2 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -122,7 +122,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.4.4 Beta' +BOT_VERSION = '1.4.5 Beta' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 410bcbb5e88c62b80426b1423e25daf5ccb7fbd0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Fri, 2 Jul 2021 15:39:07 +0300 Subject: [PATCH 066/242] seden: Set idle Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- seden.py | 6 ++++-- sedenbot/__init__.py | 4 +--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/seden.py b/seden.py index c335396..4676c6b 100644 --- a/seden.py +++ b/seden.py @@ -8,6 +8,8 @@ # if __name__ == '__main__': - from sedenbot import app + from sedenbot import app, __import_modules, idle - app.run() + with app: + __import_modules() + idle() diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 4aefee2..2e48390 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -27,7 +27,7 @@ import sedenecem.translator as _tr from dotenv import load_dotenv, set_key, unset_key -from pyrogram import Client, filters +from pyrogram import Client, filters, idle from pyrogram.handlers import MessageHandler from requests import get @@ -307,7 +307,5 @@ def __import_modules(): LOGS.warn(get_translation('loadedModulesError', [module])) -__import_modules() - LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) From 45c88c5106364845b57ab72df9267bb71008af8c Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Fri, 2 Jul 2021 15:40:30 +0300 Subject: [PATCH 067/242] seden: Update README url Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ac9469..1c5acc8 100755 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Just type `nix-shell` command in bot folder. If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). -Please go to our [GitHub.io](https://teamderuntergang.github.io/pyrogram.html) page for installation instructions! Questions asked without reading the instruction will not be answered. +Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.html) page for installation instructions! Questions asked without reading the instruction will not be answered. ## Credits * [@NaytSeyd](https://github.com/NaytSeyd) - Founder From 1bef33369f3ce1775f4a3ff1facf5d38e74eee9e Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Fri, 2 Jul 2021 17:53:17 +0300 Subject: [PATCH 068/242] Some bug fixes - Add duration and thumb to upload - Fix pmpermit and remove unnecessary variables --- sedenbot/__init__.py | 4 +-- sedenbot/modules/pmpermit.py | 51 +++++++++++++++++------------------- sedenecem/core/replier.py | 24 +++++++++++++++-- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 2e48390..9f2ce7a 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -61,9 +61,9 @@ def get_translation(transKey, params: list = None): BRAIN = [] BLACKLIST = [] CONVERSATION: Dict[Any, Any] = {} -PM_COUNT: Dict[Any, int] = {} -PM_LAST_MSG: Dict[Any, Any] = {} TEMP_SETTINGS: Dict[Any, Any] = {} +TEMP_SETTINGS['PM_COUNT'] = {} +TEMP_SETTINGS['PM_LAST_MSG'] = {} # Console verbose logging LOG_VERBOSE = sb(environ.get('LOG_VERBOSE', 'False')) diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 58c005c..99abf9a 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -7,12 +7,11 @@ # All rights reserved. See COPYING, AUTHORS. # +from re import VERBOSE from sedenbot import ( HELP, LOGS, PM_AUTO_BAN, - PM_COUNT, - PM_LAST_MSG, PM_MSG_COUNT, PM_UNAPPROVED, TEMP_SETTINGS, @@ -43,7 +42,7 @@ def pmpermit_init(): @sedenify( incoming=True, - outgoing=False, + outgoing=True, disable_edited=True, disable_notify=True, group=False, @@ -51,17 +50,13 @@ def pmpermit_init(): bot=False, ) def permitpm(client, message): - if message.from_user and message.from_user.is_self: - message.continue_propagation() - if not PM_AUTO_BAN: message.continue_propagation() else: - if auto_accept(client, message): - return + if auto_accept(client, message) or message.from_user.is_self: + message.continue_propagation() - self_user = TEMP_SETTINGS['ME'] - if message.chat.id not in [self_user.id, 777000]: + if message.chat.id != 777000: try: from sedenecem.sql.pm_permit_sql import is_approved except BaseException: @@ -71,33 +66,33 @@ def permitpm(client, message): notifsoff = is_muted(-1) if not apprv and message.text != UNAPPROVED_MSG: - if message.chat.id in PM_LAST_MSG: - prevmsg = PM_LAST_MSG[message.chat.id] + if message.chat.id in TEMP_SETTINGS['PM_LAST_MSG']: + prevmsg = TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] if message.text != prevmsg: for message in _find_unapproved_msg(client, message.chat.id): message.delete() - if PM_COUNT[message.chat.id] < (PM_MSG_COUNT - 1): + if TEMP_SETTINGS['PM_COUNT'][message.chat.id] < (PM_MSG_COUNT - 1): ret = reply(message, UNAPPROVED_MSG) - PM_LAST_MSG[message.chat.id] = ret.text + TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text else: ret = reply(message, UNAPPROVED_MSG) if ret.text: - PM_LAST_MSG[message.chat.id] = ret.text + TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text if notifsoff: client.read_history(message.chat.id) - if message.chat.id not in PM_COUNT: - PM_COUNT[message.chat.id] = 1 + if message.chat.id not in TEMP_SETTINGS['PM_COUNT']: + TEMP_SETTINGS['PM_COUNT'][message.chat.id] = 1 else: - PM_COUNT[message.chat.id] = PM_COUNT[message.chat.id] + 1 + TEMP_SETTINGS['PM_COUNT'][message.chat.id] = TEMP_SETTINGS['PM_COUNT'][message.chat.id] + 1 - if PM_COUNT[message.chat.id] > (PM_MSG_COUNT - 1): + if TEMP_SETTINGS['PM_COUNT'][message.chat.id] > (PM_MSG_COUNT - 1): reply(message, f'`{get_translation("pmpermitBlock")}`') try: - del PM_COUNT[message.chat.id] - del PM_LAST_MSG[message.chat.id] + del TEMP_SETTINGS['PM_COUNT'][message.chat.id] + del TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] except BaseException: pass @@ -124,15 +119,17 @@ def auto_accept(client, message): if is_approved(chat.id): return True - for msg in client.get_history(chat.id, limit=3): + for msg in client.get_history(chat.id, limit=1, reverse=True): + # chat.id in TEMP_SETTINGS['PM_LAST_MSG'] + # and msg.text != UNAPPROVED_MSG + # and + if ( - chat.id in PM_LAST_MSG - and msg.text != PM_LAST_MSG[chat.id] - and msg.from_user.is_self + msg.from_user.id == self_user.id ): try: - del PM_COUNT[chat.id] - del PM_LAST_MSG[chat.id] + del TEMP_SETTINGS['PM_COUNT'][chat.id] + del TEMP_SETTINGS['PM_LAST_MSG'][chat.id] except BaseException: pass diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index b2caa52..d66c1e4 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -7,9 +7,11 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove +from os import remove, path +from subprocess import getstatusoutput from .misc import MARKDOWN_FIX_CHAR, download_media_wc +from sedenbot import LOG_VERBOSE def reply_img( @@ -70,6 +72,23 @@ def reply_video( parse='md', ): try: + if not thumb: + thumb = 'downloads/thumb.png' + if path.exists(thumb): + remove(thumb) + out = getstatusoutput(f'ffmpeg -i {video} -ss 00:00:01.000 -vframes 1 {thumb}') + if LOG_VERBOSE: + print(out) + if out[0] != 0: + thumb = None + + if not duration: + out = getstatusoutput(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {video}') + if LOG_VERBOSE: + print(out) + if out[0] == 0: + duration = int(float(out[1])) + if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR if not duration: @@ -93,7 +112,8 @@ def reply_video( message.delete() if delete_file: remove(video) - except BaseException: + except BaseException as e: + raise e pass From becc61e3a9bc67dda205b50c2f6ceb8302ac9b5f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Fri, 2 Jul 2021 21:11:33 +0300 Subject: [PATCH 069/242] seden: Improve unpin command Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/ban.py | 16 ++++++++++------ sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 6cf8702..965932e 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -404,24 +404,28 @@ def pin_message(client, message): return -@sedenify(pattern='^.unpin$', compat=False, private=False, admin=True) +@sedenify(pattern='^.unpin', compat=False, private=False, admin=True) def unpin_message(client, message): + args = extract_args(message) reply = message.reply_to_message - chat_id = message.chat.id + chat = message.chat if reply: try: - client.unpin_chat_message(chat_id, reply.message_id) + reply.unpin() + message.delete() except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) return - else: + elif 'all' in args: try: - client.unpin_all_chat_messages(chat_id) + client.unpin_all_chat_messages(chat.id) + message.delete() except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) return + else: + edit(message, f'`{get_translation("wrongCommand")}`') - message.delete() @sedenify(pattern='^.(admins|bots|user(s|sdel))$', compat=False, private=False) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e59813f..2a58ac3 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -1,6 +1,6 @@ { "added": "added", - "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin\nUsage: Unpin the pinned message.\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.\n\n.setgpic\nUsage: Changes group picture.", + "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin <reply> & .unpin all\nUsage: Unpin the pinned message(s).\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.\n\n.setgpic\nUsage: Changes group picture.", "adminUsage": "I'm not admin!", "adminlist": "%1Admins in%1 %2%3%2%1:%1", "afkEnd": "I'm no longer AFK.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2b88001..ca7208c 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -1,6 +1,6 @@ { "added": "eklendi", - "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.\n\n.setgpic\nKullan\u0131m: Grubun foto\u011fraf\u0131n\u0131 de\u011fi\u015ftirir.", + "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin <yan\u0131tlama> & .unpin all\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.\n\n.setgpic\nKullan\u0131m: Grubun foto\u011fraf\u0131n\u0131 de\u011fi\u015ftirir.", "adminUsage": "Y\u00f6netici de\u011filim!", "adminlist": "%2%3%2 %1sohbetinde bulunan y\u00f6neticiler:%1", "afkEnd": "Art\u0131k AFK de\u011filim.", From ff20e7d1f0c5a20899d12e3cc93523670eabe747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= <33374965+musfay@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:05:41 +0300 Subject: [PATCH 070/242] shell.nix: cleanup (#8) --- shell.nix | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/shell.nix b/shell.nix index a3cce9c..7ea70b0 100644 --- a/shell.nix +++ b/shell.nix @@ -1,7 +1,6 @@ -with import <nixpkgs> {}; +{ pkgs ? import <nixpkgs> {} }: -stdenv.mkDerivation { - name = "sedenbot-environment"; +pkgs.mkShell { buildInputs = with pkgs; [ git nano python3 chromedriver gnused python3.pkgs.setuptools @@ -13,9 +12,9 @@ stdenv.mkDerivation { export PYTHONPATH="$PIP_PREFIX/${pkgs.python3.sitePackages}:$PYTHONPATH" export PATH="$PIP_PREFIX/bin:$PATH" # use nix binaries because we don't want to invoke host configs - export GIT="${pkgs.git.outPath}/bin/git" - export PIP="${pkgs.python3.pkgs.pip.outPath}/bin/pip" - export PYTHON="${pkgs.python3.outPath}/bin/python" + export GIT="${pkgs.git}/bin/git" + export PIP="${pkgs.python3.pkgs.pip}/bin/pip" + export PYTHON="${pkgs.python3}/bin/python" unset SOURCE_DATE_EPOCH # update bot @@ -25,9 +24,10 @@ stdenv.mkDerivation { if [ -f "config.env" -a -f "sedenuserbot.session" ]; then # update chromedriver path - sed '/CHROME_DRIVER/d' config.env - echo "CHROME_DRIVER='${pkgs.chromedriver.outPath}/bin/chromedriver'" >> config.env + sed -i -E '/CHROME_DRIVER/d' config.env + echo "CHROME_DRIVER='${pkgs.chromedriver}/bin/chromedriver'" >> config.env $PYTHON seden.py + exit else # first run $PYTHON session.py @@ -35,6 +35,7 @@ stdenv.mkDerivation { echo "Press enter to edit config.env file..." && read >> /dev/null nano config.env echo "Type 'nix-shell' to start SedenUserBot!" + exit fi ''; } \ No newline at end of file From 0c4287112e2f214e1d5333cbeae93bda5f1e23c0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sat, 17 Jul 2021 09:43:00 +0300 Subject: [PATCH 071/242] seden: Change log time format * We don't need a split second Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 9f2ce7a..ff15160 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -70,6 +70,7 @@ def get_translation(transKey, params: list = None): basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', level=DEBUG if LOG_VERBOSE else INFO, ) From c3801cc74ff82574b14297ba163eec058d3150d2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sat, 17 Jul 2021 09:47:50 +0300 Subject: [PATCH 072/242] seden: Do what is necessary Co-authored-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> Signed-off-by: frknkrc44 <krc440002@gmail.com> --- sedenbot/modules/covid19.py | 8 +++-- sedenbot/modules/effects.py | 24 +++++++++---- sedenbot/modules/memes.py | 2 +- sedenbot/modules/stickers.py | 10 ++++-- sedenecem/core/misc.py | 16 +++++++-- sedenecem/core/replier.py | 69 ++++++++++++++---------------------- sedenecem/core/sedenify.py | 2 +- 7 files changed, 72 insertions(+), 59 deletions(-) diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py index 362c411..2115499 100644 --- a/sedenbot/modules/covid19.py +++ b/sedenbot/modules/covid19.py @@ -18,7 +18,6 @@ @sedenify(pattern='^.covid(|19)$') def covid(message): - '''Copyright (c) @frknkrc44 | 2020''' try: req = get( 'https://covid19.saglik.gov.tr/', @@ -54,7 +53,10 @@ def covid(message): result = result[0] def del_dots(res): - return res.replace('.', '') + return empty_check(res.replace('.', '')) + + def empty_check(res): + return res if len(res.strip()) else 'N/A' sonuclar = ( f'**{get_translation("covidData")}**\n' @@ -64,7 +66,7 @@ def del_dots(res): + f'**{get_translation("covidCases")}** `{del_dots(result["toplam_hasta"])}`\n' + f'**{get_translation("covidDeaths")}** `{del_dots(result["toplam_vefat"])}`\n' + f'**{get_translation("covidSeriouslyill")}** `{del_dots(result["agir_hasta_sayisi"])}`\n' - + f'**{get_translation("covidPneumonia")}** `%{result["hastalarda_zaturre_oran"]}`\n' + + f'**{get_translation("covidPneumonia")}** `%{empty_check(result["hastalarda_zaturre_oran"])}`\n' + f'**{get_translation("covidHealed")}** `{del_dots(result["toplam_iyilesen"])}`\n' + f'\n**{get_translation("covidToday")}**\n' + f'**{get_translation("covidTests")}** `{del_dots(result["gunluk_test"])}`\n' diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 4d23b34..dab6708 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -7,7 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove +from os import path, remove from subprocess import Popen from sedenbot import HELP @@ -16,8 +16,8 @@ edit, extract_args, get_translation, + reply_audio, reply_video, - reply_voice, sedenify, ) @@ -79,7 +79,7 @@ def earrape(message): ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(reply or message, f'{media}.mp3', delete_file=True) + reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() else: @@ -101,6 +101,11 @@ def nightcore(message): else: edit(message, f'`{get_translation("applyNightcore")}`') media = download_media_wc(reply) + + filename = f'{media}.mp3' + if path.exists(filename): + remove(filename) + process = Popen( [ 'ffmpeg', @@ -108,12 +113,12 @@ def nightcore(message): media, '-af', 'asetrate=44100*1.16,aresample=44100,atempo=1', - f'{media}.mp3', + filename, ] ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(reply or message, f'{media}.mp3', delete_file=True) + reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() @@ -132,6 +137,11 @@ def slowedtoperfection(message): else: edit(message, f'`{get_translation("applySlowedtoperfection")}`') media = download_media_wc(reply) + + filename = f'{media}.mp3' + if path.exists(filename): + remove(filename) + process = Popen( [ 'ffmpeg', @@ -139,12 +149,12 @@ def slowedtoperfection(message): media, '-af', 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', - f'{media}.mp3', + filename, ] ) process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') - reply_voice(reply or message, f'{media}.mp3', delete_file=True) + reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) message.delete() diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index 9e965a2..8b1a559 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -574,7 +574,7 @@ def mock(message): reply_text = [] textx = message.reply_to_message mock = extract_args(message) - if mock: + if len(mock): pass elif textx: mock = textx.text diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index c1300c3..c750bec 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -56,6 +56,13 @@ def kang(client, message): else: edit(message, f'`{get_translation("stickerError")}`') return + + if not reply.sticker: + try: + media = resizer(media) + except BaseException: + edit(message, f'`{get_translation("stickerError")}`') + return if len(args) < 1: args = '1' @@ -166,9 +173,6 @@ def add_exist(conv, pack, pname, pnick): send_recv(conv, '/done') return True - if not reply.sticker: - media = resizer(media) - with PyroConversation(client, chat) as conv: try: send_recv(conv, '/cancel') diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 86ca257..01bbfe3 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -9,9 +9,10 @@ from os import makedirs from re import escape, sub +from subprocess import getstatusoutput from pyrogram.types import Message -from sedenbot import BOT_PREFIX, BRAIN, TEMP_SETTINGS, app +from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, TEMP_SETTINGS, app MARKDOWN_FIX_CHAR = '\u2064' SPAM_COUNT = [0] @@ -228,7 +229,7 @@ def reply( def extract_args(message, markdown=True): if not (message.text or message.caption): - return + return '' text = message.text or message.caption @@ -361,3 +362,14 @@ def get_download_dir() -> str: dir = './downloads' makedirs(dir, exist_ok=True) return dir + + +def get_duration(media): + out = getstatusoutput( + f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "{media}"' + ) + if LOG_VERBOSE: + print(out) + if out[0] == 0: + return int(float(out[1])) + return None diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index d66c1e4..145392a 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -10,7 +10,9 @@ from os import remove, path from subprocess import getstatusoutput -from .misc import MARKDOWN_FIX_CHAR, download_media_wc +from pyrogram.types.messages_and_media.message import Message + +from .misc import MARKDOWN_FIX_CHAR, get_duration from sedenbot import LOG_VERBOSE @@ -39,7 +41,7 @@ def reply_audio( message, audio, caption='', - duration='', + duration=None, fix_markdown=False, delete_orig=False, delete_file=False, @@ -47,10 +49,11 @@ def reply_audio( try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR + if not duration: - message.reply_audio(audio, caption=caption.strip()) - else: - message.reply_audio(audio, caption=caption.strip(), duration=int(duration)) + duration = get_duration(audio) + + message.reply_audio(audio, caption=caption.strip(), duration=int(duration)) if delete_orig: message.delete() if delete_file: @@ -76,18 +79,16 @@ def reply_video( thumb = 'downloads/thumb.png' if path.exists(thumb): remove(thumb) - out = getstatusoutput(f'ffmpeg -i {video} -ss 00:00:01.000 -vframes 1 {thumb}') + out = getstatusoutput( + f'ffmpeg -i {video} -ss 00:00:01.000 -vframes 1 {thumb}' + ) if LOG_VERBOSE: print(out) if out[0] != 0: thumb = None - + if not duration: - out = getstatusoutput(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {video}') - if LOG_VERBOSE: - print(out) - if out[0] == 0: - duration = int(float(out[1])) + duration = get_duration(video) if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -118,12 +119,22 @@ def reply_video( def reply_voice( - message, voice, caption='', fix_markdown=False, delete_orig=False, delete_file=False + message, + voice, + caption='', + duration=None, + fix_markdown=False, + delete_orig=False, + delete_file=False, ): try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR - message.reply_voice(voice, caption=caption.strip()) + + if not duration: + duration = get_duration(voice) + + message.reply_voice(voice, caption=caption.strip(), duration=duration) if delete_orig: message.delete() if delete_file: @@ -170,35 +181,9 @@ def reply_sticker(message, sticker, delete_orig=False, delete_file=False): pass -def reply_msg(message, message2, delete_orig=False): +def reply_msg(message: Message, message2: Message, delete_orig=False): try: - filename = None - if message2.media: - filename = download_media_wc(message2, sticker_orig=True) - if message2.audio: - message.reply_audio(filename) - elif message2.animation: - message.reply_animation(filename) - elif message2.sticker: - message.reply_sticker(filename) - elif message2.photo: - message.reply_photo(filename) - elif message2.video: - message.reply_video(filename) - elif message2.voice: - message.reply_voice(filename) - elif message2.video_note: - message.reply_video_note(filename) - elif message2.document: - message.reply_document(filename) - else: - filename = None - message2.forward(message.chat.id) - - if filename: - remove(filename) - else: - message.reply_text(message2.text) + message2.copy(chat_id=message.chat.id, reply_to_message_id=message.message_id) if delete_orig: message.delete() diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 81af5c5..f3e0b09 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -47,7 +47,7 @@ def wrap(client, message): return try: - if 'ME' not in TEMP_SETTINGS: + if not TEMP_SETTINGS.get('ME'): me = app.get_me() TEMP_SETTINGS['ME'] = me From 8a6e024181aa694d31f22eff2cfdb0fc8ea4c654 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sat, 17 Jul 2021 09:53:25 +0300 Subject: [PATCH 073/242] seden: Release v1.5.0 Stable * After a long time, we have released the first stable version. Thanks to everyone who contributed. * Also Docker updated Co-authored-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> Signed-off-by: frknkrc44 <krc440002@gmail.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index ff15160..a3debe0 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -123,7 +123,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.4.5 Beta' +BOT_VERSION = '1.5.0s' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 400fa8f0b9d2ae0fabc6375047dfa77a422e11de Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Fri, 13 Aug 2021 00:47:39 +0300 Subject: [PATCH 074/242] seden: Release v1.5.1 Stable * Added spamwatch plugin (works after you add a SpamWatch API key) * Changed terminal command execution method to fix some bugs Co-authored-by: NaytSeyd <naytseyd@devotag.com> Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- requirements.txt | 1 + sample_config.env | 6 ++++- sedenbot/__init__.py | 5 +++- sedenbot/modules/horeke.py | 5 ++-- sedenbot/modules/spamwatch.py | 48 +++++++++++++++++++++++++++++++++++ sedenbot/modules/system.py | 9 +++---- sedenecem/core/misc.py | 22 ++++++++++++++-- sedenecem/core/replier.py | 7 +++-- sedenecem/translator/en.json | 2 ++ sedenecem/translator/tr.json | 2 ++ 10 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 sedenbot/modules/spamwatch.py diff --git a/requirements.txt b/requirements.txt index cd8374f..1f211ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ qrcode removebg requests selenium +spamwatch speedtest-cli sqlalchemy==1.3.23 tgcrypto diff --git a/sample_config.env b/sample_config.env index 4326a36..0fe931a 100644 --- a/sample_config.env +++ b/sample_config.env @@ -57,4 +57,8 @@ PM_UNAPPROVED='' LASTFM_API='' LASTFM_SECRET='' LASTFM_USERNAME='' # Last.fm Username -LASTFM_PASSWORD='' # Last.fm Password \ No newline at end of file +LASTFM_PASSWORD='' # Last.fm Password + +# SpamWatch module +# Get from https://t.me/SpamWatchBot?start=token +SPAMWATCH_KEY='' diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index a3debe0..9cb0e28 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -123,7 +123,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.0s' +BOT_VERSION = '1.5.1s' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' @@ -172,6 +172,9 @@ def set_logger(): HEROKU_KEY = environ.get('HEROKU_KEY', None) HEROKU_APPNAME = environ.get('HEROKU_APPNAME', None) +# SpamWatch API key +SPAMWATCH_KEY = environ.get('SPAMWATCH_KEY', None) + # Chat ID for Bot Logs _LOG_ID = environ.get('LOG_ID', None) LOG_ID = int(_LOG_ID) if _LOG_ID and resr(r'^-?\d+$', _LOG_ID) else None diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index 595dbb8..f43ce13 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -180,9 +180,8 @@ def shutdown(message): def std_off(): try: - from subprocess import getoutput - - getoutput(f'kill -7 {getpid()}') + from sedenecem.core.misc import __status_out__ + __status_out__(f'kill -7 {getpid()}') except Exception: pass diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py new file mode 100644 index 0000000..e95d95f --- /dev/null +++ b/sedenbot/modules/spamwatch.py @@ -0,0 +1,48 @@ +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from sedenbot import BRAIN, HELP, SPAMWATCH_KEY +from sedenecem.core import get_translation, is_admin_myself, reply, sedenify, send_log +from spamwatch import Client as SpamWatch + + +class SWClient: + spamwatch_client = SpamWatch(SPAMWATCH_KEY) if SPAMWATCH_KEY else None + + +@sedenify(compat=False, outgoing=False, incoming=True, disable_notify=True, disable_edited=True) +def spamwatch_action(client, message): + if not SWClient.spamwatch_client: + message.continue_propagation() + + uid = message.from_user.id + if uid in BRAIN: + message.continue_propagation() + + ban_status = SWClient.spamwatch_client.get_ban(uid) + if not ban_status: + message.continue_propagation() + + if is_admin_myself(message.chat): + text = get_translation('spamWatchBan', [message.from_user.first_name, uid]) + + if 'private' == message.chat.type: + reply(message, text) + client.block_user(uid) + else: + myself = message.chat.get_member('me') + if myself.can_restrict_members: + message.chat.kick_member(uid) + reply(message, text) + else: + return + + send_log(text) + +HELP.update({'spamwatch': get_translation('spamWatchInfo')}) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 3b15b5a..7e21768 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -132,12 +132,11 @@ def terminal(message): edit(message, f'`{get_translation("termHelp")}`') return - result = f'`{get_translation("termNoResult")}`' + result = get_translation("termNoResult") try: - from subprocess import getoutput - - result = getoutput(command) - except BaseException: + from sedenecem.core.misc import __status_out__ + _, result = __status_out__(command) + except BaseException as e: pass if len(result) > 4096: diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 01bbfe3..acb5b3c 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -9,7 +9,7 @@ from os import makedirs from re import escape, sub -from subprocess import getstatusoutput +from subprocess import CalledProcessError, check_output, STDOUT from pyrogram.types import Message from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, TEMP_SETTINGS, app @@ -357,6 +357,13 @@ def is_admin(message): user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) return user.status in _admin_status_list +def is_admin_myself(chat): + if not 'group' in chat.type: + return True + + user = app.get_chat_member(chat_id=chat.id, user_id='me') + return user.status in _admin_status_list + def get_download_dir() -> str: dir = './downloads' @@ -365,7 +372,7 @@ def get_download_dir() -> str: def get_duration(media): - out = getstatusoutput( + out = __status_out__( f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "{media}"' ) if LOG_VERBOSE: @@ -373,3 +380,14 @@ def get_duration(media): if out[0] == 0: return int(float(out[1])) return None + +def __status_out__(cmd, encoding='utf-8'): + try: + output = check_output(cmd, shell=True, text=True, stderr=STDOUT, encoding=encoding) + return (0, output) + except CalledProcessError as ex: + return (ex.returncode, ex.output) + except BaseException as e: + if encoding != 'latin-1': + return __status_out__(cmd, 'latin-1') + raise e diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 145392a..8868cfc 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -8,11 +8,10 @@ # from os import remove, path -from subprocess import getstatusoutput -from pyrogram.types.messages_and_media.message import Message +from pyrogram.types import Message -from .misc import MARKDOWN_FIX_CHAR, get_duration +from .misc import MARKDOWN_FIX_CHAR, get_duration, __status_out__ from sedenbot import LOG_VERBOSE @@ -79,7 +78,7 @@ def reply_video( thumb = 'downloads/thumb.png' if path.exists(thumb): remove(thumb) - out = getstatusoutput( + out = __status_out__( f'ffmpeg -i {video} -ss 00:00:01.000 -vframes 1 {thumb}' ) if LOG_VERBOSE: diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 2a58ac3..e7b5bc7 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -509,6 +509,8 @@ "snipsUpdated": "%1Snip successfully updated. You can call the snip with .call $%2%1", "spamInfo": ".tspam <text>\nUsage: Spam the text letter by letter\n\n.spam <count> <text>\nUsage: Floods text in the chat\n\n.picspam <count> <url>\nUsage: As if text spam was not enough\n\n.delayspam <delay> <count> <text>\nUsage: .spam but with custom delay\n\n\nNOTE: Spam at your own risk ! !", "spamLog": "#SPAM\nSpam was executed successfully", + "spamWatchBan": "#SPAMWATCH\nReported user [%1](tg://user?id=%2) banned successfully", + "spamWatchInfo": "Works automatically when you entered a SpamWatch key", "spamWrong": "Something seems missing / wrong.", "specsError": "There is no information about this device or You made too many requests.", "specsError2": "Couldn't get information", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index ca7208c..1ed9dcd 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -509,6 +509,8 @@ "snipsUpdated": "%1Genel not ba\u015far\u0131yla g\u00fcncellendi. Notu .call $%2 yazarak \u00e7a\u011f\u0131rabilirsiniz%1", "spamInfo": ".tspam <metin>\nKullan\u0131m: Verilen mesaj\u0131 tek tek g\u00f6ndererek spam yapar\n\n.spam <miktar> <metin>\nKullan\u0131m: Verilen miktarda spam g\u00f6nderir\n\n.picspam <miktar> <link>\nKullan\u0131m: Verilen miktarda resimli spam g\u00f6nderir\n\n.delayspam <gecikme> <miktar> <metin>\nKullan\u0131m: Verilen miktar ve verilen gecikme ile gecikmeli spam yapar\n\n\nNOT: Sorumluluk size aittir !", "spamLog": "#SPAM\nSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", + "spamWatchBan": "#SPAMWATCH\nRaporlanm\u0131\u015f kullan\u0131c\u0131 [%1](tg://user?id=%2) ba\u015far\u0131yla engellendi", + "spamWatchInfo": "Bir SpamWatch anahtar\u0131 ekledi\u011finizde kendili\u011finden \u00e7al\u0131\u015fmaya ba\u015flar", "spamWrong": "Bir \u015feyler eksik/yanl\u0131\u015f gibi g\u00f6r\u00fcn\u00fcyor.", "specsError": "Bu cihaza dair bir bilgi bulunamad\u0131 veya \u00e7ok fazla istek att\u0131n\u0131z.", "specsError2": "Bilgi al\u0131namad\u0131", From aa668066d19a396abdfa5962da7dbfe3c76da804 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sun, 22 Aug 2021 20:37:48 +0300 Subject: [PATCH 075/242] seden: Update translations Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenecem/translator/en.json | 147 +++++++++++++++++---------------- sedenecem/translator/tr.json | 153 +++++++++++++++++------------------ 2 files changed, 149 insertions(+), 151 deletions(-) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e7b5bc7..b9241a3 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -12,21 +12,21 @@ "afkStart": "AFK AF!", "afkStartReason": "%1AFK AF!\nReason%1: %2%3%2", "afkstr1": "I'm busy right now. Please talk in a bag and when I come back you can just give me the bag!", - "afkstr10": "I'm away over 7 seas and 7 countries,\n7 waters and 7 continents,\n7 mountains and 7 hills,\n7 plains and 7 mounds,\n7 pools and 7 lakes,\n7 springs and 7 meadows,\n7 cities and 7 neighborhoods,\n7 blocks and 7 houses...\n\nWhere not even your messages can reach me!", + "afkstr10": "I'm away over 7 seas and 7 countries,\n7 waters and 7 continents,\n7 mountains and 7 hills,\n7 plains and 7 mounds,\n7 pools and 7 lakes,\n7 springs and 7 meadows,\n7 cities and 7 neighborhoods,\n7 blocks and 7 houses\u2026\n\nWhere not even your messages can reach me!", "afkstr11": "I'm away from the keyboard at the moment, but if you'll scream loud enough at your screen, I might just hear you.", "afkstr12": "I went that way\n---->", "afkstr13": "I went this way\n<----", "afkstr14": "Please leave a message and make me feel even more important than I already am.", "afkstr15": "My F\u00fchrer not here so stop writing to me,\nor else you will find yourself with a screen full of your own messages.", - "afkstr16": "If I were here,\nI'd tell you where I am.\n\nBut I'm not,\nso ask me when I return...", + "afkstr16": "If I were here,\nI'd tell you where I am.\n\nBut I'm not,\nso ask me when I return\u2026", "afkstr17": "I am away!\nI don't know when I'll be back!\nHopefully a few minutes from now!", "afkstr18": "I'm not available right now so please leave your name, number, and address and I will stalk you later.", "afkstr19": "Sorry, I'm not here right now.\nFeel free to talk to my userbot as long as you like.\nI'll get back to you later.", "afkstr2": "I'm away right now. If you need anything, leave a message after the beep:\nbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep!", "afkstr20": "I bet you were expecting an away message!", - "afkstr21": "Life is so short, there are so many things to do...\nI'm away doing one of them..", - "afkstr22": "I am not here right now...\nbut if I was...\n\nwouldn't that be awesome?", - "afkstr3": "I'll be back in a few minutes and if I'm not...,\nwait longer.", + "afkstr21": "Life is so short, there are so many things to do\u2026\nI'm away doing one of them..", + "afkstr22": "I am not here right now\u2026\nbut if I was\u2026\n\nwouldn't that be awesome?", + "afkstr3": "I'll be back in a few minutes and if I'm not\u2026,\nwait longer.", "afkstr4": "I'm not here right now, so I'm probably somewhere else.", "afkstr5": "Roses are red,\nViolets are blue,\nLeave me a message,\nAnd I'll get back to you.", "afkstr6": "Sometimes the best things in life are worth waiting for\u2026\nI'll be right back.", @@ -46,16 +46,16 @@ "answerFromBot": "I didn't get an answer from bot!", "apiHashError": "API HASH Not Set. Please check your config.env file.", "apiIdError": "API ID Not Set. Please check your config.env file.", - "applyEarrape": "Applying earrape...", - "applyNightcore": "Applying nightcore...", - "applySlowedtoperfection": "Applying slowed to perfection...", + "applyEarrape": "Applying earrape\u2026", + "applyNightcore": "Applying nightcore\u2026", + "applySlowedtoperfection": "Applying slowed to perfection\u2026", "autoppConfig": "Please set AUTO_PP variable or put a profile photo.", "autoppDisabled": "Profile photo will no longer change automatically.", "autoppDisabledAlready": "It seems that profile photo doesn't change automatically anyway.", "autoppEnabledAlready": "It seems that profile photo is already changing automatically.", "autoppInfo": ".autopp <disable>\nUsage: This command makes the photo you specify as a profile picture and adds a time. This change it every minute.\nType .autopp to turn it on, .autopp disable to turn it off.\n\nNOTE: There is even a small possibility of getting banned. So use it carefully.", - "autoppLog": "[AUTOPP] File already exists, skipping download ...", - "autoppProcess": "Setting up profile photo ...", + "autoppLog": "[AUTOPP] File already exists, skipping download\u2026", + "autoppProcess": "Setting up profile photo\u2026", "autoppResult": "Profile photo has been updated!", "banAdminError": "I guess i haven't enough perm for this.", "banError": "%1Something went wrong!%1\n\n%2%3%2", @@ -91,7 +91,7 @@ "carbonInfo": ".carbon <text>\nUsage: Beautify your code using carbon.now.sh\nUse .crblang <text> to set language for your code.", "carbonLang": "Language for carbon.now.sh set to %1%2%1", "carbonResult": "Made using [Carbon](https://carbon.now.sh/about/)\na project by [Dawn Labs](https://dawnlabs.io/)", - "carbonUpload": "Uploading...", + "carbonUpload": "Uploading.\u2026", "chatAlreadyMuted": "Chat already muted!", "chatAlreadyUnmuted": "Chat already unmuted!", "chatInfo": ".mutechat\nUsage: Allows you to mute any chat.\n\n.unmutechat\nUsage: Unmutes a muted chat.", @@ -127,8 +127,8 @@ "ddgoInfo": ".ddgo <query>\nUsage: Does a search on DuckDuckGo.", "ddgoLog": "DuckDuckGo Search query %1 was executed successfully", "decodeFail": "Decode failed.", - "deepfryApply": "%1Applying %2fry effect to media...%1", - "deepfryDownload": "Downloading media...", + "deepfryApply": "%1Applying %2fry effect to media\u2026%1", + "deepfryDownload": "Downloading media\u2026", "deepfryError": "I can't deepfry this!", "deepfryInfo": ".deepfry or .fry [1-5]\nUsage: Applies deepfry effect to specified image.", "deepfryNoPic": "%1reply to a picture or sticker for me to %2!%1", @@ -140,7 +140,7 @@ "delayspamLog": "#DELAYSPAM\nDelaySpam was executed successfully", "deleted": "Deleted", "deletedAcc": "Deleted Account", - "demoteProcess": "Demoting...", + "demoteProcess": "Demoting\u2026", "demoteResult": "%1[%2](tg://user?id=%3)%1 %4demoted!%4", "deviceError": "%1Couldn't find info about %2!%1", "deviceSearch": "%1Search results for %2:%1", @@ -152,19 +152,19 @@ "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", "directZippyLink": "%1 (%2)\n[Download](%3)", - "dogbinContent": "Getting dogbin content...", + "dogbinContent": "Getting dogbin content\u2026", "dogbinError": "Request returned an unsuccessful status code.\n\n %1", "dogbinInfo": ".paste <text>\nUsage: Create a paste or a shortened url using dogbin (https://del.dog/)\n\n.getpaste\nUsage: Gets the content of a paste or shortened url from dogbin (https://del.dog/)", "dogbinPasteResult": "%1Pasted successfully!\n\nDogbin URL:%1 %2", "dogbinPasteResult2": "%1Pasted successfully!%1\n\n%1Shortened URL:%1 %2\n\n%1Original(non-shortened) URLs%1\n%1Dogbin URL:%1 %3\n", - "dogbinPasting": "Pasting text...", + "dogbinPasting": "Pasting text\u2026", "dogbinReach": "Failed to reach Dogbin", "dogbinResult": "%1Fetched dogbin URL content successfully!\n\nContent:%1 %2", "dogbinTimeOut": "Request timed out. %1", "dogbinTooManyRedirects": "Request exceeded the configured number of maximum redirections. %1", "dogbinUrlError": "Is that even a dogbin url?", "dogbinUsage": "Elon Musk said i can't paste void.", - "downloadMedia": "Downloading ...", + "downloadMedia": "Downloading\u2026", "downloadMediaError": "My F\u00fchrer havent set me up to download this type of media yet.", "downloadReply": "Please reply a document.", "downloadYTAudio": "%3Downloading%3 %1%2%1 %3as audio%3", @@ -180,17 +180,17 @@ "envNotFound": "%2Variable%2 %1%3%1 %2not found%2", "envRemSuccess": "%2Variable%2 %1%3%1 %2successfully deleted%2", "envSetSuccess": "%2Variable%2 %1%3%1 %2successfully changed%2", - "errorLogSend": "Something went wrong. Sending logs to log group...", + "errorLogSend": "Something went wrong. Sending logs to log group\u2026", "evalLog": "Eval query %1 processed successfully", "evalUsage": "Give a statement to evaluate.", "ezanvaktiErrorInfo": "No information was found for %1.", "ezanvaktiKonum": "Please specify a city next to the command.", "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", - "fetchProxy": "Fetching proxy ...", + "fetchProxy": "Fetching proxy\u2026", "filterAdded": "%2Filter%2 %1%3%1 %2added%2", "filterChats": "Filters in chat:", "filterError": "Message couldn't be forwarded and filter couldn't be added.", - "filterInfo": ".filters\nUsage: Lists all active seden userbot filters in a chat.\n\n.addfilter <keyword> <reply text> or reply to a message with .addfilter <keyword>\nUsage: Saves the replied message as a reply to the 'keyword'.\nThe bot will reply to the message whenever 'keyword' is mentioned.\nWorks with everything from files to stickers.\n\n.stop <filter>\nUsage: Stops the specified filter.", + "filterInfo": ".filters\nUsage: Lists all active filters in a chat.\n\n.addfilter <keyword> <reply text> or reply to a message with .addfilter <keyword>\nUsage: Saves the replied message as a reply to the 'keyword'.\nThe bot will reply to the message whenever 'keyword' is mentioned.\nWorks with everything from files to stickers.\n\n.stop <filter>\nUsage: Stops the specified filter.", "filterLog": "#FILTER\nChat ID: %1%2%1\nFilter: %1%3%1\n\nAbove message saved for reply filter, please don't delete!", "filterNotFound": "%2Filter%2 %1%3%1 %2not found%2", "filterRemoved": "%2Filter%2 %1%3%1 %2removed%2", @@ -248,20 +248,20 @@ "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", - "kangstr1": "Using Witchery to kang this sticker...", - "kangstr10": "Mr.Steal Your Sticker is stealing this sticker... ", - "kangstr11": "Finally I'm kanging a sticker that Ecem will love...", - "kangstr12": "Ecem, this kang is for you...", - "kangstr2": "Plagiarising hehe...", - "kangstr3": "Inviting this sticker over to my pack...", - "kangstr4": "Kanging this sticker...", + "kangstr1": "Using Witchery to kang this sticker\u2026", + "kangstr10": "Mr.Steal Your Sticker is stealing this sticker\u2026", + "kangstr11": "Finally I'm kanging a sticker that Ecem will love\u2026", + "kangstr12": "Ecem, this kang is for you\u2026", + "kangstr2": "Plagiarising hehe\u2026", + "kangstr3": "Inviting this sticker over to my pack\u2026", + "kangstr4": "Kanging this sticker\u2026", "kangstr5": "Hey that's a nice sticker!\nMind if I kang?!..", "kangstr6": "hehe me stel ur stik\u00e9r\nhehe.", - "kangstr7": "Ay look over there (\u2609\uff61\u2609)!\u2192\nWhile I kang this...", + "kangstr7": "Ay look over there (\u2609\uff61\u2609)!\u2192\nWhile I kang this\u2026", "kangstr8": "Roses are red violets are blue, kanging this sticker so my pacc looks cool", - "kangstr9": "Imprisoning this sticker...", + "kangstr9": "Imprisoning this sticker\u2026", "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%3%4)", - "kickProcess": "Kicking...", + "kickProcess": "Kicking\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4", "kickmeResult": "Goodbye.. i go away \ud83e\udd20", "langName": "English", @@ -298,15 +298,15 @@ "lyricsNotFound": "Song %1%2 - %3%1 not found!", "lyricsOutput": "Lyrics is too big, view the file to see it.", "lyricsQuery": "%1Search query:%1 \n%2%3 - %4%2\n\n%1Lyrics:%1\n%5", - "lyricsSearch": "%1Searching lyrics for %2 - %3...%1", + "lyricsSearch": "%1Searching lyrics for %2 - %3\u2026%1", "magiskReleases": "Latest Magisk Releases:", - "makeQuote": "Making a Quote ...", + "makeQuote": "Making a Quote\u2026", "makeqrInfo": ".makeqr <text>\nUsage: Make a QR code from provided content.\nEg: .makeqr https://devotag.com\n\nNote: Use .decode command to get decoded content.", "makeqrUsage": "%1Usage:%1 %2.makeqr <text>%2", "mediaInvalid": "Invalid media.", - "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your seden userbot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", + "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your bot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> ... <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> \u2026 <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", @@ -335,9 +335,9 @@ "ocrApiMissing": "%1[%2](https://ocr.space/ocrapi)%1 %3API key missing! Add it to config vars.%3", "ocrError": "Couldn't read this.\nI guess i need new glasses.", "ocrInfo": ".ocr <language>\nUsage: Reply to an image or sticker to extract text from it.\n\nGet language codes from [here](https://ocr.space/ocrapi)", - "ocrReading": "Reading...", + "ocrReading": "Reading\u2026", "ocrResult": "%1Here's what I could read from it:%1\n\n%2", - "ofrpConnect": "Connecting to OrangeFox servers ...", + "ofrpConnect": "Connecting to OrangeFox servers\u2026", "ofrpError": "This list is probably empty.", "ofrpErrorDate": "An error occurred due to pull date", "ofrpNotFound": "%1%2 codename probably doesn't belong to an official device. You can check it at%1 %3", @@ -378,13 +378,13 @@ "ppSmall": "This image is too small!", "privacySettings": "Privacy settings prevented me from doing this.", "privateUsage": "This command can only be used private chats.", - "processing": "Processing...", + "processing": "Processing\u2026", "profileInfo": ".username <new username>\nUsage: Changes your Telegram username.\n\n.name <firstname> or .name <firstname> <lastname>\nUsage: Changes your Telegram name.(First and last name will get split by the first space)\n\n.setbio <new bio>\nUsage: Changes your Telegram bio.\n\n.reserved\nUsage: Shows usernames reserved by you.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.online <disable>\n\nUsage: It shows your account online even if you are not online on Telegram.\nType .online to turn it on, .online disable to turn it off.\n\nNOTE: Make your last seen settings public to be effective.", "promoteLog": "#PROMOTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", - "promoteProcess": "Promoting...", + "promoteProcess": "Promoting\u2026", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4promoted!%4", - "providedProxy": "Proxy connection provided ...", - "purgeError": "%1Something went wrong...%1\n\n%2%3%2", + "providedProxy": "Proxy connection provided\u2026", + "purgeError": "%1Something went wrong\u2026%1\n\n%2%3%2", "purgeInfo": ".purge\nUsage: Purges all messages starting from the reply.", "purgeLog": "%1Purge of%1 %2%3%2 %1messages done successfully.%1", "purgeResult": "%1Purge complete!\nPurged%1 %2%3%2 %1messages.%1", @@ -397,34 +397,34 @@ "randomUsage": "2 or more argument required.", "rbgApiMissing": "%1[%2](https://www.remove.bg/api)%1 %3API key missing! Add it to config vars.%3", "rbgInfo": ".rbg reply to any image (Warning: does not work on stickers.)\nUsage: Removes the background of images, using remove.bg API", - "rbgLog": "error.log", + "rbgLog": "error.txt", "rbgProcessing": "Removing background from this image..", "rbgResult": "Background removed using Remove.bg", - "rbgUsage": "Reply to an image...", + "rbgUsage": "Reply to an image\u2026", "removeFirstLine": "Please remove the first specified line from config.env file", "repeatInfo": ".repeat <no.> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.", "replyMessage": "Reply to a message.", "replySticker": "Reply a sticker.", - "restart": "BRB... *PornHub intro*", + "restart": "BRB\u2026 *PornHub intro*", "restartLog": "#RESTART\nBot restarted", "reverseError": "Unsupported type", "reverseError2": "I couldn't find anything for your ugly ass.", "reverseGoogle": "Google told me to fuck off", "reverseInfo": ".reverse\nUsage: Reply to a pic/sticker to revers-search it on Google Images !", "reverseProcess": "Image successfully uploaded to Google. Parsing source now. Maybe.", - "reverseResult": "[%1](%2)\n%3\n\nLooking for a picture...\n%3", + "reverseResult": "[%1](%2)\n%3\n\nLooking for a picture\u2026\n%3", "reverseResult2": "[%1](%2)\n\n[Similar images](%3)", "reverseUsage": "Please reply to a photo or sticker.", "rgbInfo": ".rgb\nUsage: Text and generate rgb sticker.", - "rgbProcessing": "Converting to rgb sticker...", + "rgbProcessing": "Converting to rgb sticker\u2026", "runningBot": "Your bot is running! You can test it by typing .alive in any chat. You can get the list of modules by typing .seden If you need help, you can check out our support group https://t.me/%1", "runstr1": "Where do you think you're going?", - "runstr10": "You're gonna regret that...", + "runstr10": "You're gonna regret that\u2026", "runstr11": "You could also try /kickme, I hear that's fun.", "runstr12": "Go bother someone else, no-one here cares.", "runstr13": "You can run, but you can't hide.", "runstr14": "Is that all you've got?", - "runstr15": "I'm behind you...", + "runstr15": "I'm behind you\u2026", "runstr16": "You've got company!", "runstr17": "We can do this the easy way, or the hard way.", "runstr18": "You just don't get it, do you?", @@ -439,8 +439,8 @@ "runstr26": "\"Oh, look at me! I'm so cool, I can run from a bot!\" - this person", "runstr27": "Yeah yeah, just tap /kickme already.", "runstr28": "Here, take this ring and head to Mordor while you're at it.", - "runstr29": "Legend has it, they're still running...", - "runstr3": "ZZzzZZzz... Huh? what? oh, just them again, nevermind.", + "runstr29": "Legend has it, they're still running\u2026", + "runstr3": "ZZzzZZzz\u2026 Huh? what? oh, just them again, nevermind.", "runstr30": "Unlike Harry Potter, your parents can't protect you from me.", "runstr31": "Fear leads to anger. Anger leads to hate. Hate leads to suffering. If you keep running in fear, you might be the next Vader.", "runstr32": "Multiple calculations later, I have decided my interest in your shenanigans is exactly 0.", @@ -454,14 +454,14 @@ "runstr4": "Get back here!", "runstr40": "Ah, what a waste. I liked that one.", "runstr41": "Frankly, my dear, I don't give a damn.", - "runstr42": "My milkshake brings all the boys to yard... So run faster!", + "runstr42": "My milkshake brings all the boys to yard\u2026 So run faster!", "runstr43": "You can't HANDLE the truth!", - "runstr44": "A long time ago, in a galaxy far far away... Someone would've cared about that. Not anymore though.", - "runstr45": "Hey, look at them! They're running from the inevitable banhammer... Cute.", + "runstr44": "A long time ago, in a galaxy far far away\u2026 Someone would've cared about that. Not anymore though.", + "runstr45": "Hey, look at them! They're running from the inevitable banhammer\u2026 Cute.", "runstr46": "Han shot first. So will I.", "runstr47": "What are you running after, a white rabbit?", - "runstr48": "As The Doctor would say... RUN!", - "runstr5": "Not so fast...", + "runstr48": "As The Doctor would say\u2026 RUN!", + "runstr5": "Not so fast\u2026", "runstr6": "Look out for the wall!", "runstr7": "Don't leave me alone with them!!", "runstr8": "You run, you die.", @@ -482,7 +482,7 @@ "sedenAlive": "Hello Seden! I am alive \u2764\ufe0f", "sedenErrorResult": "Query could not be returned / wrong", "sedenErrorText": "%1ERROR:%1\n\n%2%3%2\n\n%1See the Log File for details.%1", - "sedenErrorText2": "========== DISCLAIMER ==========\nThis file uploaded only here,\nwe logged only fact of error and date,\nour respect your privacy,'\nyou may not report this error if you've\nany confidential data here, no one will see your data.\n================================\n\n--------BEGIN SEDENBOT TRACEBACK LOG--------\n\nDate: %1\nChat ID: %2\nSender ID: %3\nSeden version: %4\n\nError Trigger:\n%5\n\nTraceback info:\n%6\n\nError text:\n%7\n\n--------END SEDENBOT TRACEBACK LOG--------\n\n\nLast 10 commits:\n", + "sedenErrorText2": "-------- DETAILS --------\n\nDate: %1\nChat ID: %2\nSender ID: %3\nSeden version: %4\n\nError Trigger:\n%5\n\nTraceback info:\n%6\n\nError text:\n%7\n\n----------------------\n\n\nLast 10 commits:\n", "sedenGitNotFound": "btw, Seden loves u \u2764\ufe0f", "sedenNearestDC": "%1Country:%1 %2%3%2\n%1Nearest Datacenter:%1 %2%4%2\n%1Current Datacenter:%1 %2%5%2", "sedenQuery": "%1Query:%1\n%2%3%2\n%1Result:%1\n%2%4%2\n", @@ -493,7 +493,7 @@ "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", - "shutdown": "Goodbye *Windows XP shutdown sound*...", + "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", "snipError": "Message couldn't be forwarded and snip couldn't be added.", @@ -521,8 +521,8 @@ "speedtestResultDoc": "%1SpeedTest%1 completed in %2 seconds!", "speedtestResultText": "%1SpeedTest%1 completed in %2 seconds!\nDownload: %3\nUpload: %4\nPing: %5\nISP: %6\nISP rating: %7\n%8", "ssInfo": ".ss <url>\nUsage: Takes a screenshot of a website and sends the screenshot.\nExample of a valid URL: https://devotag.com", - "ssResult": "Generating screenshot of the page...\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", - "ssUpload": "Uploading screenshot ...", + "ssResult": "Generating screenshot of the page\u2026\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", + "ssUpload": "Uploading screenshot\u2026", "ssUsage": "You must provide a valid link before I can take a screenshot.", "statsResult": "%1Total Chats:%1 %2%3%2\n%1Channels:%1 %2%4%2\n%1Groups:%1 %2%5%2\n%1Super Groups:%1 %2%6%2\n%1Bots:%1 %2%7%2\n%1PM's:%1 %2%8%2\n%1Unread Messages:%1 %2%9%2", "statusLong": "A long time ago", @@ -539,13 +539,13 @@ "sudoCheck": "This person is Seden admin!", "supportResult": "You can reach our support group [here](http://t.me/%1).", "syntaxError": "Syntax error.", - "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound... but you don/'t.", + "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound\u2026 but you don/'t.", "termHelp": "You can look at example by typing .seden system for help.", "termLog": "Terminal command %1 executed successfully", "termNoResult": "No result", "termUsage": "Please write a command.", "testException": "This is a test, don't submit a bug log.", - "testLogId": "Testing LOG_ID value ...", + "testLogId": "Testing LOG_ID value\u2026", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", "trtError": "Invalid destination language.", @@ -563,40 +563,39 @@ "udInfo": ".ud <query>\nDoes a search on Urban Dictionary.", "udNoResult": "%1No result for%1 %2", "udResult": "Sorry, No results were found for %1%2%1", - "unbanProcess": "Unbanning...", + "unbanProcess": "Unbanning\u2026", "unbanResult": "%1[%2](tg://user?id=%3)%1 %4unbanned!%4", "unblockChat": "%2Please unblock%2 %1@%3%1 %2and try again%2", - "unmuteProcess": "Unmuting...", + "unmuteProcess": "Unmuting\u2026", "unmuteResult": "%1[%2](tg://user?id=%3)%1 %4unmuted!%4", - "updateBotUpdating": "SedenBot Updating...\nThis process may take 1-2 minutes, please wait patiently. Worth your wait :)", - "updateCheck": "Checking updates for SedenBot...", - "updateComplete": "Update successfully completed!\nSedenBot is rebooting, thank you for waiting patiently :)", - "updateCustomBranch": "%1[SedenBot Updater]:%1` Looks like you are using your own custom branch: %2. If this happens, I cannot update your bot.\nBecause the name of the branch does not match..\nPlease use your bot from the SedenBot official GitHub repo.", + "updateBotUpdating": "Bot Updating\u2026", + "updateCheck": "Checking updates\u2026", + "updateComplete": "Update completed!\nRebooting.", "updateFailed": "Update failed! We ran into some problems.", "updateFolderError": "%1\n%2%3 folder was not found.%2", - "updateForceSync": "Force syncing to latest SedenBot, please wait...", + "updateForceSync": "Force syncing to latest repo, please wait\u2026", "updateGitError": "%1\n%2Git error! %3%2", "updateGitNotFound": "%1 folder doesn't look like a git repo.\nHowever, you can solve this problem by force updating the bot with the .update now command.", "updateHerokuAppName": "Please set up the HEROKU_APPNAME variable to be able to update bot.", "updateHerokuVariables": "%1\nHeroku variables are incorrectly or under defined.", "updateInfo": ".update\nUsage: Checks if seden userbot repository has any updates and shows a changelog if so.\n\n.update now\nUsage: Updates your userbot, if there are any updates in seden userbot repository.", - "updateLocalComplate": "Update successfully completed!\nSedenBot is rebooting.", + "updateLocalComplate": "Update completed!\nRebooting.", "updateLog": "LOG:", "updateNow": "%1do%1 \"%2.update now%2\" %1to update.%1", "updateOutput": "Changelog is too big, view the file to see it.", - "updateSedenBot": "Updating bot, please wait...", + "updateSedenBot": "Updating bot, please wait\u2026", "updated": "updated", "updaterGitError": "%2\n%1Here is the error log:%1\n%3", "updaterHasUpdate": "%1New update available for %3!\n\nChangelog:%1\n%4", "updaterUsingLatest": "%2Your bot is%2 %1up-to-date%1 %2Branch:%2 %1%3%1", - "updownDownload": "%1Downloading ... %2%1", + "updownDownload": "%1Downloading\u2026 %2%1", "updownDownloadSuccess": "Successfully downloaded to %1%2%1", - "updownUpload": "%1Uploading ... %2%1\n%3", + "updownUpload": "%1Uploading\u2026 %2%1\n%3", "uploadError": "File couldn't be uploaded.", "uploadFileError": "File not found.", "uploadFinish": "Uploading completed!", "uploadInfo": ".download reply to media \nUsage: Downloads file to server.\n\n.upload <path in server>\nUsage: Uploads a locally stored file to chat.", - "uploadMedia": "Uploading media...", + "uploadMedia": "Uploading media\u2026", "uploadReply": "I cant upload nothing here.", "urlError": "Error: Unable to extract link", "useridResult": "%1Username:%1 %2\n%1User ID:%1 %3%4%3", @@ -622,11 +621,11 @@ "youtubedlInfo": ".youtubedl <mp3 or mp4> <url>\nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354", "zombiesError": "I don't have enough rights.", - "zombiesFind": "Finding deleted accounts...", + "zombiesFind": "Finding deleted accounts\u2026", "zombiesFound": "%1Found%1 %2%3%2 %1deleted account.\nClean them by using%1 %2.zombies clean%2", "zombiesLog": "#CLEANUP\n%1Removed%1 %2%3%2 %1deleted account.\nGROUP: %4%1 | %2%5%2", "zombiesNoAccount": "No deleted account(s) found!", - "zombiesRemove": "Removing deleted account(s)...", + "zombiesRemove": "Removing deleted account(s)\u2026", "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" } \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 1ed9dcd..fa3ecc6 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -12,21 +12,21 @@ "afkStart": "Art\u0131k AFK'y\u0131m.", "afkStartReason": "%1Art\u0131k AFK'y\u0131m.\nSebep%1: %2%3%2", "afkstr1": "\u015eu an acele i\u015fim var, daha sonra mesaj atsan olmaz m\u0131? Zaten yine gelece\u011fim.", - "afkstr10": "7 deniz ve 7 \u00fclkeden uzaktay\u0131m,\n7 su ve 7 k\u0131ta,\n7 da\u011f ve 7 tepe,\n7 ovala ve 7 h\u00f6y\u00fck,\n7 havuz ve 7 g\u00f6l,\n7 bahar ve 7 \u00e7ay\u0131r,\n7 \u015fehir ve 7 mahalle,\n7 blok ve 7 ev...\n\nMesajlar\u0131n bile bana ula\u015famayaca\u011f\u0131 bir yer!", + "afkstr10": "7 deniz ve 7 \u00fclkeden uzaktay\u0131m,\n7 su ve 7 k\u0131ta,\n7 da\u011f ve 7 tepe,\n7 ovala ve 7 h\u00f6y\u00fck,\n7 havuz ve 7 g\u00f6l,\n7 bahar ve 7 \u00e7ay\u0131r,\n7 \u015fehir ve 7 mahalle,\n7 blok ve 7 ev\u2026\n\nMesajlar\u0131n bile bana ula\u015famayaca\u011f\u0131 bir yer!", "afkstr11": "\u015eu anda klavyeden uzaktay\u0131m, ama ekran\u0131n\u0131zda yeterince y\u00fcksek sesle \u00e7\u0131\u011fl\u0131k atarsan\u0131z, sizi duyabilirim.", "afkstr12": "\u015eu y\u00f6nde ilerliyorum\n---->", "afkstr13": "\u015eu y\u00f6nde ilerliyorum\n<----", "afkstr14": "L\u00fctfen mesaj b\u0131rak\u0131n ve beni zaten oldu\u011fumdan daha \u00f6nemli hissettirin.", "afkstr15": "Sahibim burada de\u011fil, bu y\u00fczden bana yazmay\u0131 b\u0131rak.", - "afkstr16": "Burada olsayd\u0131m,\nSana nerede oldu\u011fumu s\u00f6ylerdim.\n\nAma ben de\u011filim,\ngeri d\u00f6nd\u00fc\u011f\u00fcmde bana sor...", + "afkstr16": "Burada olsayd\u0131m,\nSana nerede oldu\u011fumu s\u00f6ylerdim.\n\nAma ben de\u011filim,\ngeri d\u00f6nd\u00fc\u011f\u00fcmde bana sor\u2026", "afkstr17": "Uzaklarday\u0131m!\nNe zaman d\u00f6nerim bilmiyorum !\nUmar\u0131m birka\u00e7 dakika sonra!", "afkstr18": "Sahibim \u015fu anda m\u00fcsait de\u011fil. Ad\u0131n\u0131z\u0131, numaran\u0131z\u0131 ve adresinizi verirseniz ona iletibilirim ve b\u00f6ylelikle geri d\u00f6nd\u00fc\u011f\u00fc zaman sizinle ilgilenebilir.", "afkstr19": "\u00dczg\u00fcn\u00fcm, sahibim burada de\u011fil.\nO gelene kadar benimle konu\u015fabilirsiniz.\nSahibim size sonra d\u00f6ner.", "afkstr2": "Arad\u0131\u011f\u0131n\u0131z ki\u015fi \u015fu anda telefona cevap veremiyor. Sinyal sesinden sonra kendi tarifeniz \u00fczerinden mesaj\u0131n\u0131z\u0131 b\u0131rakabilirsiniz. Mesaj \u00fccreti 49 kuru\u015ftur. \nbiiiiiiiiiiiiiiiiiiiiiiiiiiiiip!", "afkstr20": "Bahse girerim bir mesaj bekliyordun!", - "afkstr21": "Hayat \u00e7ok k\u0131sa, yapacak \u00e7ok \u015fey var...\nOnlardan birini yap\u0131yorum...", - "afkstr22": "\u015eu an burada de\u011filim....\nama \u00f6yleysem ...\n\nbu harika olmaz m\u0131yd\u0131?", - "afkstr3": "Birka\u00e7 dakika i\u00e7inde gelece\u011fim. Fakat gelmezsem...\ndaha fazla bekle.", + "afkstr21": "Hayat \u00e7ok k\u0131sa, yapacak \u00e7ok \u015fey var\u2026\nOnlardan birini yap\u0131yorum\u2026", + "afkstr22": "\u015eu an burada de\u011filim\u2026\nama \u00f6yleysem\u2026\n\nbu harika olmaz m\u0131yd\u0131?", + "afkstr3": "Birka\u00e7 dakika i\u00e7inde gelece\u011fim. Fakat gelmezsem\u2026\ndaha fazla bekle.", "afkstr4": "\u015eu an burada de\u011filim, muhtemelen ba\u015fka bir yerdeyim.", "afkstr5": "G\u00fcller k\u0131rm\u0131z\u0131\nMenek\u015feler mavi\nBana bir mesaj b\u0131rak\nVe sana d\u00f6nece\u011fim.", "afkstr6": "Bazen hayattaki en iyi \u015feyler beklemeye de\u011fer\u2026\nHemen d\u00f6nerim.", @@ -46,16 +46,16 @@ "answerFromBot": "Botdan cevap alamad\u0131m!", "apiHashError": "API HASH Ayarlanmad\u0131. L\u00fctfen config.env dosyas\u0131n\u0131z kontrol edin.", "apiIdError": "API ID Ayarlanmad\u0131. L\u00fctfen config.env dosyas\u0131n\u0131z kontrol edin.", - "applyEarrape": "Earrape efekti uygulan\u0131yor...", - "applyNightcore": "Nightcore uygulan\u0131yor...", - "applySlowedtoperfection": "Slowed to perfection uygulan\u0131yor...", + "applyEarrape": "Earrape efekti uygulan\u0131yor\u2026", + "applyNightcore": "Nightcore uygulan\u0131yor\u2026", + "applySlowedtoperfection": "Slowed to perfection uygulan\u0131yor\u2026", "autoppConfig": "L\u00fctfen AUTO_PP de\u011fi\u015fkeninizi ayarlay\u0131n veya bir profil foto\u011fraf\u0131 koyun.", "autoppDisabled": "Profil foto\u011fraf\u0131n\u0131z art\u0131k otomatik olarak de\u011fi\u015fmeyecek.", "autoppDisabledAlready": "G\u00f6r\u00fcn\u00fc\u015fe g\u00f6re profil foto\u011fraf\u0131n\u0131z zaten otomatik olarak de\u011fi\u015fmiyor.", "autoppEnabledAlready": "G\u00f6r\u00fcn\u00fc\u015fe g\u00f6re profil foto\u011fraf\u0131n\u0131z zaten otomatik olarak de\u011fi\u015fiyor.", "autoppInfo": ".autopp <disable>\nKullan\u0131m: Bu komut belirledi\u011finiz foto\u011fraf\u0131 profil resmi yapar\nve bir saat ekler. Bu saat her dakika de\u011fi\u015fir.\nA\u00e7mak i\u00e7in .autopp, kapatmak i\u00e7in .autopp disable yaz\u0131n.\n\nNOT: K\u00fc\u00e7\u00fck bir ihtimal bile olsa ban yeme riskiniz var. Bu y\u00fczden dikkatli kullan\u0131n.", - "autoppLog": "[AUTOPP] Dosya zaten mevcut, indirme atlan\u0131yor ...", - "autoppProcess": "Profil foto\u011fraf\u0131n\u0131z ayarlan\u0131yor ...", + "autoppLog": "[AUTOPP] Dosya zaten mevcut, indirme atlan\u0131yor\u2026", + "autoppProcess": "Profil foto\u011fraf\u0131n\u0131z ayarlan\u0131yor\u2026", "autoppResult": "Profil foto\u011fraf\u0131n\u0131z ayarland\u0131!", "banAdminError": "San\u0131r\u0131m bunun i\u00e7in yeterli yetkim olmayabilir.", "banError": "%1Bir hata olu\u015ftu!%1\n\n%2%3%2", @@ -92,7 +92,7 @@ "carbonInfo": ".carbon <metin>\nKullan\u0131m: carbon.now.sh sitesini kullanarak yazd\u0131klar\u0131n\u0131n a\u015f\u015f\u015f\u015f\u015f\u015f\u0131r\u0131 \u015fekil g\u00f6r\u00fcnmesini sa\u011flar.\n.crblang <dil> komutuyla varsay\u0131lan dilini ayarlayabilirsin.", "carbonLang": "Karbon mod\u00fcl\u00fc i\u00e7in varsay\u0131lan dil %1%2%1 olarak ayarland\u0131.", "carbonResult": "Bu resim [Carbon](https://carbon.now.sh/about/) kullan\u0131larak yap\u0131ld\u0131,\nbir [Dawn Labs](https://dawnlabs.io/) projesi.", - "carbonUpload": "Resim kar\u015f\u0131ya y\u00fckleniyor...", + "carbonUpload": "Resim kar\u015f\u0131ya y\u00fckleniyor\u2026", "chatAlreadyMuted": "Sohbet zaten susturulmu\u015f!", "chatAlreadyUnmuted": "Sohbetin zaten sesi a\u00e7\u0131lm\u0131\u015f!", "chatInfo": ".mutechat\nKullan\u0131m: Belirlenen grubu susturur.\n\n.unmutechat\nKullan\u0131m: Susturulmu\u015f bir sohbetin sesini a\u00e7ar.", @@ -128,8 +128,8 @@ "ddgoInfo": ".ddgo <kelime>\nKullan\u0131m: H\u0131zl\u0131 bir DuckDuckGo aramas\u0131 yapar.", "ddgoLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla DuckDuckGo'da arat\u0131ld\u0131!", "decodeFail": "Decode ba\u015far\u0131s\u0131z oldu.", - "deepfryApply": "%1Medyaya %2fry efekti uygulan\u0131yor...%1", - "deepfryDownload": "Medya indiriliyor...", + "deepfryApply": "%1Medyaya %2fry efekti uygulan\u0131yor\u2026%1", + "deepfryDownload": "Medya indiriliyor\u2026", "deepfryError": "Bunu deepfry yapamam!", "deepfryInfo": ".deepfry veya .fry [numara 1-5]\nKullan\u0131m: Belirlenen g\u00f6r\u00fcnt\u00fcye deepfry efekti uygular.", "deepfryNoPic": "%1%2 yapmam i\u00e7in bir resim veya \u00e7\u0131kartma al\u0131nt\u0131lamal\u0131s\u0131n!%1", @@ -141,7 +141,7 @@ "delayspamLog": "#DELAYSPAM\nDelaySpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "deleted": "Silinmi\u015f", "deletedAcc": "Silinen Hesap", - "demoteProcess": "Yetki d\u00fc\u015f\u00fcr\u00fcl\u00fcyor...", + "demoteProcess": "Yetki d\u00fc\u015f\u00fcr\u00fcl\u00fcyor\u2026", "demoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici de\u011fil!%4", "deviceError": "%1%2cihaz\u0131 i\u00e7in bilgi bulunamad\u0131!%1", "deviceSearch": "%1%2 i\u00e7in arama sonu\u00e7lar\u0131:%1", @@ -153,19 +153,19 @@ "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", "directZippyLink": "%1 (%2)\n[\u0130ndir](%3)", - "dogbinContent": "Dogbin i\u00e7eri\u011fi al\u0131n\u0131yor...", + "dogbinContent": "Dogbin i\u00e7eri\u011fi al\u0131n\u0131yor\u2026", "dogbinError": "\u0130stek ba\u015far\u0131s\u0131z bir durum kodu d\u00f6nd\u00fcrd\u00fc.\n\n %1", "dogbinInfo": ".paste <metin>\nKullan\u0131m: Dogbin kullanarak yap\u0131\u015ft\u0131r\u0131lm\u0131\u015f veya k\u0131salt\u0131lm\u0131\u015f url olu\u015fturma (https://del.dog/)\n\n.getpaste\nKullan\u0131m: Dogbin url i\u00e7eri\u011fini metne aktar\u0131r (https://del.dog/)", "dogbinPasteResult": "%1Ba\u015far\u0131yla yap\u0131\u015ft\u0131r\u0131ld\u0131!\n\nDogbin URL:%1 %2", "dogbinPasteResult2": "%1Ba\u015far\u0131yla yap\u0131\u015ft\u0131r\u0131ld\u0131!%1\n\n%1K\u0131salt\u0131lm\u0131\u015f link:%1 %2\n\n%1K\u0131salt\u0131lmam\u0131\u015f linkler%1\n%1Dogbin linki:%1 %3\n", - "dogbinPasting": "Metin yap\u0131\u015ft\u0131r\u0131l\u0131yor...", + "dogbinPasting": "Metin yap\u0131\u015ft\u0131r\u0131l\u0131yor\u2026", "dogbinReach": "Dogbine ula\u015f\u0131lamad\u0131", "dogbinResult": "%1Dogbin URL i\u00e7eri\u011fi ba\u015far\u0131yla getirildi!\n\n\u0130\u00e7erik:%1 %2", "dogbinTimeOut": "\u0130stek zaman a\u015f\u0131m\u0131na u\u011frad\u0131. %1", "dogbinTooManyRedirects": "\u0130stek, yap\u0131land\u0131r\u0131lm\u0131\u015f en fazla y\u00f6nlendirme say\u0131s\u0131n\u0131 a\u015ft\u0131. %1", "dogbinUrlError": "Bu bir Dogbin URL'si mi?", "dogbinUsage": "Elon Musk bo\u015flu\u011fu yap\u0131\u015ft\u0131ramayaca\u011f\u0131m\u0131 s\u00f6yledi.", - "downloadMedia": "\u0130ndiriliyor ...", + "downloadMedia": "\u0130ndiriliyor\u2026", "downloadMediaError": "Hen\u00fcz yap\u0131mc\u0131lar\u0131m bu t\u00fcrde bir medyay\u0131 indirmem i\u00e7in beni ayarlamam\u0131\u015f.", "downloadReply": "Please reply a document.", "downloadYTAudio": "%1%2%1 %3Ses olarak indiriliyor%3", @@ -181,13 +181,13 @@ "envNotFound": "%1%3%1 %2de\u011fi\u015fkeni bulunamad\u0131%2", "envRemSuccess": "%1%3%1 %2de\u011fi\u015fkeni ba\u015far\u0131yla silindi%2", "envSetSuccess": "%1%3%1 %2de\u011fi\u015fkeni ba\u015far\u0131yla de\u011fi\u015ftirildi%2", - "errorLogSend": "Bir sorun olu\u015ftu, kay\u0131tlar\u0131 log grubuna g\u00f6nderiyorum...", + "errorLogSend": "Bir sorun olu\u015ftu, kay\u0131tlar\u0131 log grubuna g\u00f6nderiyorum\u2026", "evalLog": "Eval sorgusu %1 ba\u015far\u0131yla y\u00fcr\u00fct\u00fcld\u00fc", "evalUsage": "De\u011ferlendirmek i\u00e7in bir ifade verin.", "ezanvaktiErrorInfo": "%1 i\u00e7in bir bilgi bulunamad\u0131.", "ezanvaktiKonum": "L\u00fctfen komutun yan\u0131na bir \u015fehir belirtin", "ezanvaktiShowInfo": "%1Diyanet Namaz Vakitleri%1\n\n\ud83d\udccd %1Yer:%1 %2%3%2\n\n\ud83c\udfd9 %1\u0130msak:%1 %2%4%2\n\ud83c\udf05 %1G\u00fcne\u015f:%1 %2%5%2\n\ud83c\udf07 %1\u00d6\u011fle:%1 %2%6%2\n\ud83c\udf06 %1\u0130kindi:%1 %2%7%2\n\ud83c\udf03 %1Ak\u015fam:%1 %2%8%2\n\ud83c\udf0c %1Yats\u0131:%1 %2%9%2", - "fetchProxy": "Proxy getiriliyor ...", + "fetchProxy": "Proxy getiriliyor\u2026", "filterAdded": "%2Filtre%2 %1%3%1 %2eklendi%2", "filterChats": "Sohbetteki filtreler:", "filterError": "Mesaj y\u00f6nlendirilemedi ve filtre eklenemedi.", @@ -222,7 +222,7 @@ "gitRepo": "Depo bulunamad\u0131.", "gitRepoList": "Depolar:", "gitTotalGist": "Toplam gist", - "gitTotalRepo": "Toplam Repo", + "gitTotalRepo": "Toplam Depo", "gitTwitter": "Twitter", "gitUsage": "Kullan\u0131m: .github <kullan\u0131c\u0131-ad\u0131>", "gitUser": "Kullan\u0131c\u0131 ID", @@ -250,20 +250,20 @@ "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", - "kangstr1": "\u00c7\u0131kartmay\u0131 d\u0131zl\u0131yorum...", - "kangstr10": "Bay d\u0131zc\u0131 bu \u00e7\u0131kartmay\u0131 d\u0131zl\u0131yor...", - "kangstr11": "Sonunda Ecem'in sevece\u011fi bir \u00e7\u0131kartma d\u0131zl\u0131yorum...", - "kangstr12": "Ecem, bu d\u0131z senin i\u00e7in...", - "kangstr2": "Ya\u015fas\u0131n d\u0131zc\u0131l\u0131k...", - "kangstr3": "Bu \u00e7\u0131kartmay\u0131 kendi paketime davet ediyorum...", - "kangstr4": "Bunu d\u0131zlamam laz\u0131m...", - "kangstr5": "Hey bu g\u00fczel bir \u00e7\u0131kartma!\nHemen d\u0131zl\u0131yorum...", + "kangstr1": "\u00c7\u0131kartmay\u0131 d\u0131zl\u0131yorum\u2026", + "kangstr10": "Bay d\u0131zc\u0131 bu \u00e7\u0131kartmay\u0131 d\u0131zl\u0131yor\u2026", + "kangstr11": "Sonunda Ecem'in sevece\u011fi bir \u00e7\u0131kartma d\u0131zl\u0131yorum\u2026", + "kangstr12": "Ecem, bu d\u0131z senin i\u00e7in\u2026", + "kangstr2": "Ya\u015fas\u0131n d\u0131zc\u0131l\u0131k\u2026", + "kangstr3": "Bu \u00e7\u0131kartmay\u0131 kendi paketime davet ediyorum\u2026", + "kangstr4": "Bunu d\u0131zlamam laz\u0131m\u2026", + "kangstr5": "Hey bu g\u00fczel bir \u00e7\u0131kartma!\nHemen d\u0131zl\u0131yorum\u2026", "kangstr6": "\u00c7\u0131kartman\u0131 d\u0131zl\u0131yorum\nhahaha.", - "kangstr7": "Hey \u015furaya bak. (\u2609\uff61\u2609)!\u2192\nBen bunu d\u0131zlarken...", - "kangstr8": "G\u00fcller k\u0131rm\u0131z\u0131 menek\u015feler mavi, bu \u00e7\u0131kartmay\u0131 paketime d\u0131zlayarak haval\u0131 olaca\u011f\u0131m...", - "kangstr9": "\u00c7\u0131kartma hapsediliyor...", + "kangstr7": "Hey \u015furaya bak. (\u2609\uff61\u2609)!\u2192\nBen bunu d\u0131zlarken\u2026", + "kangstr8": "G\u00fcller k\u0131rm\u0131z\u0131 menek\u015feler mavi, bu \u00e7\u0131kartmay\u0131 paketime d\u0131zlayarak haval\u0131 olaca\u011f\u0131m\u2026", + "kangstr9": "\u00c7\u0131kartma hapsediliyor\u2026", "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", - "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor...", + "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4", "kickmeResult": "G\u00fcle G\u00fcle ben gidiyorum \ud83e\udd20", "langName": "T\u00fcrk\u00e7e", @@ -300,18 +300,18 @@ "lyricsNotFound": "\u015eark\u0131 %1%2 - %3%1 bulunamad\u0131!", "lyricsOutput": "\u015eark\u0131 s\u00f6zleri \u00e7ok uzun, g\u00f6rmek i\u00e7in dosyay\u0131 g\u00f6r\u00fcnt\u00fcleyin.", "lyricsQuery": "%1Arama sorgusu:%1 \n%2%3 - %4%2\n\n%1S\u00f6zler:%1\n%5", - "lyricsSearch": "%1%2 - %3 i\u00e7in \u015fark\u0131 s\u00f6zleri aran\u0131yor...%1", + "lyricsSearch": "%1%2 - %3 i\u00e7in \u015fark\u0131 s\u00f6zleri aran\u0131yor\u20261", "magiskReleases": "G\u00fcncel Magisk s\u00fcr\u00fcmleri:", - "makeQuote": "Al\u0131nt\u0131 yap\u0131l\u0131yor ...", + "makeQuote": "Al\u0131nt\u0131 yap\u0131l\u0131yor\u2026", "makeqrInfo": ".makeqr <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir QR kodu yap\u0131n.\n\u00d6rnek: .makeqr https://devotag.com\n\nNot: \u00c7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", "makeqrUsage": "%1Kullan\u0131m:%1 %2.makeqr <metin>%2", "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.\n\n.amogus <metin>\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", - "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> ... <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Repo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.\n\n.invitelink\nKullan\u0131m: Mevcut grubun davet ba\u011flant\u0131s\u0131 verir.", + "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> \u2026 <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Depo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.\n\n.invitelink\nKullan\u0131m: Mevcut grubun davet ba\u011flant\u0131s\u0131 verir.", "mockUsage": "bANa bIr mETin vEr!", "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", - "muteProcess": "Sessize al\u0131n\u0131yor...", + "muteProcess": "Sessize al\u0131n\u0131yor\u2026", "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4", "nameOk": "Ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", "neofetchNotFound": "L\u00fctfen neofetch y\u00fckleyin.", @@ -337,9 +337,9 @@ "ocrApiMissing": "%1[%2](https://ocr.space/ocrapi)%1 %3API key eksik! L\u00fctfen ekleyin.%3", "ocrError": "Bunu okuyamad\u0131m.\nSan\u0131r\u0131m yeni g\u00f6zl\u00fcklere ihtiyac\u0131m var.", "ocrInfo": ".ocr <dil>\nKullan\u0131m: Metin ay\u0131klamak i\u00e7in bir resme veya \u00e7\u0131kartmaya cevap verin.\n\nDil kodlar\u0131n\u0131 [buradan](https://ocr.space/ocrapi) al\u0131n.", - "ocrReading": "Okunuyor...", + "ocrReading": "Okunuyor\u2026", "ocrResult": "%1\u0130\u015fte okuyabildi\u011fim \u015fey:%1\n\n%2", - "ofrpConnect": "OrangeFox sunucular\u0131na ba\u011flan\u0131l\u0131yor ...", + "ofrpConnect": "OrangeFox sunucular\u0131na ba\u011flan\u0131l\u0131yor\u2026", "ofrpError": "Muhtemelen bu liste bo\u015f.", "ofrpErrorDate": "Tarih bilgisi \u00e7ekilirken bir hata olu\u015ftu", "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.%1 %3 %1adresinden kontrol edebilirsiniz.%1", @@ -368,7 +368,7 @@ "pmUnblocked": "Engelin kald\u0131r\u0131ld\u0131.", "pmUnblockedLog": "[%1](tg://user?id=%2) ki\u015fisinin engeli kald\u0131r\u0131ld\u0131.", "pmUnblockedUsage": "Engelini kald\u0131raca\u011f\u0131n ki\u015finin mesaj\u0131n\u0131 al\u0131nt\u0131lamal\u0131s\u0131n.", - "pmpermitBlock": "Sen benim sahibimin PM'ini spaml\u0131yorsun, bu benim ho\u015fuma gitmiyor.\n\u015eu an ENGELLENDIN, ileride de\u011fi\u015fiklik olmad\u0131\u011f\u0131 s\u00fcrece...", + "pmpermitBlock": "Sen benim sahibimin PM'ini spaml\u0131yorsun, bu benim ho\u015fuma gitmiyor.\n\u015eu an ENGELLENDIN, ileride de\u011fi\u015fiklik olmad\u0131\u011f\u0131 s\u00fcrece\u2026", "pmpermitInfo": ".approve\nKullan\u0131m: Yan\u0131t verilen kullan\u0131c\u0131ya PM atma izni verilir.\n\n.disapprove\nKullan\u0131m: Yan\u0131t verilen kullan\u0131c\u0131n\u0131n PM onay\u0131n\u0131 kald\u0131r\u0131r.\n\n.notifoff\nKullan\u0131m: Onaylanmam\u0131\u015f \u00f6zel mesajlar\u0131n bildirimlerini temizler ya da devre d\u0131\u015f\u0131 b\u0131rak\u0131r.\n\n.notifon\nKullan\u0131m: Onaylanmam\u0131\u015f \u00f6zel mesajlar\u0131n bildirim g\u00f6ndermesine izin verir.", "pmpermitLog": "[%1](tg://user?id=%2) ki\u015fisi sadece bir hayal k\u0131r\u0131kl\u0131\u011f\u0131yd\u0131.\nPM'ni me\u015fgul etti\u011fi i\u00e7in engellendi.", "pmpermitMessage": "%1Hey! Bu bir bot. Endi\u015felenme.\n\nSahibim sana PM atma izni vermedi.\nL\u00fctfen sahibimin aktif olmas\u0131n\u0131 bekleyin, o genellikle PM'leri onaylar.\n\nBildi\u011fim kadar\u0131yla o kafay\u0131 yemi\u015f insanlara PM izni vermiyor.%1", @@ -379,13 +379,13 @@ "ppSmall": "G\u00f6r\u00fcnt\u00fc \u00e7ok k\u00fc\u00e7\u00fck!", "privacySettings": "Gizlilik ayarlar\u0131 bunu yapmama engel oldu.", "privateUsage": "Bu komut sadece \u00f6zelde kullan\u0131labilir.", - "processing": "\u0130\u015fleniyor...", + "processing": "\u0130\u015fleniyor\u2026", "profileInfo": ".username <yeni kullan\u0131c\u0131 ad\u0131>\nKullan\u0131m: Telegram'daki kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 de\u011fi\u015fir.\n\n.name <isim> veya .name <isim> <soyisim>\nKullan\u0131m: Telegram'daki isminizi de\u011fi\u015fir. (Ad ve soyad ilk bo\u015flu\u011fa dayanarak birle\u015ftirilir.)\n\n.setbio <yeni biyografi>\nKullan\u0131m: Telegram'daki biyografinizi bu komutu kullanarak de\u011fi\u015ftirir.\n\n.reserved\nKullan\u0131m: Rezerve etti\u011finiz kullan\u0131c\u0131 adlar\u0131n\u0131 g\u00f6sterir.\n\n.block\nKullan\u0131m: Bir kullan\u0131c\u0131y\u0131 engeller.\n\n.unblock\nKullan\u0131m: Engellenmi\u015f kullan\u0131c\u0131n\u0131n engelini kald\u0131r\u0131r.\n\n.online <disable>\nKullan\u0131m: Hesab\u0131n\u0131z\u0131, siz Telegram'da \u00e7evrimi\u00e7i olmasan\u0131z bile \u00e7evrimi\u00e7i g\u00f6sterir.\nA\u00e7mak i\u00e7in .online, kapatmak i\u00e7in .online disable yaz\u0131n.\n\nNOT: Etkili olmas\u0131 i\u00e7in son g\u00f6r\u00fclmenizi herkese a\u00e7\u0131k yap\u0131n.", "promoteLog": "#PROMOTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", - "promoteProcess": "Yetkilendiriliyor...", + "promoteProcess": "Yetkilendiriliyor\u2026", "promoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici!%4", - "providedProxy": "Proxy ba\u011flant\u0131s\u0131 sa\u011flan\u0131yor ...", - "purgeError": "%1Bir sorun olu\u015ftu...%1\n\n%2%3%2", + "providedProxy": "Proxy ba\u011flant\u0131s\u0131 sa\u011flan\u0131yor\u2026", + "purgeError": "%1Bir sorun olu\u015ftu\u2026%1\n\n%2%3%2", "purgeInfo": ".purge\nKullan\u0131m: Hedeflenen yan\u0131ttan ba\u015flayarak t\u00fcm mesajlar\u0131 temizler.", "purgeLog": "%1Hedeflenen%1 %2%3%2 %1mesaj ba\u015far\u0131yla silindi.%1", "purgeResult": "%1Temizlik tamamland\u0131!%1\n%2%3%2 %1tane mesaj silindi.%1", @@ -398,33 +398,33 @@ "randomUsage": "2 veya daha fazla se\u00e7enek gerekli.", "rbgApiMissing": "%1[%2](https://www.remove.bg/api)%1 %3API key eksik! L\u00fctfen ekleyin.%3", "rbgInfo": ".rbg herhangi bir foto\u011fraf\u0131 yan\u0131tlay\u0131n (Uyar\u0131: \u00e7\u0131kartmalar \u00fczerinde \u00e7al\u0131\u015fmaz.)\nKullan\u0131m: Remove.bg API kullanarak g\u00f6r\u00fcnt\u00fclerin arka plan\u0131n\u0131 kald\u0131r\u0131r.", - "rbgLog": "hata.log", + "rbgLog": "hata.txt", "rbgProcessing": "Bu g\u00f6r\u00fcnt\u00fcden arka plan kald\u0131r\u0131l\u0131yor..", "rbgResult": "Remove.bg kullan\u0131larak arka plan kald\u0131r\u0131ld\u0131", - "rbgUsage": "Bir g\u00f6r\u00fcnt\u00fcye yan\u0131t verin...", + "rbgUsage": "Bir g\u00f6r\u00fcnt\u00fcye yan\u0131t verin\u2026", "removeFirstLine": "L\u00fctfen ilk belirtilen sat\u0131r\u0131 config.env dosyas\u0131ndan kald\u0131r\u0131n", "replyMessage": "Bir mesaja yan\u0131t verin.", "replySticker": "L\u00fctfen bir \u00e7\u0131kartmay\u0131 al\u0131nt\u0131lay\u0131n.", - "restart": "Yeniden ba\u015flat\u0131l\u0131yor ...", + "restart": "Yeniden ba\u015flat\u0131l\u0131yor\u2026", "restartLog": "#RESTART\nBot yeniden ba\u015flat\u0131ld\u0131.", "reverseError": "Desteklenmeyen t\u00fcr", "reverseError2": "\u00c7irkin k\u0131\u00e7\u0131n i\u00e7in bir \u015fey bulamad\u0131m.", "reverseGoogle": "Google siktirip gitmemi s\u00f6yledi.", "reverseInfo": ".reverse\nKullan\u0131m: Foto\u011fraf veya \u00e7\u0131kartmaya yan\u0131t vererek g\u00f6r\u00fcnt\u00fcy\u00fc Google \u00fczerinden arayabilirsiniz", "reverseProcess": "G\u00f6r\u00fcnt\u00fc ba\u015far\u0131yla Google'a y\u00fcklendi. \u015eimdi kaynak ayr\u0131\u015ft\u0131r\u0131l\u0131yor.", - "reverseResult": "[%1](%2)\n%3\n\nResim ar\u0131yorum...\n%3", + "reverseResult": "[%1](%2)\n%3\n\nResim ar\u0131yorum\u2026\n%3", "reverseResult2": "[%1](%2)\n\n[Benzer g\u00f6r\u00fcnt\u00fcler](%3)", "reverseUsage": "L\u00fctfen bir foto\u011frafa veya \u00e7\u0131kartmaya yan\u0131t verin.", "rgbInfo": ".rgb\nKullan\u0131m: Metninizi RGB \u00e7\u0131kartmaya d\u00f6n\u00fc\u015ft\u00fcr\u00fcn.", - "rgbProcessing": "Resme d\u00f6n\u00fc\u015ft\u00fcr\u00fcl\u00fcyor...", + "rgbProcessing": "Resme d\u00f6n\u00fc\u015ft\u00fcr\u00fcl\u00fcyor\u2026", "runningBot": "Botun \u00e7al\u0131\u015f\u0131yor! Herhangi bir sohbete .alive yazarak test edebilirsin, .seden yazarak mod\u00fcllerin listesini alabilirsin. Yard\u0131ma ihtiyac\u0131n varsa, destek grubumuza bakabilirsin https://t.me/%1", "runstr1": "Hey! Nereye gidiyorsun?", - "runstr10": "Bunu yapt\u0131\u011f\u0131na pi\u015fman olacaks\u0131n...", + "runstr10": "Bunu yapt\u0131\u011f\u0131na pi\u015fman olacaks\u0131n\u2026", "runstr11": "/kickme tu\u015funuda deneyebilirsin, E\u011flenceli oldu\u011funu s\u00f6yl\u00fcyorlar.", "runstr12": "Git ba\u015fka birini rahats\u0131z et, burda kimse takm\u0131yor.", "runstr13": "Ka\u00e7abilirsin ama saklanamazs\u0131n.", "runstr14": "Yapabildiklerin bunlar m\u0131?", - "runstr15": "Arkanday\u0131m...", + "runstr15": "Arkanday\u0131m\u2026\u2026\u2026", "runstr16": "Misafirlerin var!", "runstr17": "Bunu kolay yoldan yapabiliriz, yada zor yoldan.", "runstr18": "Anlam\u0131yorsun, de\u011fil mi?", @@ -439,8 +439,8 @@ "runstr26": "\"Hey, bana bak\u0131n! Bottan ka\u00e7abiliyorum \u00e7ok haval\u0131y\u0131m!\" - bu ki\u015fi", "runstr27": "Evet evet, /kickme tu\u015funa \u015fimdiden bas.", "runstr28": "\u0130\u015fte, bu y\u00fcz\u00fc\u011f\u00fc al\u0131n ve Mordor'a gidin.", - "runstr29": "Efsaneye g\u00f6re onlar hala \u00e7al\u0131\u015f\u0131yor...", - "runstr3": "ZZzzZZzz... Noldu? oh, yine onlarm\u0131\u015f, bo\u015fver.", + "runstr29": "Efsaneye g\u00f6re onlar hala \u00e7al\u0131\u015f\u0131yor\u2026", + "runstr3": "ZZzzZZzz\u2026 Noldu? oh, yine onlarm\u0131\u015f, bo\u015fver.", "runstr30": "Harry Potter'\u0131n aksine, ebeveynlerin seni benden koruyamaz.", "runstr31": "Korku \u00f6fkeye, \u00f6fke nefrete, nefret ac\u0131ya yol a\u00e7ar. Korku i\u00e7inde ka\u00e7maya devam edersen, bir sonraki Vader sen olabilirsin.", "runstr32": "Birden fazla hesaplama yap\u0131ld\u0131ktan sonra, dalaverelerine olan ilgimin tam olarak 0 oldu\u011funa karar verdim.", @@ -454,13 +454,13 @@ "runstr4": "Geri gel!", "runstr40": "Ah, ne b\u00fcy\u00fck kay\u0131p. Bu seferkini sevmi\u015ftim.", "runstr41": "A\u00e7\u0131kcas\u0131 can\u0131m, umrumda de\u011fil.", - "runstr42": "S\u00fct\u00fcm t\u00fcm erkekleri avluya \u00e7ekiyor... Daha h\u0131zl\u0131 ko\u015f!", + "runstr42": "S\u00fct\u00fcm t\u00fcm erkekleri avluya \u00e7ekiyor\u2026 Daha h\u0131zl\u0131 ko\u015f!", "runstr43": "Ger\u00e7e\u011fi KALDIRAMAZSIN!", "runstr44": "Uzun zaman \u00f6nce, \u00e7ok \u00e7ok uzaktaki bir galakside birileri takabilirdi. Ama art\u0131k de\u011fil.", - "runstr45": "Hey, onlara bak! Ka\u00e7\u0131n\u0131lmaz banhammer'dan ka\u00e7\u0131yorlar... Ne kadarda tatl\u0131.", + "runstr45": "Hey, onlara bak! Ka\u00e7\u0131n\u0131lmaz banhammer'dan ka\u00e7\u0131yorlar\u2026 Ne kadarda tatl\u0131.", "runstr46": "Han \u00f6nce vuruldu. Ben de \u00f6yle yapaca\u011f\u0131m", "runstr47": "Beyaz tav\u015fan\u0131n, arkas\u0131nda ne yap\u0131yorsun?", - "runstr48": "Doktorunda s\u00f6yleyece\u011fi gibi... KA\u00c7!", + "runstr48": "Doktorunda s\u00f6yleyece\u011fi gibi\u2026 KA\u00c7!", "runstr5": "Ka\u00e7\u0131n OneBot geliyor !!", "runstr6": "Duvara dikkat et!", "runstr7": "Beni onlarla sak\u0131n yaln\u0131z b\u0131rakma!!", @@ -482,7 +482,7 @@ "sedenAlive": "Merhaba Seden! Seni Seviyorum \u2764\ufe0f", "sedenErrorResult": "Sorgu eksik veya hatal\u0131", "sedenErrorText": "%1HATA:%1\n\n%2%3%2\n\n%1Detaylar i\u00e7in dosyaya bak\u0131n\u0131z.%1", - "sedenErrorText2": "========== UYARI ==========\nBu dosya sadece burada y\u00fcklendi,\nsadece hata ve tarih k\u0131sm\u0131n\u0131 kaydettik,\ngizlili\u011finize sayg\u0131 duyuyoruz,\nburada herhangi bir gizli veri varsa\nbu hata raporu olmayabilir, kimse verilerinize ula\u015famaz.\n================================\n\n--------SEDENBOT HATA GUNLUGU--------\n\nTarih: %1\nGrup ID: %2\nG\u00f6nderen ki\u015finin ID: %3\nSeden s\u00fcr\u00fcm\u00fc: %4\n\nOlay Tetikleyici:\n%5\n\nGeri izleme bilgisi::\n%6\n\nHata metni:\n%7\n\n--------SEDENBOT HATA GUNLUGU BITIS--------\n\n\nSon 10 commit:\n", + "sedenErrorText2": "-------- DETAYLAR --------\n\nTarih: %1\nGrup ID: %2\nG\u00f6nderen ki\u015finin ID: %3\nSeden s\u00fcr\u00fcm\u00fc: %4\n\nHata Tetikleyici:\n%5\n\nGeri izleme bilgisi:\n%6\n\nHata metni:\n%7\n\n----------------------\n\n\nSon 10 commit:\n", "sedenGitNotFound": "Bu arada Seden seni \u00e7ok seviyor \u2764\ufe0f", "sedenNearestDC": "%1\u00dclke:%1 %2%3%2\n%1En Yak\u0131n Veri Merkezi:%1 %2%4%2\n%1\u015eu Anki Veri Merkezi:%1 %2%5%2", "sedenQuery": "%1Sorgu:%1\n%2%3%2\n%1Sonu\u00e7:%1\n%2%4%2\n", @@ -493,7 +493,7 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", - "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz ...", + "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", "snipError": "Mesaj y\u00f6nlendirilemedi ve k\u00fcresel not eklenemedi.", @@ -521,8 +521,8 @@ "speedtestResultDoc": "%1SpeedTest%1 %2 saniyede tamamland\u0131!", "speedtestResultText": "%1SpeedTest%1 %2 saniyede tamamland\u0131!\n\u0130ndirme: %3\nY\u00fckleme: %4\nGecikme: %5\nISS: %6\nISS puan\u0131: %7\n%8", "ssInfo": ".ss <link>\nKullan\u0131m: Belirtilen web sitesinden bir ekran g\u00f6r\u00fcnt\u00fcs\u00fc al\u0131r ve g\u00f6nderir.\nGe\u00e7erli bir site ba\u011flant\u0131s\u0131 \u00f6rne\u011fi: https://devotag.com", - "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor...\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", - "ssUpload": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc kar\u015f\u0131ya y\u00fckleniyor ...", + "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor\u2026\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", + "ssUpload": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc kar\u015f\u0131ya y\u00fckleniyor\u2026", "ssUsage": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc alabilmem i\u00e7in ge\u00e7erli bir ba\u011flant\u0131 vermelisin.", "statsResult": "%1Toplam Sohbet:%1 %2%3%2\n%1Kanallar:%1 %2%4%2\n%1Gruplar:%1 %2%5%2\n%1S\u00fcper Gruplar:%1 %2%6%2\n%1Botlar:%1 %2%7%2\n%1\u00d6zel Mesajlar:%1 %2%8%2\n%1Okunmam\u0131\u015f Mesajlar:%1 %2%9%2", "statusLong": "Uzun zaman \u00f6nce", @@ -539,13 +539,13 @@ "sudoCheck": "Bu ki\u015fi Seden yetkilisi!", "supportResult": "[Buradan](http://t.me/%1) destek grubumuza ula\u015fabilirsiniz.", "syntaxError": "S\u00f6zdizimi hatas\u0131.", - "systemInfo": ".alive\nKullan\u0131m: Seden botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin...", + "systemInfo": ".alive\nKullan\u0131m: Botunun \u00e7al\u0131\u015f\u0131p \u00e7al\u0131\u015fmad\u0131\u011f\u0131n\u0131 kontrol etmek i\u00e7in kullan\u0131l\u0131r.\n\n.ping\nKullan\u0131m: Botun ping de\u011ferini g\u00f6sterir.\n\n.echo\nKullan\u0131m: Yazd\u0131\u011f\u0131n\u0131z metni tekrar eder.\n\n.botver\nKullan\u0131m: Seden UserBot s\u00fcr\u00fcm\u00fcn\u00fc g\u00f6sterir.\n\n.dc\nKullan\u0131m: Sunucunuza en yak\u0131n veri merkezini g\u00f6sterir.\n\n.neofetch\nKullan\u0131m: Neofetch komutunu kullanarak sistem bilgisi g\u00f6sterir.\n\n.eval 2 + 3\nKullan\u0131m: Mini ifadeleri de\u011ferlendirin.\n\n.term echo Merhaba Seden!\nKullan\u0131m: Sunucunuzda bash komutlar\u0131n\u0131 ve komut dosyalar\u0131n\u0131 \u00e7al\u0131\u015ft\u0131r\u0131n.\n\n.restart\nKullan\u0131m: Botu yeniden ba\u015flat\u0131r.\n\n.kapat\nKullan\u0131m: Bazen can\u0131n botunu kapatmak ister. Ger\u00e7ekten o nostaljik Windows XP kapan\u0131\u015f sesini duyabilece\u011fini zannedersin\u2026", "termHelp": "Yard\u0131m almak i\u00e7in .seden system yazarak \u00f6rne\u011fe bakabilirsin.", "termLog": "Terminal komutu %1 ba\u015far\u0131yla \u00e7al\u0131\u015ft\u0131r\u0131ld\u0131", "termNoResult": "Komutun sonucu al\u0131namad\u0131.", "termUsage": "L\u00fctfen bir komut yaz\u0131n.", "testException": "Bu bir test, hata kayd\u0131 g\u00f6ndermeyin.", - "testLogId": "LOG_ID de\u011feri test ediliyor ...", + "testLogId": "LOG_ID de\u011feri test ediliyor\u2026", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", @@ -563,40 +563,39 @@ "udInfo": ".ud <terim>\nKullan\u0131m: Urban Dictionary aramas\u0131 yapman\u0131n kolay yolu.", "udNoResult": "%2 %1i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131%1", "udResult": "\u00dczg\u00fcn\u00fcm, %1%2%1 i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131.", - "unbanProcess": "Yasak kald\u0131r\u0131l\u0131yor...", + "unbanProcess": "Yasak kald\u0131r\u0131l\u0131yor\u2026", "unbanResult": "%1[%2](tg://user?id=%3)%1 %4i\u00e7in yasaklama ba\u015far\u0131yla kald\u0131r\u0131ld\u0131!%4", "unblockChat": "%2L\u00fctfen%2 %1@%3%1 %2engelini kald\u0131r\u0131n ve tekrar deneyin%2", - "unmuteProcess": "Sessizden \u00e7\u0131kar\u0131l\u0131yor...", + "unmuteProcess": "Sessizden \u00e7\u0131kar\u0131l\u0131yor\u2026", "unmuteResult": "%1[%2](tg://user?id=%3)%1 %4tekrardan konu\u015fabilir!%4", - "updateBotUpdating": "SedenBot G\u00fcncelleniyor...\nBu i\u015flem 1-2 dakika s\u00fcrebilir, l\u00fctfen sab\u0131rla bekle. Beklemene de\u011fer :)", - "updateCheck": "SedenBot i\u00e7in g\u00fcncellemeler denetleniyor...", - "updateComplete": "G\u00fcncelleme ba\u015far\u0131yla tamamland\u0131!\nSedenBot yeniden ba\u015flat\u0131l\u0131yor, sab\u0131rla bekledi\u011fin i\u00e7in te\u015fekk\u00fcr ederiz :)", - "updateCustomBranch": "%1[SedenBot G\u00fcncelleyici]:%1` Galiba botunun branch ismini de\u011fi\u015ftirdin. Kulland\u0131\u011f\u0131n branch ismi: %2. B\u00f6yle olursa botunu g\u00fcncelleyemem. \u00c7\u00fcnk\u00fc branch ismi uyu\u015fmuyor..\nL\u00fctfen botunu SedenBot resmi repodan kullan.", + "updateBotUpdating": "Bot g\u00fcncelleniyor\u2026", + "updateCheck": "G\u00fcncellemeler denetleniyor\u2026", + "updateComplete": "G\u00fcncelleme tamamland\u0131!\nYeniden ba\u015flat\u0131l\u0131yor.", "updateFailed": "G\u00fcncelleme ba\u015far\u0131s\u0131z oldu! Baz\u0131 sorunlarla kar\u015f\u0131la\u015ft\u0131k.", "updateFolderError": "%1\n%2%3 klas\u00f6r\u00fc bulunamad\u0131.%2", - "updateForceSync": "G\u00fcncel SedenBot kodu zorla e\u015fitleniyor...", + "updateForceSync": "G\u00fcncel depo kodu zorla e\u015fitleniyor\u2026", "updateGitError": "%1\n%2Git hatas\u0131! %3%2", - "updateGitNotFound": "%1 klas\u00f6r\u00fc bir git reposu gibi g\u00f6r\u00fcnm\u00fcyor.\nFakat bu sorunu .update now komutuyla botu zorla g\u00fcncelleyerek \u00e7\u00f6zebilirsin.", - "updateHerokuAppName": "SedenBot G\u00fcncelleyiciyi kullanabilmek i\u00e7in HEROKU_APPNAME de\u011fi\u015fkenini tan\u0131mlamal\u0131s\u0131n. Aksi halde g\u00fcncelleyici \u00e7al\u0131\u015fmaz.", + "updateGitNotFound": "%1 klas\u00f6r\u00fc bir git deposu gibi g\u00f6r\u00fcnm\u00fcyor.\nFakat bu sorunu .update now komutuyla botu zorla g\u00fcncelleyerek \u00e7\u00f6zebilirsin.", + "updateHerokuAppName": "G\u00fcncelleme yapabilmek i\u00e7in HEROKU_APPNAME de\u011fi\u015fkenini tan\u0131mlamal\u0131s\u0131n.", "updateHerokuVariables": "%1\nHeroku de\u011fi\u015fkenleri yanl\u0131\u015f veya eksik tan\u0131mlanm\u0131\u015f.", "updateInfo": ".update\nKullan\u0131m: Botunuza siz kurduktan sonra herhangi bir g\u00fcncelleme gelip gelmedi\u011fini kontrol eder.\n\n.update now\nKullan\u0131m: Botunuzu g\u00fcnceller.", - "updateLocalComplate": "G\u00fcncelleme ba\u015far\u0131yla tamamland\u0131!\nSedenBot yeniden ba\u015flat\u0131l\u0131yor.", + "updateLocalComplate": "G\u00fcncelleme tamamland\u0131!\nYeniden ba\u015flat\u0131l\u0131yor.", "updateLog": "LOG:", "updateNow": "%1G\u00fcncellemeyi yapmak i\u00e7in%1 %2.update now%2 %1komutunu kullan.%1", "updateOutput": "De\u011fi\u015fiklik listesi \u00e7ok b\u00fcy\u00fck, dosya olarak g\u00f6r\u00fcnt\u00fclemelisin.", - "updateSedenBot": "Bot g\u00fcncelle\u015ftiriliyor, l\u00fctfen bekle....", + "updateSedenBot": "Bot g\u00fcncelle\u015ftiriliyor, l\u00fctfen bekle\u2026", "updated": "g\u00fcncellendi", "updaterGitError": "%2\n%1Hata kayd\u0131:%1\n%3", "updaterHasUpdate": "%1%3 i\u00e7in g\u00fcncelleme mevcut!\n\nDe\u011fi\u015fiklik listesi:%1\n%4", "updaterUsingLatest": "%2Botunuz%2 %1g\u00fcncel%1 %2Dal:%2 %1%3%1", - "updownDownload": "%1\u0130ndiriliyor ... %2%1", + "updownDownload": "%1\u0130ndiriliyor\u2026 %2%1", "updownDownloadSuccess": "Ba\u015far\u0131yla %1%2%1 konumuna indirildi", - "updownUpload": "%1Y\u00fckleniyor ... %2%1\n%3", + "updownUpload": "%1Y\u00fckleniyor\u2026 %2%1\n%3", "uploadError": "Dosya y\u00fcklenemedi.", "uploadFileError": "Dosya bulunamad\u0131.", "uploadFinish": "Y\u00fckleme tamamland\u0131!", "uploadInfo": ".download <bir \u015feye cevap vererek>\nKullan\u0131m: Sunucuya dosyay\u0131 indirir.\n\n.upload <sunucudaki dosya yolu>\nKullan\u0131m: Sunucunuzdaki bir dosyay\u0131 sohbete upload eder.", - "uploadMedia": "Medya y\u00fckleniyor...", + "uploadMedia": "Medya y\u00fckleniyor\u2026", "uploadReply": "Buraya hi\u00e7li\u011fi y\u00fckleyemem.", "urlError": "Hata: link \u00e7\u0131kar\u0131lam\u0131yor", "useridResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2\n%1Kullan\u0131c\u0131 ID:%1 %3%4%3", @@ -622,11 +621,11 @@ "youtubedlInfo": ".youtubedl <mp3 ya da mp4> <link>\nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334", "zombiesError": "Yeterli yetkim bulunmamakta.", - "zombiesFind": "Silinen hesaplar bulunuyor...", + "zombiesFind": "Silinen hesaplar bulunuyor\u2026", "zombiesFound": "%1Bu grupta%1 %2%3%2 %1adet silinen hesap bulundu.\nTemizlemek i\u00e7in%1 %2.zombies clean%2 %1komutunu kullan.%1", "zombiesLog": "#TEMIZLIK\n%2%3%2 %1tane silinen hesap at\u0131ld\u0131.\nGRUP: %4%1 | %2%5%2", "zombiesNoAccount": "Silinen hesap(lar) bulunamad\u0131!", - "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor...", + "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor\u2026", "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" } \ No newline at end of file From 12442825cbab5ab8879d3bdc402d40158e1c5357 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Sun, 22 Aug 2021 20:42:46 +0300 Subject: [PATCH 076/242] seden: Update app.json Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index c34dd8a..c7e9f9d 100644 --- a/app.json +++ b/app.json @@ -12,7 +12,7 @@ "url": "https://github.com/heroku/heroku-buildpack-chromedriver" } ], - "description": "A modular Telegram userbot running on Python 3.8 with an sqlalchemy database,", + "description": "A modular Telegram userbot running on Python 3.9 with an sqlalchemy database,", "env": { "ALIVE_MSG": { "description": "Custom message for .alive command. We have our own alive message by default.", @@ -97,7 +97,7 @@ "pyrogram", "postgresql" ], - "logo": "https://i.resimyukle.xyz/7eaU8d.png", + "logo": "https://i.imgur.com/GL1yJpN.png", "name": "Telegram SedenBot", "repository": "https://github.com/TeamDerUntergang/Telegram-SedenUserBot", "stack": "container", From 03b9d6ff0a38132188a1e1897511e67f1032d2ab Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Fri, 3 Sep 2021 07:15:10 +0300 Subject: [PATCH 077/242] seden: [EXP] Add Railway deploy Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- Dockerfile | 3 +++ README.md | 3 ++- app.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 097f8cb..58c182a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,3 +6,6 @@ WORKDIR /DerUntergang/ # Clone Repo RUN git clone -b seden https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git /DerUntergang/ + +# Run bot +CMD ["python3", "seden.py"] \ No newline at end of file diff --git a/README.md b/README.md index 1c5acc8..dbb96b4 100755 --- a/README.md +++ b/README.md @@ -39,8 +39,9 @@ python3 seden.py ### Nix/NixOS Just type `nix-shell` command in bot folder. -## Heroku Deploy +## Heroku / Railway Deploy [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TeamDerUntergang/Telegram-SedenUserBot/tree/seden) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FTeamDerUntergang%2FTelegram-SedenUserBot&plugins=postgresql&envs=API_ID%2CAPI_HASH%2CSESSION%2CALIVE_MSG%2CBOT_PREFIX%2CCHROME_DRIVER%2CLOG_ID%2CLOG_VERBOSE%2CPM_AUTO_BAN%2CPM_MSG_COUNT%2CPM_UNAPPROVED%2CSEDEN_LANG&optionalEnvs=ALIVE_MSG%2CBOT_PREFIX%2CCHROME_DRIVER%2CLOG_ID%2CLOG_VERBOSE%2CPM_AUTO_BAN%2CPM_MSG_COUNT%2CPM_UNAPPROVED%2CSEDEN_LANG) If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). diff --git a/app.json b/app.json index c7e9f9d..cd99203 100644 --- a/app.json +++ b/app.json @@ -28,7 +28,7 @@ "required": true }, "BOT_PREFIX": { - "description": "It changes your bot pattern (default pattern is '.' (dot).", + "description": "It changes your bot pattern (default pattern is '.' (dot)).", "required": false }, "CHROME_DRIVER": { From f1eff2344d33b94c7dd70df07e8ef75a108d34c7 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@devotag.com> Date: Tue, 7 Sep 2021 17:13:32 +0300 Subject: [PATCH 078/242] seden: Add warning text to Heroku API Key Signed-off-by: NaytSeyd <naytseyd@devotag.com> --- sedenbot/modules/updater.py | 9 ++++++--- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index dfb556a..56563f2 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -119,9 +119,12 @@ def upstream(ups): edit(ups, f'`{get_translation("updateSedenBot")}`') if HEROKU_KEY: - heroku = from_key(HEROKU_KEY) - heroku_app = None - heroku_applications = heroku.apps() + try: + heroku = from_key(HEROKU_KEY) + heroku_app = None + heroku_applications = heroku.apps() + except BaseException: + return edit(ups, f'`{get_translation("updateHerokuApiKey")}`') if not HEROKU_APPNAME: edit(ups, f'`{get_translation("updateHerokuAppName")}`') repo.__del__() diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index b9241a3..125b2f9 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -576,6 +576,7 @@ "updateForceSync": "Force syncing to latest repo, please wait\u2026", "updateGitError": "%1\n%2Git error! %3%2", "updateGitNotFound": "%1 folder doesn't look like a git repo.\nHowever, you can solve this problem by force updating the bot with the .update now command.", + "updateHerokuApiKey": "I can't do this because Heroku API key is wrong. Please change the API key by editing HEROKU_KEY variable", "updateHerokuAppName": "Please set up the HEROKU_APPNAME variable to be able to update bot.", "updateHerokuVariables": "%1\nHeroku variables are incorrectly or under defined.", "updateInfo": ".update\nUsage: Checks if seden userbot repository has any updates and shows a changelog if so.\n\n.update now\nUsage: Updates your userbot, if there are any updates in seden userbot repository.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index fa3ecc6..95dee66 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -576,6 +576,7 @@ "updateForceSync": "G\u00fcncel depo kodu zorla e\u015fitleniyor\u2026", "updateGitError": "%1\n%2Git hatas\u0131! %3%2", "updateGitNotFound": "%1 klas\u00f6r\u00fc bir git deposu gibi g\u00f6r\u00fcnm\u00fcyor.\nFakat bu sorunu .update now komutuyla botu zorla g\u00fcncelleyerek \u00e7\u00f6zebilirsin.", + "updateHerokuApiKey": "Heroku API anahtar\u0131 hatal\u0131 oldu\u011fundan bu i\u015flemi ger\u00e7ekle\u015ftiremiyorum. L\u00fctfen HEROKU_KEY de\u011fi\u015fkenini d\u00fczenleyerek API anahtar\u0131n\u0131 de\u011fi\u015ftirin", "updateHerokuAppName": "G\u00fcncelleme yapabilmek i\u00e7in HEROKU_APPNAME de\u011fi\u015fkenini tan\u0131mlamal\u0131s\u0131n.", "updateHerokuVariables": "%1\nHeroku de\u011fi\u015fkenleri yanl\u0131\u015f veya eksik tan\u0131mlanm\u0131\u015f.", "updateInfo": ".update\nKullan\u0131m: Botunuza siz kurduktan sonra herhangi bir g\u00fcncelleme gelip gelmedi\u011fini kontrol eder.\n\n.update now\nKullan\u0131m: Botunuzu g\u00fcnceller.", From 6f5e4f6581d1dcc5006f163a2bbcdd8f1529a203 Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Sat, 25 Sep 2021 23:17:08 +0300 Subject: [PATCH 079/242] Some bug fixes and improvements - Proxy fixed - covid19 module fixed - DEEPGRAM variable removed - Unneeded args removed from PyroClient - App version and device model changed --- sedenbot/__init__.py | 17 ++--------------- sedenbot/modules/covid19.py | 2 +- sedenecem/core/proxy.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 9cb0e28..e3a1710 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -180,17 +180,6 @@ def set_logger(): LOG_ID = int(_LOG_ID) if _LOG_ID and resr(r'^-?\d+$', _LOG_ID) else None del _LOG_ID -# Connect to the test server -# -# You'll have a separate account, -# but you won't be able to access contacts -# or messages on the regular server -# -# Also known as Deep Telegram -# -# For more information: https://docs.pyrogram.org/topics/test-servers -DEEPGRAM = sb(environ.get('DEEPGRAM', 'False')) - # PmPermit PM Auto Ban Stuffs PM_AUTO_BAN = sb(environ.get('PM_AUTO_BAN', 'False')) _PM_MSG_COUNT = environ.get('PM_MSG_COUNT', 'default') @@ -275,11 +264,9 @@ def export_session_string(self): SESSION, api_id=API_ID, api_hash=API_HASH, - app_version='Seden UserBot', - device_model='DerUntergang', + app_version='SedenEcem', + device_model='Firefox 91.0.2', system_version=f'v{BOT_VERSION}', - lang_code='tr', - test_mode=DEEPGRAM, ) diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py index 2115499..8f1af2b 100644 --- a/sedenbot/modules/covid19.py +++ b/sedenbot/modules/covid19.py @@ -39,7 +39,7 @@ def covid(message): if 'var sondurumjson' in turejq: result = loads( sub( - '(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s)', + '(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s|var haftalikdurumjson(.*))', '', turejq, ) diff --git a/sedenecem/core/proxy.py b/sedenecem/core/proxy.py index 5a740a5..1260af9 100644 --- a/sedenecem/core/proxy.py +++ b/sedenecem/core/proxy.py @@ -1,3 +1,12 @@ +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + from bs4 import BeautifulSoup from requests import get from sedenbot import TEMP_SETTINGS, get_translation @@ -37,7 +46,7 @@ def _xget_random_proxy(): req = get('https://sslproxies.org/', headers=head) soup = BeautifulSoup(req.text, 'html.parser') - res = soup.find('table', {'id': 'proxylisttable'}).find('tbody') + res = soup.find('div', {'class': 'fpl-list'}).find('tbody') res = res.findAll('tr') for item in res: infos = item.findAll('td') From 02c5d90b0e0ba58d21f8d800ca6c2f541be56431 Mon Sep 17 00:00:00 2001 From: Orkun <orkuncoskun9@gmail.com> Date: Tue, 28 Sep 2021 19:09:05 +0300 Subject: [PATCH 080/242] Change session variable (#9) bot's variables and the session's variables must be the same. --- session.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/session.py b/session.py index 8de29ac..1d1217b 100644 --- a/session.py +++ b/session.py @@ -32,8 +32,8 @@ 'sedenuserbot', api_id=API_ID, api_hash=API_HASH, - app_version='Seden UserBot', - device_model='DerUntergang', + app_version='SedenEcem', + device_model='Firefox 91.0.2', system_version='Session', lang_code='en', ) @@ -76,8 +76,8 @@ 'sedenuserbot', api_id=API_ID, api_hash=API_HASH, - app_version='Seden UserBot', - device_model='DerUntergang', + app_version='SedenEcem', + device_model='Firefox 91.0.2', system_version='Session', lang_code='tr', ) From 4c452bbfb2241ad02cc036ab7dff37c1a0df9a69 Mon Sep 17 00:00:00 2001 From: oguzbakir <b21627007@cs.hacettepe.edu.tr> Date: Sat, 2 Oct 2021 21:27:16 +0000 Subject: [PATCH 081/242] seden: Add EXIF information extraction --- requirements.txt | 1 + sedenbot/modules/exif.py | 185 +++++++++++++++++++++++++++++++++++ sedenecem/translator/de.json | 4 + sedenecem/translator/en.json | 4 + sedenecem/translator/tr.json | 4 + 5 files changed, 198 insertions(+) create mode 100644 sedenbot/modules/exif.py diff --git a/requirements.txt b/requirements.txt index 1f211ff..44a8365 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ bs4 cowpy deethon emoji +exifread gitpython googletrans==3.1.0a0 gtts diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py new file mode 100644 index 0000000..27c9057 --- /dev/null +++ b/sedenbot/modules/exif.py @@ -0,0 +1,185 @@ +# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +import math +from os import remove, path + +from exifread import process_file +from fractions import Fraction +from PIL import Image +from sedenbot import HELP +from sedenecem.core import ( + download_media_wc, + edit, + get_translation, + sedenify, +) + + +@sedenify(pattern="^.exif") +def exif_data(message): + reply = message.reply_to_message + google_coordinate = ["https://www.google.com/maps?q="] + edit(message, f'`{get_translation("exifProcess")}`') + + if reply: + data = check_media(reply) + + if not data: + edit(message, f'`{get_translation("exifError")}`') + return + + # Download Media + image_file = download_media_wc(reply, "image.jpg") + image = open(image_file, "rb") + remove(image_file) + + # Extract EXIF data + data = process_file(image) + + if not data: + edit(message, f'`{get_translation("exifError")}`') + return + + # Get EXIF tags + tag_keys = list(data.keys()) + tag_keys.sort() + + # Create dictionary for conversion functions + unit_dict = { + "EXIF ApertureValue": calculate_aperture, + "EXIF BrightnessValue": calculate_brightness, + "EXIF FNumber": calculate_fnumber, + "EXIF FocalLength": calculate_focal, + "EXIF ShutterSpeedValue": calculate_shutter, + "GPS GPSAltitude": calculate_altitude, + "GPS GPSLatitude": calculate_gps, + "GPS GPSLatitudeRef": calculate_latitude_ref, + "GPS GPSLongitude": calculate_gps, + "GPS GPSLongitudeRef": calculate_longitude_ref, + "JPEGThumbnail": handle_thumbnail, + } + + # Build EXIF data string + data_str = get_translation("exifLog") + for i in tag_keys: + if i in unit_dict.keys(): + converted = "" + + # GPS Data + if "GPS" == i.split(" ")[0]: + converted = unit_dict[i](data[i].printable, google_coordinate) + + # Thumbnail Image Data in Byte Array + elif type(data[i]) == bytes: + converted = unit_dict[i](data[i]) + + # Others + else: + converted = unit_dict[i](data[i].printable) + + data_str += "{0} : {1}\n".format(i, converted) + else: + data_str += "{0} : {1}\n".format(i, str(data[i])) + if len(google_coordinate) == 5: + google_coordinate.insert(0, "Google Maps Link: ") + data_str += "".join(google_coordinate) + google_coordinate.insert(-1, "\n") + + edit(message, data_str) + + +def calculate_aperture(string): + return "f/%.1f" % ( + math.sqrt(2.0) ** (int(string.split("/")[0]) / int(string.split("/")[1])) + ) + + +def calculate_brightness(string): + return "%.2f EV" % (int(string.split("/")[0]) / int(string.split("/")[1])) + + +def calculate_fnumber(string): + return "f/%.1f" % (int(string.split("/")[0]) / int(string.split("/")[1])) + + +def calculate_focal(string): + return "{0} mm".format( + str(math.floor(int(string.split("/")[0]) / int(string.split("/")[1]))) + ) + + +def calculate_shutter(string): + return "1/{0} sec".format( + str(math.floor(2 ** (int(string.split("/")[0]) / int(string.split("/")[1])))) + ) + + +def calculate_altitude(string, google_coordinate): + ret = string + if "/" in ret: + ret = "%.1f m" % (int(string.split("/")[0]) / int(string.split("/")[1])) + return ret + + +def calculate_gps(coord, google_coordinate): + coord_list = coord.split(",") + hour = int(coord_list[0].replace("[", "")) + minutes = coord_list[1].strip().split("/") + seconds = coord_list[2].strip().replace("]", "").split("/") + if len(minutes) > 1: + minutes = int(minutes[0]) / int(minutes[1]) + seconds = (minutes % 1) * 60 + else: + minutes = int(minutes[0]) + seconds = int(seconds[0]) / int(seconds[1]) + + google_coordinate.append( + "{0}%C2%B0{1}'{2}%22".format(hour, math.floor(minutes), "%.2f" % seconds) + ) + return "{0}°{1}'{2}\"".format(hour, math.floor(minutes), "%.2f" % seconds) + + +def calculate_latitude_ref(coord, google_coordinate): + google_coordinate.append(coord.strip() + "+") + return coord.strip() + + +def calculate_longitude_ref(coord, google_coordinate): + google_coordinate.append(coord.strip()) + return coord.strip() + + +def handle_thumbnail(img): + return "" + + +def check_media(reply_message): + data = False + + if reply_message and reply_message.media: + if reply_message.photo: + data = True + elif reply_message.sticker and not reply_message.sticker.is_animated: + data = True + elif reply_message.document: + name = reply_message.document.file_name + print(name) + print(name[name.find(".") + 1 :]) + if ( + name + and "." in name + and path.splitext(name)[1] in [".png", ".jpg", ".jpeg", ".webp"] + ): + data = True + + return data + + +HELP.update({"exif": get_translation("exifInfo")}) diff --git a/sedenecem/translator/de.json b/sedenecem/translator/de.json index 3c15646..e4b6f67 100644 --- a/sedenecem/translator/de.json +++ b/sedenecem/translator/de.json @@ -107,6 +107,10 @@ "evalInfo": ".eval 2 + 3\nVerwendung: Mini ausdr\u00fccke auswerten.", "evalLog": "Eval abfrage %1 erfolgreich verarbeitet", "evalUsage": "Geben sie eine aussage zu bewerten.", + "exifError": "EXIF-Daten nicht gefunden.", + "exifLog": "EXIF-Daten für Bild:\n", + "exifProcess": "Extrahieren von EXIF-Informationen.", + "exifInfo": ".exif\nVerwendung: Extrahieren Sie EXIF-Informationen aus der angegebenen Fotodatei.", "ezanvaktiErrorInfo": "F\u00fcr %1 wurden keine informationen gefunden.", "ezanvaktiKonum": "Bitte geben sie eine stadt neben dem befehl an.", "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 125b2f9..b8ff27a 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -183,6 +183,10 @@ "errorLogSend": "Something went wrong. Sending logs to log group\u2026", "evalLog": "Eval query %1 processed successfully", "evalUsage": "Give a statement to evaluate.", + "exifError": "EXIF data not found.", + "exifLog": "EXIF data for image:\n", + "exifProcess": "Extracting EXIF information.", + "exifInfo": ".exif\nUsage: Extract EXIF information from given photo file.", "ezanvaktiErrorInfo": "No information was found for %1.", "ezanvaktiKonum": "Please specify a city next to the command.", "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 95dee66..ea552b0 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -184,6 +184,10 @@ "errorLogSend": "Bir sorun olu\u015ftu, kay\u0131tlar\u0131 log grubuna g\u00f6nderiyorum\u2026", "evalLog": "Eval sorgusu %1 ba\u015far\u0131yla y\u00fcr\u00fct\u00fcld\u00fc", "evalUsage": "De\u011ferlendirmek i\u00e7in bir ifade verin.", + "exifError": "EXIF verisi bulunamad\u0131.", + "exifLog": "EXIF verisi:\n", + "exifProcess": "EXIF verisi \u00e7\u0131kart\u0131l\u0131yor.", + "exifInfo": ".exif\nKullan\u0131m: Verilen foto\u011fraf dosyas\u0131ndan EXIF verisini \u00e7\u0131kart\u0131r.", "ezanvaktiErrorInfo": "%1 i\u00e7in bir bilgi bulunamad\u0131.", "ezanvaktiKonum": "L\u00fctfen komutun yan\u0131na bir \u015fehir belirtin", "ezanvaktiShowInfo": "%1Diyanet Namaz Vakitleri%1\n\n\ud83d\udccd %1Yer:%1 %2%3%2\n\n\ud83c\udfd9 %1\u0130msak:%1 %2%4%2\n\ud83c\udf05 %1G\u00fcne\u015f:%1 %2%5%2\n\ud83c\udf07 %1\u00d6\u011fle:%1 %2%6%2\n\ud83c\udf06 %1\u0130kindi:%1 %2%7%2\n\ud83c\udf03 %1Ak\u015fam:%1 %2%8%2\n\ud83c\udf0c %1Yats\u0131:%1 %2%9%2", From 7061ca4aa03713efc1664260a6dc760550d283b1 Mon Sep 17 00:00:00 2001 From: oguzbakir <b21627007@cs.hacettepe.edu.tr> Date: Sun, 3 Oct 2021 18:35:51 +0000 Subject: [PATCH 082/242] seden: Use f-strings for exif module --- sedenbot/modules/exif.py | 26 ++++++++++++++++---------- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index 27c9057..909cd30 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -88,7 +88,7 @@ def exif_data(message): else: data_str += "{0} : {1}\n".format(i, str(data[i])) if len(google_coordinate) == 5: - google_coordinate.insert(0, "Google Maps Link: ") + google_coordinate.insert(0, get_translation("exifMaps")) data_str += "".join(google_coordinate) google_coordinate.insert(-1, "\n") @@ -96,35 +96,41 @@ def exif_data(message): def calculate_aperture(string): + division = string.split("/") return "f/%.1f" % ( - math.sqrt(2.0) ** (int(string.split("/")[0]) / int(string.split("/")[1])) + math.sqrt(2.0) ** (int(division[0]) / int(division[1])) ) def calculate_brightness(string): - return "%.2f EV" % (int(string.split("/")[0]) / int(string.split("/")[1])) + division = string.split("/") + return "%.2f EV" % (int(division[0]) / int(division[1])) def calculate_fnumber(string): - return "f/%.1f" % (int(string.split("/")[0]) / int(string.split("/")[1])) + division = string.split("/") + return "f/%.1f" % (int(division[0]) / int(division[1])) def calculate_focal(string): + division = string.split("/") return "{0} mm".format( - str(math.floor(int(string.split("/")[0]) / int(string.split("/")[1]))) + str(math.floor(int(division[0]) / int(division[1]))) ) def calculate_shutter(string): + division = string.split("/") return "1/{0} sec".format( - str(math.floor(2 ** (int(string.split("/")[0]) / int(string.split("/")[1])))) + str(math.floor(2 ** (int(division[0]) / int(division[1])))) ) def calculate_altitude(string, google_coordinate): ret = string + division = string.split("/") if "/" in ret: - ret = "%.1f m" % (int(string.split("/")[0]) / int(string.split("/")[1])) + ret = "%.1f m" % (int(division[0]) / int(division[1])) return ret @@ -141,13 +147,13 @@ def calculate_gps(coord, google_coordinate): seconds = int(seconds[0]) / int(seconds[1]) google_coordinate.append( - "{0}%C2%B0{1}'{2}%22".format(hour, math.floor(minutes), "%.2f" % seconds) + f"{hour}%C2%B0{math.floor(minutes)}'{seconds:.2f}%22" ) - return "{0}°{1}'{2}\"".format(hour, math.floor(minutes), "%.2f" % seconds) + return f"{hour}°{math.floor(minutes)}'{seconds:.2f}\"" def calculate_latitude_ref(coord, google_coordinate): - google_coordinate.append(coord.strip() + "+") + google_coordinate.append(f"{coord.strip()}+") return coord.strip() diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index b8ff27a..503a5d5 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -185,6 +185,7 @@ "evalUsage": "Give a statement to evaluate.", "exifError": "EXIF data not found.", "exifLog": "EXIF data for image:\n", + "exifMaps": "Google Maps Link: ", "exifProcess": "Extracting EXIF information.", "exifInfo": ".exif\nUsage: Extract EXIF information from given photo file.", "ezanvaktiErrorInfo": "No information was found for %1.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index ea552b0..b9d5190 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -186,6 +186,7 @@ "evalUsage": "De\u011ferlendirmek i\u00e7in bir ifade verin.", "exifError": "EXIF verisi bulunamad\u0131.", "exifLog": "EXIF verisi:\n", + "exifMaps": "Google Maps Linki: ", "exifProcess": "EXIF verisi \u00e7\u0131kart\u0131l\u0131yor.", "exifInfo": ".exif\nKullan\u0131m: Verilen foto\u011fraf dosyas\u0131ndan EXIF verisini \u00e7\u0131kart\u0131r.", "ezanvaktiErrorInfo": "%1 i\u00e7in bir bilgi bulunamad\u0131.", From 2297bb8c39bb81de0c99f12c736894677df7fe8b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Oct 2021 21:52:06 +0300 Subject: [PATCH 083/242] seden: Use yt-dlp instead of youtube_dl Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- sedenbot/modules/youtubedl.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 44a8365..33d4986 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,4 +27,4 @@ sqlalchemy==1.3.23 tgcrypto urbandict wikipedia -youtube-dl +yt-dlp diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 5fef80b..199443e 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -23,7 +23,7 @@ reply_video, sedenify, ) -from youtube_dl import YoutubeDL +from yt_dlp import YoutubeDL @sedenify(pattern='^.(youtube|yt)dl') @@ -98,6 +98,7 @@ def youtubedl(message): elif util == 'mp3': ydl_opts = { + 'age_limit': 18, 'outtmpl': f'%(title)s.%(ext)s', 'format': 'bestaudio/best', 'addmetadata': True, From 92ebb27cf1644c44b3bcae9d45182f0c4817e94c Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Oct 2021 21:54:36 +0300 Subject: [PATCH 084/242] seden: Nuke Dogbin module Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/dogbin.py | 94 ----------------------------------- sedenbot/modules/youtubedl.py | 1 - sedenecem/translator/en.json | 12 ----- sedenecem/translator/tr.json | 12 ----- 4 files changed, 119 deletions(-) delete mode 100644 sedenbot/modules/dogbin.py diff --git a/sedenbot/modules/dogbin.py b/sedenbot/modules/dogbin.py deleted file mode 100644 index 0f56e2d..0000000 --- a/sedenbot/modules/dogbin.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from requests import exceptions, get, post -from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, sedenify - -DOGBIN_URL = 'https://del.dog/' - - -@sedenify(pattern='^.paste') -def paste(message): - match = extract_args(message) - reply = message.reply_to_message - - if match: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("dogbinUsage")}`') - match = reply.text - else: - edit(message, f'`{get_translation("dogbinUsage")}`') - return - - edit(message, f'`{get_translation("dogbinPasting")}`') - resp = post(f'{DOGBIN_URL}documents', data=match.encode('utf-8')) - - dogbin_final_url = '' - if resp.status_code == 200: - response = resp.json() - key = response['key'] - dogbin_final_url = f'{DOGBIN_URL}{key}' - - if response['isUrl']: - reply_text = get_translation( - 'dogbinPasteResult2', ['`', dogbin_final_url, f'{DOGBIN_URL}v/{key}'] - ) - else: - reply_text = get_translation('dogbinPasteResult', ['`', dogbin_final_url]) - else: - reply_text = f'`{get_translation("dogbinReach")}`' - - edit(message, reply_text, preview=False) - - -@sedenify(outgoing=True, pattern="^.getpaste") -def getpaste(message): - reply = message.reply_to_message - match = extract_args(message) - edit(message, f'`{get_translation("dogbinContent")}`') - - if reply: - match = str(reply.text) - - format_normal = f'{DOGBIN_URL}' - format_view = f'{DOGBIN_URL}v/' - - if match.startswith(format_view): - dogbin = match[len(format_view) :] - elif match.startswith(format_normal): - dogbin = match[len(format_normal) :] - elif match.startswith('del.dog/'): - dogbin = match[len('del.dog/') :] - else: - edit(message, f'`{get_translation("dogbinUrlError")}`') - return - - resp = get(f'{DOGBIN_URL}raw/{dogbin}') - - try: - resp.raise_for_status() - except exceptions.HTTPError as HTTPErr: - edit(message, get_translation('dogbinError', [str(HTTPErr)])) - return - except exceptions.Timeout as TimeoutErr: - edit(message, get_translation('dogbinTimeOut', [str(TimeoutErr)])) - return - except exceptions.TooManyRedirects as RedirectsErr: - edit(message, get_translation('dogbinTooManyRedirects', [str(RedirectsErr)])) - return - - reply_text = get_translation('dogbinResult', ['`', resp.text]) - - edit(message, reply_text, preview=False) - - -HELP.update({'dogbin': get_translation('dogbinInfo')}) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 199443e..e97e00c 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -98,7 +98,6 @@ def youtubedl(message): elif util == 'mp3': ydl_opts = { - 'age_limit': 18, 'outtmpl': f'%(title)s.%(ext)s', 'format': 'bestaudio/best', 'addmetadata': True, diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 503a5d5..83cff6a 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -152,18 +152,6 @@ "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", "directZippyLink": "%1 (%2)\n[Download](%3)", - "dogbinContent": "Getting dogbin content\u2026", - "dogbinError": "Request returned an unsuccessful status code.\n\n %1", - "dogbinInfo": ".paste <text>\nUsage: Create a paste or a shortened url using dogbin (https://del.dog/)\n\n.getpaste\nUsage: Gets the content of a paste or shortened url from dogbin (https://del.dog/)", - "dogbinPasteResult": "%1Pasted successfully!\n\nDogbin URL:%1 %2", - "dogbinPasteResult2": "%1Pasted successfully!%1\n\n%1Shortened URL:%1 %2\n\n%1Original(non-shortened) URLs%1\n%1Dogbin URL:%1 %3\n", - "dogbinPasting": "Pasting text\u2026", - "dogbinReach": "Failed to reach Dogbin", - "dogbinResult": "%1Fetched dogbin URL content successfully!\n\nContent:%1 %2", - "dogbinTimeOut": "Request timed out. %1", - "dogbinTooManyRedirects": "Request exceeded the configured number of maximum redirections. %1", - "dogbinUrlError": "Is that even a dogbin url?", - "dogbinUsage": "Elon Musk said i can't paste void.", "downloadMedia": "Downloading\u2026", "downloadMediaError": "My F\u00fchrer havent set me up to download this type of media yet.", "downloadReply": "Please reply a document.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index b9d5190..4a58e84 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -153,18 +153,6 @@ "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", "directZippyLink": "%1 (%2)\n[\u0130ndir](%3)", - "dogbinContent": "Dogbin i\u00e7eri\u011fi al\u0131n\u0131yor\u2026", - "dogbinError": "\u0130stek ba\u015far\u0131s\u0131z bir durum kodu d\u00f6nd\u00fcrd\u00fc.\n\n %1", - "dogbinInfo": ".paste <metin>\nKullan\u0131m: Dogbin kullanarak yap\u0131\u015ft\u0131r\u0131lm\u0131\u015f veya k\u0131salt\u0131lm\u0131\u015f url olu\u015fturma (https://del.dog/)\n\n.getpaste\nKullan\u0131m: Dogbin url i\u00e7eri\u011fini metne aktar\u0131r (https://del.dog/)", - "dogbinPasteResult": "%1Ba\u015far\u0131yla yap\u0131\u015ft\u0131r\u0131ld\u0131!\n\nDogbin URL:%1 %2", - "dogbinPasteResult2": "%1Ba\u015far\u0131yla yap\u0131\u015ft\u0131r\u0131ld\u0131!%1\n\n%1K\u0131salt\u0131lm\u0131\u015f link:%1 %2\n\n%1K\u0131salt\u0131lmam\u0131\u015f linkler%1\n%1Dogbin linki:%1 %3\n", - "dogbinPasting": "Metin yap\u0131\u015ft\u0131r\u0131l\u0131yor\u2026", - "dogbinReach": "Dogbine ula\u015f\u0131lamad\u0131", - "dogbinResult": "%1Dogbin URL i\u00e7eri\u011fi ba\u015far\u0131yla getirildi!\n\n\u0130\u00e7erik:%1 %2", - "dogbinTimeOut": "\u0130stek zaman a\u015f\u0131m\u0131na u\u011frad\u0131. %1", - "dogbinTooManyRedirects": "\u0130stek, yap\u0131land\u0131r\u0131lm\u0131\u015f en fazla y\u00f6nlendirme say\u0131s\u0131n\u0131 a\u015ft\u0131. %1", - "dogbinUrlError": "Bu bir Dogbin URL'si mi?", - "dogbinUsage": "Elon Musk bo\u015flu\u011fu yap\u0131\u015ft\u0131ramayaca\u011f\u0131m\u0131 s\u00f6yledi.", "downloadMedia": "\u0130ndiriliyor\u2026", "downloadMediaError": "Hen\u00fcz yap\u0131mc\u0131lar\u0131m bu t\u00fcrde bir medyay\u0131 indirmem i\u00e7in beni ayarlamam\u0131\u015f.", "downloadReply": "Please reply a document.", From be5f502d93566c5f1836ea2d2371c8bd5599c236 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 30 Oct 2021 20:21:37 +0300 Subject: [PATCH 085/242] seden: effects: Fix 'NoneType' Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/effects.py | 42 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index dab6708..baa39bc 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -30,9 +30,12 @@ def earrape(message): util = args[0].lower() if util == 'mp4': if not ( - reply.video - or reply.video_note - or (reply.document and 'video' in reply.document.mime_type) + reply + and ( + reply.video + or reply.video_note + or (reply.document and 'video' in reply.document.mime_type) + ) ): edit(message, f'`{get_translation("wrongMedia")}`') else: @@ -55,12 +58,15 @@ def earrape(message): message.delete() elif util == 'mp3': if not ( - reply.video - or reply.video_note - or ( - reply.audio - or reply.voice - or (reply.document and 'video' in reply.document.mime_type) + reply + and ( + reply.video + or reply.video_note + or ( + reply.audio + or reply.voice + or (reply.document and 'video' in reply.document.mime_type) + ) ) ): edit(message, f'`{get_translation("wrongMedia")}`') @@ -93,9 +99,12 @@ def nightcore(message): reply = message.reply_to_message if not ( - reply.audio - or reply.voice - or (reply.document and 'audio' in reply.document.mime_type) + reply + and ( + reply.audio + or reply.voice + or (reply.document and 'audio' in reply.document.mime_type) + ) ): edit(message, f'`{get_translation("wrongMedia")}`') else: @@ -129,9 +138,12 @@ def slowedtoperfection(message): reply = message.reply_to_message if not ( - reply.audio - or reply.voice - or (reply.document and 'audio' in reply.document.mime_type) + reply + and ( + reply.audio + or reply.voice + or (reply.document and 'audio' in reply.document.mime_type) + ) ): edit(message, f'`{get_translation("wrongMedia")}`') else: From fc5980042c513d937716f6c30685ec360d2322b7 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 30 Oct 2021 20:41:23 +0300 Subject: [PATCH 086/242] seden: Nuke Deezer module Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 1 - sedenbot/__init__.py | 3 --- sedenbot/modules/deezer.py | 45 -------------------------------------- 3 files changed, 49 deletions(-) delete mode 100644 sedenbot/modules/deezer.py diff --git a/requirements.txt b/requirements.txt index 33d4986..3597c5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ bs4 cowpy -deethon emoji exifread gitpython diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index e3a1710..ad9cacb 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -156,9 +156,6 @@ def set_logger(): PACKNAME = environ.get('PACKNAME', None) PACKNICK = environ.get('PACKNICK', None) -# Deezer ARL Token -DEEZER_TOKEN = environ.get('DEEZER_TOKEN', None) - # SQL Database URL DATABASE_URL = environ.get('DATABASE_URL', None) diff --git a/sedenbot/modules/deezer.py b/sedenbot/modules/deezer.py deleted file mode 100644 index 6499752..0000000 --- a/sedenbot/modules/deezer.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from deethon import Session -from sedenbot import DEEZER_TOKEN, HELP -from sedenecem.core import edit, extract_args, get_translation, reply_audio, sedenify - - -@sedenify(pattern='^.deezer') -def deezermusic(message): - if not DEEZER_TOKEN: - return edit(message, f'`{get_translation("deezerArlMissing")}`') - args = extract_args(message) - url = args - edit(message, f'`{get_translation("processing")}`') - if not url: - return edit(message, f'`{get_translation("wrongCommand")}`') - - try: - deezer = Session(DEEZER_TOKEN) - except Exception as e: - return edit(message, get_translation('banError', ['`', '**', e])) - - try: - if 'deezer' in url: - if 'track' in url: - track = deezer.download(url, bitrate='MP3_320') - edit(message, f'`{get_translation("uploadMedia")}`') - reply_audio(message, track, delete_orig=True) - elif 'album' in url: - album = deezer.download(url, bitrate='MP3_320') - edit(message, f'`{get_translation("uploadMedia")}`') - for track in album: - reply_audio(message, track, delete_orig=True) - except Exception as e: - return edit(message, get_translation('banError', ['`', '**', e])) - - -HELP.update({'deezer': get_translation('deezerInfo')}) From 1f8060a2f38b8cf4be2e0c11b77f5fec0793f9dd Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 31 Oct 2021 13:20:11 +0300 Subject: [PATCH 087/242] seden: Change idle to run Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- seden.py | 6 ++---- sedenbot/__init__.py | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/seden.py b/seden.py index 4676c6b..4259adc 100644 --- a/seden.py +++ b/seden.py @@ -8,8 +8,6 @@ # if __name__ == '__main__': - from sedenbot import app, __import_modules, idle + from sedenbot import app - with app: - __import_modules() - idle() + app.run() \ No newline at end of file diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index ad9cacb..59829de 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -27,7 +27,7 @@ import sedenecem.translator as _tr from dotenv import load_dotenv, set_key, unset_key -from pyrogram import Client, filters, idle +from pyrogram import Client, filters from pyrogram.handlers import MessageHandler from requests import get @@ -295,5 +295,7 @@ def __import_modules(): LOGS.warn(get_translation('loadedModulesError', [module])) +__import_modules() + LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) From 50854034cc0acc8b3b37f357acbb0f0e6b67fafa Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 31 Oct 2021 19:37:56 +0300 Subject: [PATCH 088/242] seden: Nuke currency Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sample_config.env | 2 +- sedenbot/modules/scrapers.py | 33 +++++---------------------------- sedenecem/translator/en.json | 2 -- sedenecem/translator/tr.json | 2 -- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/sample_config.env b/sample_config.env index 0fe931a..2cf21c3 100644 --- a/sample_config.env +++ b/sample_config.env @@ -48,7 +48,7 @@ GENIUS_TOKEN='' LOG_VERBOSE=False # PM Auto Ban and other PmPermit Stuffs -PM_AUTO_BAN='' +PM_AUTO_BAN=False PM_MSG_COUNT='' PM_UNAPPROVED='' diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 7c53fc5..e1ccfe1 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -206,10 +206,12 @@ def get_result(res): title = res.find('h3') if title: title = title.text - desc = res.find('div', attrs= {'class': ['VwiC3b', 'yXK7lf', 'MUxGbd', 'yDYNvb', 'lyLwlc']}) + desc = res.find( + 'div', attrs={'class': ['VwiC3b', 'yXK7lf', 'MUxGbd', 'yDYNvb', 'lyLwlc']} + ) if desc: desc = desc.text - + if link and title and desc: return f'[{replacer(title)}]({link["href"]})\n{desc or ""}' @@ -237,7 +239,7 @@ def get_result(res): ) soup = BeautifulSoup(req.text, 'html.parser') - + res1 = soup.find_all('div', attrs={'class': 'g'}) out = '' @@ -521,32 +523,7 @@ def doviz(message): edit(message, out) -@sedenify(pattern='^.currency') -def currency(message): - input_str = extract_args(message) - input_sgra = input_str.split(' ') - if len(input_sgra) == 3: - try: - number = float(input_sgra[0]) - currency_from = input_sgra[1].upper() - currency_to = input_sgra[2].upper() - request_url = f'https://api.ratesapi.io/api/latest?base={currency_from}' - current_response = get(request_url).json() - if currency_to in current_response['rates']: - current_rate = float(current_response['rates'][currency_to]) - rebmun = round(number * current_rate, 2) - edit(message, f'**{number} {currency_from} = {rebmun} {currency_to}**') - else: - edit(message, f'`{get_translation("currencyError")}`') - except Exception as e: - edit(message, str(e)) - else: - edit(message, f'`{get_translation("syntaxError")}`') - return - - HELP.update({'img': get_translation('imgInfo')}) -HELP.update({'currency': get_translation('currencyInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) HELP.update({'goolag': get_translation('googleInfo')}) HELP.update({'duckduckgo': get_translation('ddgoInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 83cff6a..c9176c2 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -121,8 +121,6 @@ "covidToday": "Today", "covidTotal": "Total", "cpUsage": "\ud83d\ude02\ud83c\udd71\ufe0fIvE\ud83d\udc50sOME\ud83d\udc45text\ud83d\udc45for\u270c\ufe0fMe\ud83d\udc4ctO\ud83d\udc50MAkE\ud83d\udc40iT\ud83d\udc9efunNy!\ud83d\udca6", - "currencyError": "What you're writing looks like an alien currency, so I can't convert it.", - "currencyInfo": ".currency <amount> <from> <to>\nUsage: Converts various currencies for you.", "ddgoDesc": "No description found.", "ddgoInfo": ".ddgo <query>\nUsage: Does a search on DuckDuckGo.", "ddgoLog": "DuckDuckGo Search query %1 was executed successfully", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 4a58e84..07384d7 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -122,8 +122,6 @@ "covidToday": "Bug\u00fcn", "covidTotal": "Toplam", "cpUsage": "\ud83d\ude02Bana\ud83d\udcafBIR\u270c\ufe0fmE\ud83c\udd71\ufe0fIn\ud83d\udc50Ver\ud83d\udc4f", - "currencyError": "Yazd\u0131\u011f\u0131n \u015fey uzayl\u0131lar\u0131n kulland\u0131\u011f\u0131 bir para birimine benziyor, bu y\u00fczden d\u00f6n\u00fc\u015ft\u00fcremiyorum.", - "currencyInfo": ".currency <miktar> <d\u00f6n\u00fc\u015ft\u00fcr\u00fclecek birim> <d\u00f6n\u00fc\u015fecek birim>\nKullan\u0131m: Belirtilen para miktarlar\u0131n\u0131 d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n\n.d\u00f6viz\nKullan\u0131m: G\u00fcncel d\u00f6viz kurlar\u0131n\u0131 getirir.", "ddgoDesc": "A\u00e7\u0131klama sa\u011flanmam\u0131\u015f", "ddgoInfo": ".ddgo <kelime>\nKullan\u0131m: H\u0131zl\u0131 bir DuckDuckGo aramas\u0131 yapar.", "ddgoLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla DuckDuckGo'da arat\u0131ld\u0131!", From c41e6b103f8ab903144e82ea2af961690015dd5f Mon Sep 17 00:00:00 2001 From: fatihesergg <47848940+fatihesergg@users.noreply.github.com> Date: Mon, 8 Nov 2021 21:12:01 +0300 Subject: [PATCH 089/242] seden: Add Spotify module (#11) * seden: Add Spotify module * seden: spotify: Add Heroku env --- app.json | 7 + requirements.txt | 1 + sample_config.env | 6 + sedenbot/__init__.py | 6 + sedenbot/modules/spotify_api.py | 225 ++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 9 +- sedenecem/translator/tr.json | 9 +- 7 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 sedenbot/modules/spotify_api.py diff --git a/app.json b/app.json index cd99203..35c542c 100644 --- a/app.json +++ b/app.json @@ -80,7 +80,14 @@ "SESSION": { "description": "Get this value by running python3 session.py in terminal or bash script.", "required": true + }, + "SPOTIPY_CLIENT_ID": { + "description": "Get this value from https://developer.spotify.com Required for spotify module." + }, + "SPOTIPY_CLIENT_SECRET": { + "description": "Get this value from https://developer.spotify.com Required for spotify module." } + }, "formation": { "seden": { diff --git a/requirements.txt b/requirements.txt index 3597c5a..956855f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,4 @@ tgcrypto urbandict wikipedia yt-dlp +spotipy diff --git a/sample_config.env b/sample_config.env index 2cf21c3..25e0e35 100644 --- a/sample_config.env +++ b/sample_config.env @@ -44,6 +44,12 @@ OCR_APIKEY='' # Genius lyrics get this value from https://genius.com/developers GENIUS_TOKEN='' +# Spotify Client ID +SPOTIPY_CLIENT_ID='' + +# Spotify Client Secret +SPOTIPY_CLIENT_SECRET='' + # If you need Verbosity on the Logging LOG_VERBOSE=False diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 59829de..04351f2 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -133,6 +133,12 @@ def set_logger(): # Genius module GENIUS_TOKEN = environ.get('GENIUS_TOKEN', None) or environ.get('GENIUS', None) +# Spoify Client ID +SPOTIPY_CLIENT_ID = environ.get('SPOTIPY_CLIENT_ID') + +# SPotify Client SECRET +SPOTIPY_CLIENT_SECRET = environ.get('SPOTIPY_CLIENT_SECRET') + # Change Alive Message ALIVE_MSG = environ.get('ALIVE_MSG', None) diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py new file mode 100644 index 0000000..cc8078c --- /dev/null +++ b/sedenbot/modules/spotify_api.py @@ -0,0 +1,225 @@ +from glob import glob +from os import mkdir, path, remove +from queue import Queue +from threading import Thread +from urllib.request import urlretrieve +from zipfile import ZipFile + +from requests import get +from sedenecem.core import ( + edit, + extract_args, + get_translation, + reply_audio, + reply_doc, + reply_img, + sedenify, +) +from spotipy import Spotify, SpotifyClientCredentials +from yt_dlp import YoutubeDL + + +class Spotipy: + def __init__(self): + self.spotify_dir() + + def spotify_dir(self): + if path.exists('./Spotify'): + pass + else: + mkdir('./Spotify') + + def fetch_token(self): + self.spotify_dir() + sp = Spotify(auth_manager=SpotifyClientCredentials()) + return sp + + def download_track(self, query): + ydl_opts = { + 'outtmpl': f'./Spotify/%(title)s.%(ext)s', + 'format': 'bestaudio/best', + 'addmetadata': True, + 'writethumbnail': True, + 'prefer_ffmpeg': True, + "extractaudio": True, + 'geo_bypass': True, + 'nocheckcertificate': True, + 'cachedir': False, + 'default_search': 'ytsearch', + 'noplaylist': True, + 'postprocessors': [ + { + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '320', + }, + {'key': 'EmbedThumbnail'}, + {'key': 'FFmpegMetadata'}, + ], + 'quiet': True, + 'logtostderr': False, + } + + with YoutubeDL(ydl_opts) as ydl: + ydl.download([query]) + + def worker(self, q): + while not q.empty(): + item = q.get() + self.download_track(item) + q.task_done() + + def search_track(self, message): + sp = self.fetch_token() + args = extract_args(message) + if 'zip' in args: + args = extract_args(message).split(' ', 3) + playlist_url = args[2] + edit(message, f'`{get_translation("downloadMedia")}`') + zip_file = ZipFile('./Spotify/playlist.zip', 'w') + fields = "items.track.track_number,items.track.name,items.track.artists.name,items.track.album.name,items.track.album.release_date,total,items.track.album.images" + playlist = sp.playlist_items( + playlist_url, + fields=fields, + additional_types=['track'], + )['items'] + threads = [] + video_urls = [] + q = Queue() + for item in playlist: + track = f'{item["track"]["name"]} ' + artist = item['track']['artists'][0]['name'] + track += artist + video_urls.append(track) + for url in video_urls: + q.put(url) + for i in range(15): + t1 = Thread(target=self.worker, args=(q,), daemon=True) + t1.start() + threads.append(t1) + for t in threads: + t.join() + for i in glob('./Spotify/*.mp3'): + zip_file.write(i) + zip_file.close() + edit(message, f'`{get_translation("uploadingZip")}`') + reply_doc(message, './Spotify/playlist.zip', delete_after_send=True) + message.delete() + for song in glob('./Spotify/*.mp3'): + remove(song) + + else: + args = extract_args(message).split(' ', 2) + playlist_url = args[1] + edit(message, f'`{get_translation("downloadMedia")}`') + fields = "items.track.track_number,items.track.name,items.track.artists.name,items.track.album.name,items.track.album.release_date,total,items.track.album.images" + playlist = sp.playlist_items( + playlist_url, + fields=fields, + additional_types=['track'], + )['items'] + threads = [] + video_urls = [] + q = Queue() + for item in playlist: + track = f'{item["track"]["name"]} ' + artist = item['track']['artists'][0]['name'] + track += artist + video_urls.append(track) + for url in video_urls: + q.put(url) + for i in range(15): + t1 = Thread(target=self.worker, args=(q,), daemon=True) + t1.start() + threads.append(t1) + for t in threads: + t.join() + for song in glob('./Spotify/*.mp3'): + reply_audio(message, song, delete_file=True) + for song in glob('./Spotify/*.mp3'): + remove(song) + + def show_playlist(self, username=None): + sp = self.fetch_token() + global counter + counter = 0 + users_playlists = '\n' + + if username: + result = sp.user_playlists(username, limit=50) + else: + result = sp.user_playlists(username=self.username) + + for item in result['items']: + pl_name = item['name'] + users_playlists += f'▪️ {pl_name}\n' + counter += 1 + msg = f'{users_playlists}' + return msg + + def show_users_detail( + self, + username, + message, + ): + sp = self.fetch_token() + if username == None: + return edit(message, (get_translation("invalidUsername"))) + else: + r = get(f"https://open.spotify.com/user/{username}") + if r.status_code == 404: + edit(message, get_translation("userNotFound", ['**', '`', username])) + + else: + user = sp.user(username) + profile_photo = [i['url'] for i in user['images']] + if profile_photo: + r = urlretrieve("".join(profile_photo), './Spotify/pfp.png') + else: + profile_photo = None + pass + + out = get_translation( + 'spotifyResult', + [ + '**', + '`', + username, + user['external_urls']['spotify'], + self.show_playlist(username), + counter, + ], + ) + + media_perm = True + if 'group' in message.chat.type: + perm = message.chat.permissions + media_perm = perm.can_send_media_messages + + if profile_photo and media_perm: + reply_img( + message, + photo='./Spotify/pfp.png', + caption=out, + delete_file=True, + ) + message.delete() + else: + edit(message, out, preview=False) + + +@sedenify(pattern='^.spoti(|fy)') +def spotify_download(message): + spotify = Spotipy() + args = extract_args(message).split(' ', 2) + if args[0] == 'dl' or args[0] == 'dl zip': + spotify.search_track(message) + + elif 'show' == args[0]: + if len(args) == 1: + edit(message, f'`{get_translation("invalidUsername")}`') + else: + username = args[1] + spotify.show_users_detail(username=username, message=message) + else: + edit(message, f'`{get_translation("invalidProcess")}`') diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index c9176c2..e6a5829 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -170,10 +170,10 @@ "evalLog": "Eval query %1 processed successfully", "evalUsage": "Give a statement to evaluate.", "exifError": "EXIF data not found.", + "exifInfo": ".exif\nUsage: Extract EXIF information from given photo file.", "exifLog": "EXIF data for image:\n", "exifMaps": "Google Maps Link: ", "exifProcess": "Extracting EXIF information.", - "exifInfo": ".exif\nUsage: Extract EXIF information from given photo file.", "ezanvaktiErrorInfo": "No information was found for %1.", "ezanvaktiKonum": "Please specify a city next to the command.", "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", @@ -239,6 +239,8 @@ "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", + "invalidProcess": "Invalid arg!", + "invalidUsername": "Enter a valid username", "kangstr1": "Using Witchery to kang this sticker\u2026", "kangstr10": "Mr.Steal Your Sticker is stealing this sticker\u2026", "kangstr11": "Finally I'm kanging a sticker that Ecem will love\u2026", @@ -511,6 +513,7 @@ "speedtestInfo": ".speedtest\nUsage: Does a speedtest and shows results.", "speedtestResultDoc": "%1SpeedTest%1 completed in %2 seconds!", "speedtestResultText": "%1SpeedTest%1 completed in %2 seconds!\nDownload: %3\nUpload: %4\nPing: %5\nISP: %6\nISP rating: %7\n%8", + "spotifyResult": "%1Username:%1 %2%3%2\n%1Profile URL: %4\nPlaylist(s):%1\n%2%5%2\n\n%2%6%2 %1playlists found!%1", "ssInfo": ".ss <url>\nUsage: Takes a screenshot of a website and sends the screenshot.\nExample of a valid URL: https://devotag.com", "ssResult": "Generating screenshot of the page\u2026\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", "ssUpload": "Uploading screenshot\u2026", @@ -589,7 +592,9 @@ "uploadInfo": ".download reply to media \nUsage: Downloads file to server.\n\n.upload <path in server>\nUsage: Uploads a locally stored file to chat.", "uploadMedia": "Uploading media\u2026", "uploadReply": "I cant upload nothing here.", + "uploadingZip": "Sending zip file\u2026", "urlError": "Error: Unable to extract link", + "userNotFound": "%1User%1 %2%3%2 %1not found!%1", "useridResult": "%1Username:%1 %2\n%1User ID:%1 %3%4%3", "userlist": "%1%2 Users in%1 %3%4%3%1:%1", "usernameSuccess": "Your username was succesfully changed.", @@ -620,4 +625,4 @@ "zombiesRemove": "Removing deleted account(s)\u2026", "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" -} \ No newline at end of file +} diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 07384d7..2ca186a 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -171,10 +171,10 @@ "evalLog": "Eval sorgusu %1 ba\u015far\u0131yla y\u00fcr\u00fct\u00fcld\u00fc", "evalUsage": "De\u011ferlendirmek i\u00e7in bir ifade verin.", "exifError": "EXIF verisi bulunamad\u0131.", + "exifInfo": ".exif\nKullan\u0131m: Verilen foto\u011fraf dosyas\u0131ndan EXIF verisini \u00e7\u0131kart\u0131r.", "exifLog": "EXIF verisi:\n", "exifMaps": "Google Maps Linki: ", "exifProcess": "EXIF verisi \u00e7\u0131kart\u0131l\u0131yor.", - "exifInfo": ".exif\nKullan\u0131m: Verilen foto\u011fraf dosyas\u0131ndan EXIF verisini \u00e7\u0131kart\u0131r.", "ezanvaktiErrorInfo": "%1 i\u00e7in bir bilgi bulunamad\u0131.", "ezanvaktiKonum": "L\u00fctfen komutun yan\u0131na bir \u015fehir belirtin", "ezanvaktiShowInfo": "%1Diyanet Namaz Vakitleri%1\n\n\ud83d\udccd %1Yer:%1 %2%3%2\n\n\ud83c\udfd9 %1\u0130msak:%1 %2%4%2\n\ud83c\udf05 %1G\u00fcne\u015f:%1 %2%5%2\n\ud83c\udf07 %1\u00d6\u011fle:%1 %2%6%2\n\ud83c\udf06 %1\u0130kindi:%1 %2%7%2\n\ud83c\udf03 %1Ak\u015fam:%1 %2%8%2\n\ud83c\udf0c %1Yats\u0131:%1 %2%9%2", @@ -241,6 +241,8 @@ "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", + "invalidProcess": "Ge\u00e7ersiz \u0130\u015flem!", + "invalidUsername": "Ge\u00e7erli bir kullan\u0131c\u0131 girin!", "kangstr1": "\u00c7\u0131kartmay\u0131 d\u0131zl\u0131yorum\u2026", "kangstr10": "Bay d\u0131zc\u0131 bu \u00e7\u0131kartmay\u0131 d\u0131zl\u0131yor\u2026", "kangstr11": "Sonunda Ecem'in sevece\u011fi bir \u00e7\u0131kartma d\u0131zl\u0131yorum\u2026", @@ -511,6 +513,7 @@ "speedtestInfo": ".speedtest\nKullan\u0131m: Bir speedtest uygular ve sonucu g\u00f6sterir.", "speedtestResultDoc": "%1SpeedTest%1 %2 saniyede tamamland\u0131!", "speedtestResultText": "%1SpeedTest%1 %2 saniyede tamamland\u0131!\n\u0130ndirme: %3\nY\u00fckleme: %4\nGecikme: %5\nISS: %6\nISS puan\u0131: %7\n%8", + "spotifyResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2%3%2\n%1Profil Ba\u011flant\u0131s\u0131: %4\n\u00c7alma Listeleri:%1\n%2%5%2\n\n%1Toplamda%1 %2%6%2 %1adet \u00e7alma listesi bulundu!%1", "ssInfo": ".ss <link>\nKullan\u0131m: Belirtilen web sitesinden bir ekran g\u00f6r\u00fcnt\u00fcs\u00fc al\u0131r ve g\u00f6nderir.\nGe\u00e7erli bir site ba\u011flant\u0131s\u0131 \u00f6rne\u011fi: https://devotag.com", "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor\u2026\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", "ssUpload": "Ekran g\u00f6r\u00fcnt\u00fcs\u00fc kar\u015f\u0131ya y\u00fckleniyor\u2026", @@ -589,7 +592,9 @@ "uploadInfo": ".download <bir \u015feye cevap vererek>\nKullan\u0131m: Sunucuya dosyay\u0131 indirir.\n\n.upload <sunucudaki dosya yolu>\nKullan\u0131m: Sunucunuzdaki bir dosyay\u0131 sohbete upload eder.", "uploadMedia": "Medya y\u00fckleniyor\u2026", "uploadReply": "Buraya hi\u00e7li\u011fi y\u00fckleyemem.", + "uploadingZip": "Zip dosyas\u0131 g\u00f6nderiliyor\u2026", "urlError": "Hata: link \u00e7\u0131kar\u0131lam\u0131yor", + "userNotFound": "%1Kullan\u0131c\u0131%1 %2%3%2 %1bulunamad\u0131%1", "useridResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2\n%1Kullan\u0131c\u0131 ID:%1 %3%4%3", "userlist": "%3%4%3 %1i\u00e7erisinde bulunan %2 kullan\u0131c\u0131lar:%1", "usernameSuccess": "Kullan\u0131c\u0131 ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", @@ -620,4 +625,4 @@ "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor\u2026", "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" -} \ No newline at end of file +} From 46004a61706670d5d8da78940ae4bda7f48f2e39 Mon Sep 17 00:00:00 2001 From: fatihesergg <47848940+fatihesergg@users.noreply.github.com> Date: Wed, 10 Nov 2021 22:46:59 +0300 Subject: [PATCH 090/242] seden: spotify: Add help msg (#13) --- sedenbot/modules/spotify_api.py | 4 ++++ sedenecem/translator/de.json | 4 ++-- sedenecem/translator/en.json | 3 ++- sedenecem/translator/tr.json | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index cc8078c..ab916bf 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -6,6 +6,7 @@ from zipfile import ZipFile from requests import get +from sedenbot import HELP from sedenecem.core import ( edit, extract_args, @@ -223,3 +224,6 @@ def spotify_download(message): spotify.show_users_detail(username=username, message=message) else: edit(message, f'`{get_translation("invalidProcess")}`') + + +HELP.update({'spotify': get_translation('spotifyInfo')}) diff --git a/sedenecem/translator/de.json b/sedenecem/translator/de.json index e4b6f67..6741e0e 100644 --- a/sedenecem/translator/de.json +++ b/sedenecem/translator/de.json @@ -108,9 +108,9 @@ "evalLog": "Eval abfrage %1 erfolgreich verarbeitet", "evalUsage": "Geben sie eine aussage zu bewerten.", "exifError": "EXIF-Daten nicht gefunden.", - "exifLog": "EXIF-Daten für Bild:\n", - "exifProcess": "Extrahieren von EXIF-Informationen.", "exifInfo": ".exif\nVerwendung: Extrahieren Sie EXIF-Informationen aus der angegebenen Fotodatei.", + "exifLog": "EXIF-Daten f\u00fcr Bild:\n", + "exifProcess": "Extrahieren von EXIF-Informationen.", "ezanvaktiErrorInfo": "F\u00fcr %1 wurden keine informationen gefunden.", "ezanvaktiKonum": "Bitte geben sie eine stadt neben dem befehl an.", "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e6a5829..5c8a83b 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -513,6 +513,7 @@ "speedtestInfo": ".speedtest\nUsage: Does a speedtest and shows results.", "speedtestResultDoc": "%1SpeedTest%1 completed in %2 seconds!", "speedtestResultText": "%1SpeedTest%1 completed in %2 seconds!\nDownload: %3\nUpload: %4\nPing: %5\nISP: %6\nISP rating: %7\n%8", + "spotifyInfo": "Shows user information\n.spoti(ify) show <username>\nDownloads and seding playlist(s) as zip\n.spoti(fy) dl zip <playlisturl>\nDownloads and send playlist(s) as without zip.\n.spoti(fy) dl <playlisturl>", "spotifyResult": "%1Username:%1 %2%3%2\n%1Profile URL: %4\nPlaylist(s):%1\n%2%5%2\n\n%2%6%2 %1playlists found!%1", "ssInfo": ".ss <url>\nUsage: Takes a screenshot of a website and sends the screenshot.\nExample of a valid URL: https://devotag.com", "ssResult": "Generating screenshot of the page\u2026\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", @@ -625,4 +626,4 @@ "zombiesRemove": "Removing deleted account(s)\u2026", "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" -} +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2ca186a..f11148e 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -513,6 +513,7 @@ "speedtestInfo": ".speedtest\nKullan\u0131m: Bir speedtest uygular ve sonucu g\u00f6sterir.", "speedtestResultDoc": "%1SpeedTest%1 %2 saniyede tamamland\u0131!", "speedtestResultText": "%1SpeedTest%1 %2 saniyede tamamland\u0131!\n\u0130ndirme: %3\nY\u00fckleme: %4\nGecikme: %5\nISS: %6\nISS puan\u0131: %7\n%8", + "spotifyInfo": "Kullan\u0131c\u0131 bilgilerini g\u00f6sterir\n.spoti(ify) show <username>\nPlaylist(leri) indirir ve zipliyip g\u00f6nderir.\n.spoti(fy) dl zip <playlisturl>\nPlaylist(leri) indirir ve ziplemeden g\u00f6nderir.\n.spoti(fy) dl <playlisturl>", "spotifyResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2%3%2\n%1Profil Ba\u011flant\u0131s\u0131: %4\n\u00c7alma Listeleri:%1\n%2%5%2\n\n%1Toplamda%1 %2%6%2 %1adet \u00e7alma listesi bulundu!%1", "ssInfo": ".ss <link>\nKullan\u0131m: Belirtilen web sitesinden bir ekran g\u00f6r\u00fcnt\u00fcs\u00fc al\u0131r ve g\u00f6nderir.\nGe\u00e7erli bir site ba\u011flant\u0131s\u0131 \u00f6rne\u011fi: https://devotag.com", "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor\u2026\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", @@ -625,4 +626,4 @@ "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor\u2026", "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" -} +} \ No newline at end of file From 651520240cb03f99160ffe28f54a85729b3456a8 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 12 Dec 2021 22:27:57 +0300 Subject: [PATCH 091/242] seden: [EXP 2/2] Nuke Railway deploy Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dbb96b4..4aff8b9 100755 --- a/README.md +++ b/README.md @@ -39,9 +39,8 @@ python3 seden.py ### Nix/NixOS Just type `nix-shell` command in bot folder. -## Heroku / Railway Deploy +## Heroku [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TeamDerUntergang/Telegram-SedenUserBot/tree/seden) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FTeamDerUntergang%2FTelegram-SedenUserBot&plugins=postgresql&envs=API_ID%2CAPI_HASH%2CSESSION%2CALIVE_MSG%2CBOT_PREFIX%2CCHROME_DRIVER%2CLOG_ID%2CLOG_VERBOSE%2CPM_AUTO_BAN%2CPM_MSG_COUNT%2CPM_UNAPPROVED%2CSEDEN_LANG&optionalEnvs=ALIVE_MSG%2CBOT_PREFIX%2CCHROME_DRIVER%2CLOG_ID%2CLOG_VERBOSE%2CPM_AUTO_BAN%2CPM_MSG_COUNT%2CPM_UNAPPROVED%2CSEDEN_LANG) If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). From 3d4aabffbdde5b50391771519b0d8c821263bf03 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 26 Dec 2021 00:25:20 +0300 Subject: [PATCH 092/242] seden: stickers: Fix kang, packinfo Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index c750bec..340517c 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -56,7 +56,7 @@ def kang(client, message): else: edit(message, f'`{get_translation("stickerError")}`') return - + if not reply.sticker: try: media = resizer(media) @@ -99,7 +99,7 @@ def kang(client, message): def pack_created(pname): try: set_name = InputStickerSetShortName(short_name=TEMP_SETTINGS[pname]) - set = GetStickerSet(stickerset=set_name) + set = GetStickerSet(stickerset=set_name, hash=0) client.send(data=set) return True except BaseException: @@ -237,7 +237,8 @@ def packinfo(client, message): get_stickerset = client.send( GetStickerSet( - stickerset=InputStickerSetShortName(short_name=reply.sticker.set_name) + stickerset=InputStickerSetShortName(short_name=reply.sticker.set_name), + hash=0, ) ) pack_emojis = [] From a0ba8d9f8b01413e6138c95810c4175b5314cee8 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 28 Dec 2021 17:19:16 +0300 Subject: [PATCH 093/242] seden: Cleanup app.json Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- app.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app.json b/app.json index 35c542c..7cf4608 100644 --- a/app.json +++ b/app.json @@ -80,14 +80,7 @@ "SESSION": { "description": "Get this value by running python3 session.py in terminal or bash script.", "required": true - }, - "SPOTIPY_CLIENT_ID": { - "description": "Get this value from https://developer.spotify.com Required for spotify module." - }, - "SPOTIPY_CLIENT_SECRET": { - "description": "Get this value from https://developer.spotify.com Required for spotify module." } - }, "formation": { "seden": { @@ -110,4 +103,4 @@ "stack": "container", "success_url": "https://telegram.dog/SedenUserBot", "website": "https://teamderuntergang.github.io/" -} +} \ No newline at end of file From 04956175194c3d1176680e90c20be0b63c41dcfe Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 1 Jan 2022 19:15:51 +0300 Subject: [PATCH 094/242] seden: Update copyright notice Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- seden.py | 2 +- seden_translate_sorter.py | 2 +- sedenbot/__init__.py | 2 +- sedenbot/modules/afk.py | 2 +- sedenbot/modules/android.py | 2 +- sedenbot/modules/autopp.py | 2 +- sedenbot/modules/ban.py | 2 +- sedenbot/modules/blacklist.py | 2 +- sedenbot/modules/chat.py | 2 +- sedenbot/modules/colors.py | 2 +- sedenbot/modules/covid19.py | 2 +- sedenbot/modules/deepfry.py | 2 +- sedenbot/modules/direct_link.py | 2 +- sedenbot/modules/ecem.py | 2 +- sedenbot/modules/effects.py | 2 +- sedenbot/modules/env.py | 2 +- sedenbot/modules/exif.py | 2 +- sedenbot/modules/ezanvakti.py | 2 +- sedenbot/modules/filters.py | 2 +- sedenbot/modules/git.py | 2 +- sedenbot/modules/globals.py | 2 +- sedenbot/modules/horeke.py | 2 +- sedenbot/modules/info.py | 2 +- sedenbot/modules/lastfm.py | 2 +- sedenbot/modules/locks.py | 2 +- sedenbot/modules/lyrics.py | 2 +- sedenbot/modules/memes.py | 2 +- sedenbot/modules/misc.py | 2 +- sedenbot/modules/notes.py | 2 +- sedenbot/modules/ocr.py | 2 +- sedenbot/modules/pmpermit.py | 2 +- sedenbot/modules/profile.py | 2 +- sedenbot/modules/purge.py | 2 +- sedenbot/modules/qrcode.py | 2 +- sedenbot/modules/quotly.py | 2 +- sedenbot/modules/remove_bg.py | 2 +- sedenbot/modules/reverse.py | 2 +- sedenbot/modules/rgb.py | 2 +- sedenbot/modules/sangmata.py | 2 +- sedenbot/modules/scrapers.py | 2 +- sedenbot/modules/screencapture.py | 2 +- sedenbot/modules/sed.py | 2 +- sedenbot/modules/seden.py | 2 +- sedenbot/modules/snips.py | 2 +- sedenbot/modules/spammer.py | 2 +- sedenbot/modules/spamwatch.py | 2 +- sedenbot/modules/speedtest.py | 2 +- sedenbot/modules/spotify_api.py | 9 +++++++++ sedenbot/modules/stickers.py | 2 +- sedenbot/modules/system.py | 2 +- sedenbot/modules/updater.py | 2 +- sedenbot/modules/updown.py | 2 +- sedenbot/modules/weather.py | 2 +- sedenbot/modules/youtubedl.py | 2 +- sedenecem/core/__init__.py | 2 +- sedenecem/core/conv.py | 2 +- sedenecem/core/image.py | 2 +- sedenecem/core/misc.py | 2 +- sedenecem/core/proxy.py | 2 +- sedenecem/core/replier.py | 2 +- sedenecem/core/sedenify.py | 2 +- sedenecem/core/sedenlog.py | 2 +- sedenecem/core/send.py | 2 +- sedenecem/core/webdriver.py | 2 +- sedenecem/translator/__init__.py | 2 +- session.py | 2 +- 66 files changed, 74 insertions(+), 65 deletions(-) diff --git a/seden.py b/seden.py index 4259adc..68ed7dc 100644 --- a/seden.py +++ b/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/seden_translate_sorter.py b/seden_translate_sorter.py index 4564c01..f4ec6f4 100644 --- a/seden_translate_sorter.py +++ b/seden_translate_sorter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 04351f2..ef65441 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index a366c74..6326690 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 7aa5b72..6ff4d4c 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index ada400e..dbba4fc 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 965932e..cda9981 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/blacklist.py index 094ccc6..e7baac3 100644 --- a/sedenbot/modules/blacklist.py +++ b/sedenbot/modules/blacklist.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat.py index 377e50e..df5cc72 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/colors.py b/sedenbot/modules/colors.py index 6dbf69e..3aa5b99 100644 --- a/sedenbot/modules/colors.py +++ b/sedenbot/modules/colors.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py index 8f1af2b..514629b 100644 --- a/sedenbot/modules/covid19.py +++ b/sedenbot/modules/covid19.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index 22dc8cc..a0cd02d 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index 4d4fe62..e4a9b16 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ecem.py b/sedenbot/modules/ecem.py index feee00b..74a34a2 100644 --- a/sedenbot/modules/ecem.py +++ b/sedenbot/modules/ecem.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index baa39bc..34e0e07 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/env.py b/sedenbot/modules/env.py index 3f84233..dc4b642 100644 --- a/sedenbot/modules/env.py +++ b/sedenbot/modules/env.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index 909cd30..0b22778 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index d54c9e6..ec9868c 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index 19ca963..a83cadd 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/git.py b/sedenbot/modules/git.py index 3ec42a4..26284a9 100644 --- a/sedenbot/modules/git.py +++ b/sedenbot/modules/git.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 11151b3..0dbba7a 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index f43ce13..f85533a 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 7643022..81428ec 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/lastfm.py b/sedenbot/modules/lastfm.py index 00909d5..cd9d97c 100644 --- a/sedenbot/modules/lastfm.py +++ b/sedenbot/modules/lastfm.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/locks.py index c63ee4e..2744df3 100644 --- a/sedenbot/modules/locks.py +++ b/sedenbot/modules/locks.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/lyrics.py index 6dfc371..477fd3b 100644 --- a/sedenbot/modules/lyrics.py +++ b/sedenbot/modules/lyrics.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index 8b1a559..c329703 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index 78cf418..bfca486 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index 7b8aace..905b3e9 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/ocr.py index 81127e9..87e3e02 100644 --- a/sedenbot/modules/ocr.py +++ b/sedenbot/modules/ocr.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 99abf9a..9909e93 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index d8425cd..495bb60 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index 45eb2f9..0b174c9 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index 17c7c13..b86e7af 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/quotly.py b/sedenbot/modules/quotly.py index d0f733d..af393a3 100644 --- a/sedenbot/modules/quotly.py +++ b/sedenbot/modules/quotly.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 814db07..9547719 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index 1a0bcd8..537a63c 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index 468a352..5da2af6 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/sangmata.py b/sedenbot/modules/sangmata.py index 0fe61f1..438345e 100644 --- a/sedenbot/modules/sangmata.py +++ b/sedenbot/modules/sangmata.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index e1ccfe1..0465b0b 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index 3d950e6..b242488 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/sed.py index e48d71a..c3c49fd 100644 --- a/sedenbot/modules/sed.py +++ b/sedenbot/modules/sed.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index de952f9..df00152 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 18d1cd9..4df58b1 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index ade3efb..728df85 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index e95d95f..3b09f00 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index c12379f..d87097c 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index ab916bf..3352fde 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -1,3 +1,12 @@ +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + from glob import glob from os import mkdir, path, remove from queue import Queue diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 340517c..bdf0b84 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 7e21768..1757f87 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index 56563f2..a58593e 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index 55b14cc..f2bc943 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index d48ec3e..9ef07ad 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index e97e00c..0cff372 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # Copyright (C) 2021 kisekinopureya <https://github.com/kisekinopureya> # # This file is part of TeamDerUntergang project, diff --git a/sedenecem/core/__init__.py b/sedenecem/core/__init__.py index 1aad87d..263a682 100644 --- a/sedenecem/core/__init__.py +++ b/sedenecem/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/conv.py b/sedenecem/core/conv.py index 06d51df..6319f14 100644 --- a/sedenecem/core/conv.py +++ b/sedenecem/core/conv.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index 10de538..be1e077 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index acb5b3c..aef00d0 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/proxy.py b/sedenecem/core/proxy.py index 1260af9..6f8229c 100644 --- a/sedenecem/core/proxy.py +++ b/sedenecem/core/proxy.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 8868cfc..50edf08 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index f3e0b09..f7bf7cc 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/sedenlog.py b/sedenecem/core/sedenlog.py index 7ace488..1136e06 100644 --- a/sedenecem/core/sedenlog.py +++ b/sedenecem/core/sedenlog.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/send.py b/sedenecem/core/send.py index 21a7eb9..55bda18 100644 --- a/sedenecem/core/send.py +++ b/sedenecem/core/send.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/webdriver.py b/sedenecem/core/webdriver.py index 335c642..44228d9 100644 --- a/sedenecem/core/webdriver.py +++ b/sedenecem/core/webdriver.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/translator/__init__.py b/sedenecem/translator/__init__.py index af380f4..0f81797 100644 --- a/sedenecem/translator/__init__.py +++ b/sedenecem/translator/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/session.py b/session.py index 1d1217b..ae2950f 100644 --- a/session.py +++ b/session.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2021 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. From f7e5628c084ca18958842f118308054d4d5561a3 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 18 Jan 2022 14:38:42 +0300 Subject: [PATCH 095/242] seden: Update modules * Change kick_chat_member to ban_chat_member * Update ban.py, globals.py * Minor string edits * Update Pyrogram to 1.3.5 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 4 ++-- sedenbot/modules/ban.py | 6 +++--- sedenbot/modules/globals.py | 6 +++--- sedenecem/translator/en.json | 14 +++++++------- sedenecem/translator/tr.json | 12 ++++++------ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/requirements.txt b/requirements.txt index 956855f..5875220 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,10 +10,10 @@ humanize image_to_ascii lyricsgenius pillow -git+https://github.com/frknkrc44/psycopg2@try-switch-pkg-config +psycopg2 pybase64 pylast -pyrogram==1.2.9 +pyrogram==1.3.5 python-barcode python-dotenv qrcode diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index cda9981..6925978 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -65,7 +65,7 @@ def ban_user(client, message): try: chat_id = message.chat.id - client.kick_chat_member(chat_id, user.id) + client.ban_chat_member(chat_id, user.id) edit( message, get_translation('banResult', ['**', user.first_name, user.id, '`']) ) @@ -153,7 +153,7 @@ def kick_user(client, message): try: chat_id = message.chat.id - client.kick_chat_member(chat_id, user.id) + client.ban_chat_member(chat_id, user.id) client.unban_chat_member(chat_id, user.id) edit( message, @@ -518,7 +518,7 @@ def zombie_accounts(client, message): for i in client.iter_chat_members(chat_id): if i.user.is_deleted: try: - client.kick_chat_member(chat_id, i.user.id) + client.ban_chat_member(chat_id, i.user.id) except UserAdminInvalid: count -= 1 users += 1 diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 0dbba7a..99c9d51 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -79,7 +79,7 @@ def gban_user(client, message): except BaseException: pass sleep(1) - send_log(get_translation('gbanLog', [user.first_name, user.id])) + send_log(get_translation('gbanLog', [user.first_name, user.id, '`'])) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) return @@ -174,7 +174,7 @@ def gban_check(client, message): try: user_id = message.from_user.id chat_id = message.chat.id - client.kick_chat_member(chat_id, user_id) + client.ban_chat_member(chat_id, user_id) except BaseException: pass @@ -229,7 +229,7 @@ def gmute_user(client, message): except BaseException: pass sleep(1) - send_log(get_translation('gmuteLog', [user.first_name, user.id])) + send_log(get_translation('gmuteLog', [user.first_name, user.id, '`'])) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) return diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 5c8a83b..3cf2e9d 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -60,9 +60,9 @@ "banAdminError": "I guess i haven't enough perm for this.", "banError": "%1Something went wrong!%1\n\n%2%3%2", "banFailUser": "Please specify a valid user!", - "banLog": "#BAN\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", + "banLog": "#BAN\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)", "banProcess": "Whacking the pest!", - "banResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4banned!%4", + "banResult": "%1[%2](tg://user?id=%3)%1 %4banned!%4", "barcodeInfo": ".barcode <text>\nUsage: Make a barcode from provided content.\nEg: .barcode https://devotag.com\n\nNote: Use .decode command to get decoded content.", "barcodeUsage": "%1Usage:%1 %2.barcode <text>%2", "bioSuccess": "Successfully changed Bio.", @@ -192,8 +192,8 @@ "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 is %2\u00bd gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 This person is %1\u00bd gay!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 I am %1\u00bd gay!", - "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2)", - "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4globally banned!%4", + "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)", + "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4", "gbannedUsers": "GBanned Users:", "geniusToken": "Please set the Genius token. Thank you!", "gitAccount": "Account Type", @@ -221,7 +221,7 @@ "gitWebsite": "Website", "globalsInfo": ".gban <username/reply>\nUsage: Bans the person in all groups you have in common with them.\n\n.ungban <username/reply>\nUsage: Removes the person from the gbanned list\n\n.listgban\nUsage: List GBanned users.\n\n.gmute <username/reply>\nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute <username/reply>\nUsage: Removes the person from the gmuted list\n\n.listgmute\nUsage: List GMuted users.", "globalsSqlLog": "Unable to run GBan and GMute command, no SQL connection found", - "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2)", + "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2) (%3%2%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4", "gmutedUsers": "GMuted Users:", "googleDesc": "No description found.", @@ -253,7 +253,7 @@ "kangstr7": "Ay look over there (\u2609\uff61\u2609)!\u2192\nWhile I kang this\u2026", "kangstr8": "Roses are red violets are blue, kanging this sticker so my pacc looks cool", "kangstr9": "Imprisoning this sticker\u2026", - "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%3%4)", + "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%3%4)", "kickProcess": "Kicking\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4", "kickmeResult": "Goodbye.. i go away \ud83e\udd20", @@ -301,7 +301,7 @@ "mirrorError": "Error: Different mirror not found for the link", "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> \u2026 <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", - "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2)\nGROUP: %3 (%4%5%4)", + "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", "muteResult": "%1[%2](tg://user?id=%3)%1 %4muted!%4", "nameOk": "Your name was succesfully changed.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index f11148e..56a7c5f 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -60,9 +60,9 @@ "banAdminError": "San\u0131r\u0131m bunun i\u00e7in yeterli yetkim olmayabilir.", "banError": "%1Bir hata olu\u015ftu!%1\n\n%2%3%2", "banFailUser": "Ge\u00e7erli bir kullan\u0131c\u0131 belirtiniz!", - "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", + "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", "banProcess": "D\u00fc\u015fman vuruldu!", - "banResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %1yasakland\u0131!%1", + "banResult": "%1[%2](tg://user?id=%3)%1 %1yasakland\u0131!%1", "barcodeInfo": ".barcode <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir barkod yap\u0131n.\n\u00d6rnek: .barcode https://devotag.com\n\nNot: \u00e7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", "barcodeUsage": "%1Kullan\u0131m:%1 %2.barcode <metin>%2", "base64Info": ".base64\nKullan\u0131m: Verilen dizenin base64 kodlamas\u0131n\u0131 bulun. \u00d6rnek .base64 en merhaba", @@ -193,7 +193,7 @@ "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 \u00bd%2 gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 Sen \u00bd%1 gaysin!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 \u00bd%1 gayim!", - "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", + "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", @@ -222,7 +222,7 @@ "gitWebsite": "Website", "globalsInfo": ".gban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.listgban\nKullan\u0131m: K\u00fcresel yasaklanan kullan\u0131c\u0131lar\u0131 listeler.\n\n.gmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.listgmute\nKullan\u0131m: K\u00fcresel susturulan kullan\u0131c\u0131lar\u0131 listeler.", "globalsSqlLog": "GBan ve GMute komutu \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", - "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2)", + "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4", "gmutedUsers": "K\u00fcresel susturulan kullan\u0131c\u0131lar:", "googleDesc": "A\u00e7\u0131klama bulunamad\u0131.", @@ -255,7 +255,7 @@ "kangstr7": "Hey \u015furaya bak. (\u2609\uff61\u2609)!\u2192\nBen bunu d\u0131zlarken\u2026", "kangstr8": "G\u00fcller k\u0131rm\u0131z\u0131 menek\u015feler mavi, bu \u00e7\u0131kartmay\u0131 paketime d\u0131zlayarak haval\u0131 olaca\u011f\u0131m\u2026", "kangstr9": "\u00c7\u0131kartma hapsediliyor\u2026", - "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", + "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4", "kickmeResult": "G\u00fcle G\u00fcle ben gidiyorum \ud83e\udd20", @@ -303,7 +303,7 @@ "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> \u2026 <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Depo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.\n\n.invitelink\nKullan\u0131m: Mevcut grubun davet ba\u011flant\u0131s\u0131 verir.", "mockUsage": "bANa bIr mETin vEr!", - "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2)\nGRUP: %3 (%4%5%4)", + "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", "muteProcess": "Sessize al\u0131n\u0131yor\u2026", "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4", "nameOk": "Ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", From 19d7b56d514d66e27727b7c953397e686503d46b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 5 Feb 2022 02:15:13 +0300 Subject: [PATCH 096/242] seden: Update pyrogram Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5875220..79d6d9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ pillow psycopg2 pybase64 pylast -pyrogram==1.3.5 +pyrogram==1.4.1 python-barcode python-dotenv qrcode From 41dc78e8f24f1b59dde10ecb80c665fd87138df0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 5 Feb 2022 02:17:34 +0300 Subject: [PATCH 097/242] seden: stickers: Adapt video stickers * We can call this feature experimental for now Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 29 +++++++++++++++++++++-------- sedenecem/core/image.py | 29 +++++++++++++++++++++++++++++ sedenecem/core/misc.py | 8 ++++++-- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index bdf0b84..1e48615 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -24,8 +24,9 @@ get_translation, reply_doc, sedenify, + sticker_resize, + video_convert, ) -from sedenecem.core import sticker_resize as resizer # ================= CONSTANT ================= DIZCILIK = [get_translation(f'kangstr{i+1}') for i in range(0, 12)] @@ -46,20 +47,26 @@ def kang(client, message): return anim = False + video = False media = None chat = 'Stickers' - if reply.photo or reply.document or reply.sticker: + if reply.photo or reply.video or reply.document or reply.sticker: edit(message, f'`{choice(DIZCILIK)}`') anim = reply.sticker and reply.sticker.is_animated - media = download_media_wc(reply, sticker_orig=anim) + video = reply.sticker and reply.sticker.is_video + media = download_media_wc(reply, sticker_orig=anim or video) else: edit(message, f'`{get_translation("stickerError")}`') return if not reply.sticker: try: - media = resizer(media) + if reply.video: + media = video_convert(media) + video = True + else: + media = sticker_resize(media) except BaseException: edit(message, f'`{get_translation("stickerError")}`') return @@ -81,7 +88,13 @@ def kang(client, message): pname = f'PNAME_{ptime}' pnick = f'PNICK_{ptime}' - name_suffix = ('_anim', ' (Animated)') if anim else ('', '') + name_suffix = ( + ('_anim', ' (Animated)') + if anim + else ('_video', ' (Video)') + if video + else ('', '') + ) TEMP_SETTINGS[pname] = ( PACKNAME.replace(' ', '') @@ -94,7 +107,7 @@ def kang(client, message): ) TEMP_SETTINGS[f'{pnick}_TEMPLATE'] = f'{kanger}\'s UserBot pack ' - limit = '50' if anim else '120' + limit = '50' if anim or video else '120' def pack_created(pname): try: @@ -106,7 +119,7 @@ def pack_created(pname): return False def create_new(conv, pack, pname, pnick): - cmd = f'/new{"animated" if anim else "pack"}' + cmd = f'/new{"animated" if anim else "video" if video else "pack"}' try: send_recv(conv, cmd) @@ -128,7 +141,7 @@ def create_new(conv, pack, pname, pnick): return send_recv(conv, emoji) send_recv(conv, '/publish') - if anim: + if anim or video: send_recv(conv, f'<{TEMP_SETTINGS[pnick]}>') send_recv(conv, '/skip') ret = send_recv(conv, TEMP_SETTINGS[pname]) diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index be1e077..eb4b994 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -8,6 +8,7 @@ # from math import floor +from subprocess import Popen from PIL import Image @@ -38,3 +39,31 @@ def sticker_resize(photo): temp = f'{get_download_dir()}/temp.png' image.save(temp, 'PNG') return temp + + +def video_convert(video): + process = Popen( + [ + 'ffmpeg', + '-i', + f'{video}', + '-vf', + 'scale=512:512:force_original_aspect_ratio=decrease', + '-c:v', + 'libvpx-vp9', + '-crf', + '30', + '-pix_fmt', + 'yuv420p', + '-ss', + '0', + '-to', + '3', + '-an', + '-y', + f'{get_download_dir()}/temp.webm', + ] + ) + _ = process.communicate() + output = f'{get_download_dir()}/temp.webm' + return output diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index aef00d0..f0404f4 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -289,7 +289,7 @@ def download_media(client, data, file_name=None, progress=None, sticker_orig=Tru elif data.video_note: file_name = f'{data.video_note.file_id}.mp4' elif data.sticker: - file_name = f'sticker.{("tgs" if sticker_orig else "json.gz") if data.sticker.is_animated else ("webp" if sticker_orig else "png")}' + file_name = f'sticker.{("tgs" if sticker_orig else "json.gz") if data.sticker.is_animated else ("webm" if data.sticker.is_video else "mp4" ("webp" if sticker_orig else "png"))}' else: return None @@ -357,6 +357,7 @@ def is_admin(message): user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) return user.status in _admin_status_list + def is_admin_myself(chat): if not 'group' in chat.type: return True @@ -381,9 +382,12 @@ def get_duration(media): return int(float(out[1])) return None + def __status_out__(cmd, encoding='utf-8'): try: - output = check_output(cmd, shell=True, text=True, stderr=STDOUT, encoding=encoding) + output = check_output( + cmd, shell=True, text=True, stderr=STDOUT, encoding=encoding + ) return (0, output) except CalledProcessError as ex: return (ex.returncode, ex.output) From a7d228b6507d679aa41956c7d8952686cde89b4c Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 12:21:31 +0300 Subject: [PATCH 098/242] add gif support for video stickers * now getsticker command supports animated & video stickers Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 1e48615..8cf6f82 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -51,10 +51,10 @@ def kang(client, message): media = None chat = 'Stickers' - if reply.photo or reply.video or reply.document or reply.sticker: + if reply.photo or reply.video or reply.animation or reply.document or reply.sticker: edit(message, f'`{choice(DIZCILIK)}`') anim = reply.sticker and reply.sticker.is_animated - video = reply.sticker and reply.sticker.is_video + video = reply.animation or reply.sticker and reply.sticker.is_video media = download_media_wc(reply, sticker_orig=anim or video) else: edit(message, f'`{get_translation("stickerError")}`') @@ -62,7 +62,7 @@ def kang(client, message): if not reply.sticker: try: - if reply.video: + if reply.video or reply.animation: media = video_convert(media) video = True else: @@ -213,23 +213,32 @@ def send_recv(conv, msg, doc=False): return conv.recv_msg() -@sedenify(pattern='^.getsticker$') +@sedenify(pattern='^.getsticker') def getsticker(message): + args = extract_args(message) reply = message.reply_to_message if not reply or not reply.sticker: edit(message, f'`{get_translation("replySticker")}`') return - photo = download_media_wc(reply, f'{get_download_dir()}/sticker.png') - image = Image.open(photo) - photo = f'{get_download_dir()}/sticker.png' - image.save(photo) + video = False + photo = False + + if reply.sticker and reply.sticker.is_animated or reply.sticker.is_video: + video = download_media_wc(reply) + else: + photo = download_media_wc(reply, f'{get_download_dir()}/sticker.png') + image = Image.open(photo) + photo = f'{get_download_dir()}/sticker.png' + image.save(photo) reply_doc( reply, - photo, + video or photo, caption=f'**Sticker ID:** `{reply.sticker.file_id}' - f'`\n**Emoji**: `{reply.sticker.emoji or get_translation("notSet")}`', + f'`\n**Emoji**: `{reply.sticker.emoji or get_translation("notSet")}`' + if args == '-v' + else '', delete_after_send=True, ) message.delete() From 4d8c61f02155fdd1c32403f3913261f74e052bd5 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 12:24:58 +0300 Subject: [PATCH 099/242] Release Seden v1.5.2 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index ef65441..d10f69e 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -123,7 +123,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.1s' +BOT_VERSION = '1.5.2' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 60041341d5a318293bafe6a31c55333018b1e2d0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 14:06:19 +0300 Subject: [PATCH 100/242] some changes on init Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 36 +++++++++++++++++++----------------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index d10f69e..36e036b 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -40,6 +40,12 @@ def reload_env(): LOGS = getLogger(__name__) +# +# Bot lang +# +# If missted, the default lang is English. +SEDEN_LANG = environ.get('SEDEN_LANG', 'en') + def get_translation(transKey, params: list = None): ret = _tr.get_translation(SEDEN_LANG, transKey) @@ -74,12 +80,6 @@ def get_translation(transKey, params: list = None): level=DEBUG if LOG_VERBOSE else INFO, ) -# -# Bot lang -# -# If missted, the default lang is English. -SEDEN_LANG = environ.get('SEDEN_LANG', 'en') - def set_local_env(key: str, value: str): return set_key(PurePath('config.env'), key, value) @@ -106,12 +106,6 @@ def set_logger(): set_logger() -# Check that the config is edited using the previously used variable. -# Basically, check for config file. -if environ.get('___________DELETE_______THIS_____LINE__________', None): - LOGS.warn(get_translation("removeFirstLine")) - quit(1) - # Telegram APP ID and HASH API_ID = environ.get('API_ID', None) if not API_ID: @@ -166,10 +160,12 @@ def set_logger(): DATABASE_URL = environ.get('DATABASE_URL', None) # SedenBot Session -SESSION = environ.get('SESSION', 'sedenuserbot') +SESSION = environ.get('SESSION', 'sedenify') # SedenBot repo url for updater -REPO_URL = environ.get('REPO_URL', 'https://github.com/TeamDerUntergang/SedenUserBot') +REPO_URL = environ.get( + 'REPO_URL', 'https://github.com/TeamDerUntergang/Telegram-SedenUserBot' +) # Heroku Credentials for updater HEROKU_KEY = environ.get('HEROKU_KEY', None) @@ -259,6 +255,15 @@ def __init__(self, session, **args): super().__init__(session, **args) self.add_handler(MessageHandler(PyroClient.store_msg, filters.incoming)) + def start(self): + super().start() + LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) + LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) + + def stop(self): + super().stop() + LOGS.info(get_translation('goodbyeMsg')) + def export_session_string(self): raise NotImplementedError @@ -302,6 +307,3 @@ def __import_modules(): __import_modules() - -LOGS.info(get_translation('runningBot', [SUPPORT_GROUP])) -LOGS.info(get_translation('sedenVersion', [BOT_VERSION])) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 3cf2e9d..20957e2 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -224,6 +224,7 @@ "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2) (%3%2%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4", "gmutedUsers": "GMuted Users:", + "goodbyeMsg": "Goodbye\u2026", "googleDesc": "No description found.", "googleInfo": ".google <query>\nUsage: Does a search on Google.", "googleLog": "Google Search query %1 was executed successfully", @@ -394,7 +395,6 @@ "rbgProcessing": "Removing background from this image..", "rbgResult": "Background removed using Remove.bg", "rbgUsage": "Reply to an image\u2026", - "removeFirstLine": "Please remove the first specified line from config.env file", "repeatInfo": ".repeat <no.> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.", "replyMessage": "Reply to a message.", "replySticker": "Reply a sticker.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 56a7c5f..9fbd0b9 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -225,6 +225,7 @@ "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4", "gmutedUsers": "K\u00fcresel susturulan kullan\u0131c\u0131lar:", + "goodbyeMsg": "G\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "googleDesc": "A\u00e7\u0131klama bulunamad\u0131.", "googleInfo": ".google <kelime>\nKullan\u0131m: H\u0131zl\u0131 bir Google aramas\u0131 yapar.", "googleLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla Google'da arat\u0131ld\u0131!", @@ -395,7 +396,6 @@ "rbgProcessing": "Bu g\u00f6r\u00fcnt\u00fcden arka plan kald\u0131r\u0131l\u0131yor..", "rbgResult": "Remove.bg kullan\u0131larak arka plan kald\u0131r\u0131ld\u0131", "rbgUsage": "Bir g\u00f6r\u00fcnt\u00fcye yan\u0131t verin\u2026", - "removeFirstLine": "L\u00fctfen ilk belirtilen sat\u0131r\u0131 config.env dosyas\u0131ndan kald\u0131r\u0131n", "replyMessage": "Bir mesaja yan\u0131t verin.", "replySticker": "L\u00fctfen bir \u00e7\u0131kartmay\u0131 al\u0131nt\u0131lay\u0131n.", "restart": "Yeniden ba\u015flat\u0131l\u0131yor\u2026", From 9c9cb0d914ebf1a33289d8df43000d60aaa9a0e4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 14:33:13 +0300 Subject: [PATCH 101/242] adapt webm files to stickers module Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 8cf6f82..17ea695 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -62,7 +62,12 @@ def kang(client, message): if not reply.sticker: try: - if reply.video or reply.animation: + if ( + reply.video + or reply.animation + or reply.document + and 'webm' in reply.document.mime_type + ): media = video_convert(media) video = True else: From 16441fc2625f1647e74f5bc1e03abd5d6004217d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 14:55:48 +0300 Subject: [PATCH 102/242] reduce bitrate and duration Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenecem/core/image.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index eb4b994..c8bfe39 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -53,12 +53,12 @@ def video_convert(video): 'libvpx-vp9', '-crf', '30', + '-b:v', + '500k', '-pix_fmt', 'yuv420p', - '-ss', - '0', - '-to', - '3', + '-t', + '2.9', '-an', '-y', f'{get_download_dir()}/temp.webm', From 252be33b7407a71bfcc557ae72c44fd5fab2c402 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 15:15:28 +0300 Subject: [PATCH 103/242] some additions again cuz i forgot Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 2 +- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 17ea695..8ad9cd7 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -66,7 +66,7 @@ def kang(client, message): reply.video or reply.animation or reply.document - and 'webm' in reply.document.mime_type + and 'webm' or 'mp4' in reply.document.mime_type ): media = video_convert(media) video = True diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 20957e2..a26b444 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -527,7 +527,7 @@ "statusWeek": "Within the last week", "stickerAdded": "%1Sticker kanged successfully!%1\nPack can be found [here](https://t.me/addstickers/%2)", "stickerError": "I can't kang that", - "stickerInfo": ".kang [Optional: <Pack Number> <Emoji>]\nUsage: Reply .kang to a sticker or an image to kang it to your sticker pack. Default emoji is \ud83e\udd24\n\n.getsticker\nUsage: Sends the replied sticker in file format.\n\n.packinfo\nUsage: Gets info about the sticker pack.", + "stickerInfo": ".kang [Optional: <Pack Number> <Emoji>]\nUsage: Reply .kang to a sticker or an image to kang it to your sticker pack. Default emoji is \ud83e\udd24\n\n.getsticker [-v (verbosity)]\nUsage: Sends the replied sticker in file format.\n\n.packinfo\nUsage: Gets info about the sticker pack.", "stickerPackFull": "Pack %1 is full.", "stickerUsage": "Give me something..", "strUsage": "GiiiiiiiB sooooooomeeeeeee teeeeeeext!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 9fbd0b9..fa87ef4 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -527,7 +527,7 @@ "statusWeek": "Bir hafta i\u00e7inde", "stickerAdded": "%1Ba\u015far\u0131yla d\u0131zland\u0131.%1\nEri\u015fmek i\u00e7in [buraya](https://t.me/addstickers/%2) dokunun.", "stickerError": "Bunu d\u0131zlayamam.", - "stickerInfo": ".d\u0131zla [\u0130ste\u011fe ba\u011fl\u0131: <Paket Numaras\u0131> <Emoji>]\nKullan\u0131m: .d\u0131zla ile bir \u00e7\u0131kartmaya ya da resme yan\u0131tlayarak kendi \u00e7\u0131kartma paketinize \u00e7\u0131kartma olarak ekleyebilirsiniz. Varsay\u0131lan emoji \ud83e\udd24 kullan\u0131l\u0131r.\n\n.getsticker\nKullan\u0131m: Yan\u0131tlanan \u00e7\u0131kartmay\u0131 dosya bi\u00e7iminde g\u00f6nderir.\n\n.packinfo\nKullan\u0131m: \u00c7\u0131kartma paketi hakk\u0131nda bilgi verir.", + "stickerInfo": ".d\u0131zla [\u0130ste\u011fe ba\u011fl\u0131: <Paket Numaras\u0131> <Emoji>]\nKullan\u0131m: .d\u0131zla ile bir \u00e7\u0131kartmaya ya da resme yan\u0131tlayarak kendi \u00e7\u0131kartma paketinize \u00e7\u0131kartma olarak ekleyebilirsiniz. Varsay\u0131lan emoji \ud83e\udd24 kullan\u0131l\u0131r.\n\n.getsticker [-v (ayr\u0131nt\u0131lar)]\nKullan\u0131m: Yan\u0131tlanan \u00e7\u0131kartmay\u0131 dosya bi\u00e7iminde g\u00f6nderir.\n\n.packinfo\nKullan\u0131m: \u00c7\u0131kartma paketi hakk\u0131nda bilgi verir.", "stickerPackFull": "%1 numaral\u0131 paket dolu.", "stickerUsage": "Bana d\u0131zlayabilece\u011fim bir \u015fey ver.", "strUsage": "Baaaaanaaaaa biiiiir meeeeetiiiiin veeeeer!", From ea47a3d0baece2305667af1108f6cfe1bd1f1859 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 15:58:49 +0300 Subject: [PATCH 104/242] some additions again cuz i forgot [2/2] Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 7 ++++--- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 8ad9cd7..df4e17f 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -66,7 +66,8 @@ def kang(client, message): reply.video or reply.animation or reply.document - and 'webm' or 'mp4' in reply.document.mime_type + and 'webm' + or 'mp4' in reply.document.mime_type ): media = video_convert(media) video = True @@ -184,8 +185,8 @@ def add_exist(conv, pack, pname, pnick): return create_new(conv, pack, pname, pnick) status = send_recv(conv, media, doc=True) - if 'Sorry' in status.text: - edit(message, f'`{get_translation("stickerError")}`') + if 'Sorry' or 'duration is too long' or 'File is too big' in status.text: + edit(message, get_translation('botError', ['`', '**', chat])) return send_recv(conv, emoji) send_recv(conv, '/done') diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index a26b444..67adcb4 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -76,6 +76,7 @@ "blacklistSqlLog": "Unable to run blacklist module, no SQL connection found", "blacklistText": "Give me a text", "blankBlacklist": "There are no blacklist in current chat.", + "botError": "%1Something went wrong! Check%1 %2@%3%2 %1bot for detailed information.%1", "botlist": "%1Bots in%1 %2%3%2%1:%1", "brainError": "%1Error!%1\n%2[%3](tg://user?id=%4)%2 %1is Seden admin..\nSo I can't do this.%1", "callInfo": ".call #note or .call $snip\nUsage: Used to get notes or global notes", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index fa87ef4..b53473c 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -77,6 +77,7 @@ "blacklistSqlLog": "Karaliste mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "blacklistText": "Bana bir metin ver", "blankBlacklist": "Karalisteye eklenmi\u015f kelime bulunamad\u0131.", + "botError": "%1Hata olu\u015ftu! Detayl\u0131 bilgi i\u00e7in%1 %2@%3%2 %1botunu kontrol edin.%1", "botlist": "%2%3%2 %1sohbetinde bulunan botlar:%1", "brainError": "%1Hata!%1\n%2[%3](tg://user?id=%4)%2 %1bir Seden yetkilisi..\nYani bunu yapamam.%1", "callInfo": ".call #note veya .call $snip\nKullan\u0131m: Notlar\u0131 veya genel notlar\u0131 \u00e7a\u011f\u0131rmak i\u00e7in kullan\u0131l\u0131r", From 7960d1003698bfc602236cff5b2b0a1ae99a9e93 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 15:59:35 +0300 Subject: [PATCH 105/242] Release Seden v1.5.3 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 36e036b..58aa193 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.2' +BOT_VERSION = '1.5.3' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 275395a958f4c2b3930b3dacc37307c6b3ceb3b7 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 6 Feb 2022 23:42:15 +0300 Subject: [PATCH 106/242] fix reported bug 'str' object is not callable Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 2 +- sedenecem/core/misc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index df4e17f..29ce576 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -185,7 +185,7 @@ def add_exist(conv, pack, pname, pnick): return create_new(conv, pack, pname, pnick) status = send_recv(conv, media, doc=True) - if 'Sorry' or 'duration is too long' or 'File is too big' in status.text: + if ('Sorry' or ('duration is too long' or 'File is too big')) in status.text: edit(message, get_translation('botError', ['`', '**', chat])) return send_recv(conv, emoji) diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index f0404f4..5c79b87 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -289,7 +289,7 @@ def download_media(client, data, file_name=None, progress=None, sticker_orig=Tru elif data.video_note: file_name = f'{data.video_note.file_id}.mp4' elif data.sticker: - file_name = f'sticker.{("tgs" if sticker_orig else "json.gz") if data.sticker.is_animated else ("webm" if data.sticker.is_video else "mp4" ("webp" if sticker_orig else "png"))}' + file_name = f'sticker.{("tgs" if sticker_orig else "json.gz") if data.sticker.is_animated else "webm" if data.sticker.is_video else "webp" if sticker_orig else "png"}' else: return None From 825f218e8cd97d002ad7ab93ad300f51128a6bfb Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sat, 5 Mar 2022 21:54:38 +0300 Subject: [PATCH 107/242] Add GDrive module Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- gdrive_auth.py | 7 + requirements.txt | 9 +- sample_config.env | 6 +- sedenbot/__init__.py | 4 + sedenbot/modules/gdrive.py | 282 +++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 9 ++ sedenecem/translator/tr.json | 10 ++ 7 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 gdrive_auth.py create mode 100644 sedenbot/modules/gdrive.py diff --git a/gdrive_auth.py b/gdrive_auth.py new file mode 100644 index 0000000..332fba2 --- /dev/null +++ b/gdrive_auth.py @@ -0,0 +1,7 @@ +from pydrive.auth import GoogleAuth + +gauth = GoogleAuth() +gauth.LocalWebserverAuth() +gauth.SaveCredentialsFile('creds.txt') + +print(f'creds.txt Dosyası oluşturuldu.Dosyayı kontrol edin.') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 79d6d9c..eddbafe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,12 @@ + bs4 cowpy emoji exifread gitpython +google-api-python-client +google-auth-httplib2 +google-auth-oauthlib googletrans==3.1.0a0 gtts heroku3 @@ -12,8 +16,9 @@ lyricsgenius pillow psycopg2 pybase64 +pydrive pylast -pyrogram==1.4.1 +pyrogram==1.4.8 python-barcode python-dotenv qrcode @@ -22,9 +27,9 @@ requests selenium spamwatch speedtest-cli +spotipy sqlalchemy==1.3.23 tgcrypto urbandict wikipedia yt-dlp -spotipy diff --git a/sample_config.env b/sample_config.env index 25e0e35..cf51d37 100644 --- a/sample_config.env +++ b/sample_config.env @@ -1,6 +1,3 @@ -# Delete this line before you do anything -___________DELETE_______THIS_____LINE__________=True - # Get these from https://my.telegram.org/ # REQUIRED API_ID='' @@ -50,6 +47,9 @@ SPOTIPY_CLIENT_ID='' # Spotify Client Secret SPOTIPY_CLIENT_SECRET='' +# Gdrive Folder ID +GDRIVE_FOLDER_ID='' + # If you need Verbosity on the Logging LOG_VERBOSE=False diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 58aa193..f608303 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -30,6 +30,7 @@ from pyrogram import Client, filters from pyrogram.handlers import MessageHandler from requests import get +from subprocess import Popen def reload_env(): @@ -133,6 +134,9 @@ def set_logger(): # SPotify Client SECRET SPOTIPY_CLIENT_SECRET = environ.get('SPOTIPY_CLIENT_SECRET') +# Gdrive Folder ID +GDRIVE_FOLDER_ID = environ.get('GDRIVE_FOLDER_ID', None) + # Change Alive Message ALIVE_MSG = environ.get('ALIVE_MSG', None) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py new file mode 100644 index 0000000..313392e --- /dev/null +++ b/sedenbot/modules/gdrive.py @@ -0,0 +1,282 @@ +from io import FileIO +from os import path, remove +from queue import Queue +from re import findall, search +from shutil import copy, copyfileobj +from time import time + +from google.oauth2.credentials import Credentials +from googleapiclient.discovery import build +from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload +from pyrogram.types import Message +from requests import get +from sedenbot import GDRIVE_FOLDER_ID, HELP, LOGS +from sedenecem.core import ( + download_media_wc, + edit, + extract_args, + get_translation, + reply_doc, + sedenify, +) + + +class Progress: + def __init__(self, msg: Message, file_name, start_time): + self.msg = msg + self.file_name = file_name + self.start_time = start_time + + def pyrogram_download(self, current, total): + percentage = float(current * 100 / total) + if (curr_time := time()) - self.start_time > 5: + self.start_time = curr_time + self.msg.edit_text( + get_translation( + 'pyrogramDown', + ['**', '`', self.file_name, f'½{round(percentage, 2)}'], + ) + ) + + +class WebHelper: + def __init__(self, message: Message): + self.message = message + + def download_link(self, queue: Queue): + while not queue.empty(): + url = queue.get() + re_url = search("^https://drive.google.com", url) + if re_url: + self.download_link_gdrive(url) + + queue.task_done() + else: + r = get(url, stream=True) + + total_size = int(r.headers.get('content-length', 0)) + downloaded_size = 0 + + if 'Content-Disposition' in r.headers.keys(): + fname = findall("filename=(.+)", r.headers["Content-Disposition"])[ + 0 + ] + else: + fname = url.split('/')[-1] + + start_time = time() + with open(f'./{fname}', 'wb') as f: + for chunk in r.iter_content(chunk_size=50 * 1024 * 1024): + f.write(chunk) + downloaded_size += int(len(chunk)) + percentage = round(float(downloaded_size * 100 / total_size), 2) + print(f'Hız {downloaded_size / total_size}\n') + if (curr_time := time()) - start_time > 5: + start_time = curr_time + edit( + self.message, + get_translation( + 'reqDown', ['**', '`', fname, f'½{percentage}'] + ), + ) + self.upload_to_gdrive(fname) + del fname + + queue.task_done() + + def download_link_gdrive(self, link): + file_id = search('d\/(.*)\/v', link).group(1) + + scopes = ['https://www.googleapis.com/auth/drive'] + creds = Credentials.from_authorized_user_file('token.json', scopes) + service = build('drive', 'v3', credentials=creds) + + file_info = ( + service.files() + .get(fileId=file_id, fields='name,size', supportsTeamDrives=True) + .execute() + ) + request = service.files().get_media(fileId=file_id, supportsTeamDrives=True) + + fh = open(f'{file_info.get("name")}', 'wb') + downloader = MediaIoBaseDownload( + fd=fh, request=request, chunksize=50 * 1024 * 1024 + ) + + done = False + while done is False: + status, done = downloader.next_chunk() + progress = edit( + get_translation( + self.message, + 'gdriveDown', + [ + '**', + '`', + file_info.get('name'), + f'½{int(status.progress() * 100)}', + ], + ) + ) + edit( + self.message, + get_translation('gdriveDownComplete', ['**', '`', file_info.get('name')]), + ) + self.upload_to_gdrive(file_info.get("name")) + + def upload_to_gdrive(self, filename): + + scopes = ['https://www.googleapis.com/auth/drive'] + creds = Credentials.from_authorized_user_file('token.json', scopes) + service = build('drive', 'v3', credentials=creds) + + file_metadata = { + 'name': filename, + 'parents': [GDRIVE_FOLDER_ID], + } + + media = MediaFileUpload(filename, resumable=True, chunksize=50 * 1024 * 1024) + + file = service.files().create( + body=file_metadata, media_body=media, fields='id', supportsTeamDrives=True + ) + response = None + start_time = time() + while response is None: + status, response = file.next_chunk() + if status: + if (curr_time := time()) - start_time > 5: + start_time = curr_time + edit( + self.message, + get_translation( + 'gdriveUp', + ['**', '`', filename, f'½{int(status.progress() * 100)}'], + ), + ) + else: + edit( + self.message, + get_translation('gdriveUpComplete', ['**', '`', filename]), + ) + remove(filename) + + +@sedenify(pattern='.gauth') +def drive_auth(message): + msg = message.reply_to_message + download_media_wc(data=msg, file_name='token.json') + copy('./downloads/token.json', '.') + edit(message, get_translation('gauthTokenSucces', ['`'])) + + +@sedenify(pattern='.gupload') +def drive_upload(message): + + if path.exists('token.json'): + pass + else: + return edit(message, get_translation('gauthTokenErr', ['`'])) + + reply = message.reply_to_message + if reply: + file_name = reply.document.file_name + + start_time = time() + progress = Progress(message, file_name, start_time) + + down_file = download_media_wc( + reply, file_name, progress=progress.pyrogram_download + ) + + scopes = ['https://www.googleapis.com/auth/drive'] + creds = Credentials.from_authorized_user_file('token.json', scopes) + service = build('drive', 'v3', credentials=creds) + + file_metadata = { + 'name': file_name, + 'parents': [GDRIVE_FOLDER_ID], + } + + media = MediaFileUpload(down_file, resumable=True, chunksize=50 * 1024 * 1024) + + file = service.files().create( + body=file_metadata, media_body=media, fields='id', supportsTeamDrives=True + ) + start_time = time() + response = None + while response is None: + status, response = file.next_chunk() + if status: + if (curr_time := time()) - start_time > 5: + start_time = curr_time + edit( + message, + get_translation( + 'gdriveUp', + ['**', '`', file_name, int(status.progress() * 100)], + ), + ) + edit(message, get_translation('gdriveUpComplete', ['**', '`', file_name])) + remove(down_file) + else: + args = extract_args(message).split(' ') + queue = Queue() + + for i in args: + queue.put(i) + + web = WebHelper(message) + web.download_link(queue) + + +@sedenify(pattern='.gdownload') +def gdownload(message): + if path.exists('token.json'): + pass + else: + return edit(message, get_translation('gauthTokenErr', ['`'])) + args = extract_args(message) + file_id = search('d\/(.*)\/v', args).group(1) + + scopes = ['https://www.googleapis.com/auth/drive'] + creds = Credentials.from_authorized_user_file('token.json', scopes) + service = build('drive', 'v3', credentials=creds) + + file_info = ( + service.files() + .get(fileId=file_id, fields='name,size', supportsTeamDrives=True) + .execute() + ) + + if int(file_info.get('size')) >= 2147483648: + return edit(message, get_translation('tgUpLimit', ['`'])) + request = service.files().get_media(fileId=file_id, supportsTeamDrives=True) + + fh = FileIO(f'./downloads/{file_info.get("name")}', 'wb') + downloader = MediaIoBaseDownload(fd=fh, request=request, chunksize=50 * 1024 * 1024) + + done = False + while done is False: + status, done = downloader.next_chunk() + progress = edit( + message, + get_translation( + 'gdriveDown', + ['**', '`', file_info.get('name'), int(status.progress() * 100)], + ), + ) + + start_time = time() + progress = Progress(message, file_info.get('name'), start_time) + + reply_doc( + message, + f'./downloads/{file_info.get("name")}', + progress=progress.pyrogram_download, + delete_orig=False, + ) + message.delete() + + +HELP.update({'gdrive': get_translation('gdriveUsage')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 67adcb4..082c7f2 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -190,12 +190,18 @@ "filterUpdated": "%2Filter%2 %1%3%1 %2updated%2", "filtersSqlLog": "Unable to run filters module, no SQL connection found", "founderResult": "%1=======================================\n\nThis bot;\nDeveloped by%1 %2[NaytSeyd](https://t.me/NightShade)%2 %1and%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2\n%1In addition\nLovingly edited by%1 %2[Sedenogen](https://t.me/CiyanogenOneTeams)%2\n\n%1=======================================%1", + "gauthTokenErr": "%1Token file is missing.%1", + "gauthTokenSucces": "%1Token file created.%1", "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 is %2\u00bd gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 This person is %1\u00bd gay!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 I am %1\u00bd gay!", "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)", "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4", "gbannedUsers": "GBanned Users:", + "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", + "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", + "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%%4%2", + "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", "geniusToken": "Please set the Genius token. Thank you!", "gitAccount": "Account Type", "gitBio": "Bio", @@ -386,6 +392,7 @@ "purgeUsage": "I need a message to start purging from.", "purgemeInfo": ".purgeme <X>\nUsage: Deletes x amount of your latest messages.", "purgemeUsage": "Purgeme failed, please specify number.", + "pyrogramDown": "%Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", "pythonVersionError": "You must have at least a Python version 3.8\nMultiple features depend on this. Bot quitting.", "quotlyInfo": ".q\nUsage: Enhance ur text to sticker.", "randomResult": "%1Query:%1\n%2%3%2\n%1Output:%1\n%2%4%2", @@ -399,6 +406,7 @@ "repeatInfo": ".repeat <no.> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.", "replyMessage": "Reply to a message.", "replySticker": "Reply a sticker.", + "reqDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", "restart": "BRB\u2026 *PornHub intro*", "restartLog": "#RESTART\nBot restarted", "reverseError": "Unsupported type", @@ -542,6 +550,7 @@ "termUsage": "Please write a command.", "testException": "This is a test, don't submit a bug log.", "testLogId": "Testing LOG_ID value\u2026", + "tgUpLimit": "%1File size bigger than 2Gb.I cant do that.%1", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", "trtError": "Invalid destination language.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index b53473c..86b6d38 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -191,12 +191,19 @@ "filterUpdated": "%2Filtre%2 %1%3%1 %2g\u00fcncellendi%2", "filtersSqlLog": "Filters mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "founderResult": "%1=======================================\n\nBu bot;%1\n%2[NaytSeyd](https://t.me/NightShade)%2 %1ve%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2 %1taraf\u0131ndan geli\u015ftirilmektedir.\nEk olarak%1\n%2[Sedenogen](https://t.me/CiyanogenOneTeams)%2 %1taraf\u0131ndan sevgi ile d\u00fczenlenmi\u015ftir.\n\n=======================================%1", + "gauthTokenErr": "%1Token dosyas\u0131 eksik.%1", + "gauthTokenSucces": "%1Token dosyas\u0131 olu\u015fturuldu.%1", "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 \u00bd%2 gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 Sen \u00bd%1 gaysin!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 \u00bd%1 gayim!", "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", + "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", + "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", + "gdriveUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi:%1 %2%4%2", + "gdriveUpComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya Y\u00fcklendi.%1", + "gdriveUsage": ".gauth <token_file>\nToken dosyas\u0131n\u0131 .gauth ile yan\u0131tlay\u0131n.Yetkilendirme i\u00e7in gereklidir.\n\n.gupload <reply_message> yada <link>\nTelegrama at\u0131lm\u0131\u015f bir dosyay\u0131 .gupload ile yan\u0131tlay\u0131n veya link belirtin.Yant\u0131lanan dosyay\u0131 veya linki google drive'a y\u00fckler.\n\n.gdownload <link>\nKi\u015fisel drive'\u0131n\u0131zdan veya google drive linkinden dosyay\u0131 indirir ve telegrama y\u00fckler.", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", "gitAccount": "Kullan\u0131c\u0131 tipi", "gitBio": "Biyografi", @@ -387,6 +394,7 @@ "purgeUsage": "Temizlemeye ba\u015flamak i\u00e7in bir mesaja ihtiyac\u0131m var.", "purgemeInfo": ".purgeme <X>\nKullan\u0131m: Hedeflenen yan\u0131ttan ba\u015flayarak t\u00fcm mesajlar\u0131 temizler..", "purgemeUsage": "Temizlik yap\u0131lamad\u0131, say\u0131 ge\u00e7ersiz.", + "pyrogramDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "pythonVersionError": "En az Python 3.8 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", "quotlyInfo": ".q\nKullan\u0131m: Metninizi \u00e7\u0131kartmaya d\u00f6n\u00fc\u015ft\u00fcr\u00fcn.", "randomResult": "%1Sorgu:%1\n%2%3%2\n%1\u00c7\u0131kt\u0131:%1\n%2%4%2", @@ -399,6 +407,7 @@ "rbgUsage": "Bir g\u00f6r\u00fcnt\u00fcye yan\u0131t verin\u2026", "replyMessage": "Bir mesaja yan\u0131t verin.", "replySticker": "L\u00fctfen bir \u00e7\u0131kartmay\u0131 al\u0131nt\u0131lay\u0131n.", + "reqDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "restart": "Yeniden ba\u015flat\u0131l\u0131yor\u2026", "restartLog": "#RESTART\nBot yeniden ba\u015flat\u0131ld\u0131.", "reverseError": "Desteklenmeyen t\u00fcr", @@ -542,6 +551,7 @@ "termUsage": "L\u00fctfen bir komut yaz\u0131n.", "testException": "Bu bir test, hata kayd\u0131 g\u00f6ndermeyin.", "testLogId": "LOG_ID de\u011feri test ediliyor\u2026", + "tgUpLimit": "%1Dosya boyutu 2gb dan fazla oldu\u011fu i\u00e7in bunu yapamam.%1", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", From 3cff9109abdc63a3a49749a24eddf9aa28d9ebf0 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sat, 5 Mar 2022 21:56:07 +0300 Subject: [PATCH 108/242] Release Seden v1.5.4 Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index f608303..5b342a2 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -118,7 +118,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.3' +BOT_VERSION = '1.5.4' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From ad69bb5386e64caa1d546a7b9ab53ba197bfec72 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sat, 5 Mar 2022 21:57:01 +0300 Subject: [PATCH 109/242] Change document mime_type Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/stickers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 29ce576..3ee6163 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -60,20 +60,19 @@ def kang(client, message): edit(message, f'`{get_translation("stickerError")}`') return - if not reply.sticker: + if not anim and reply.sticker: try: if ( reply.video or reply.animation or reply.document - and 'webm' - or 'mp4' in reply.document.mime_type + and 'video' in reply.document.mime_type ): media = video_convert(media) video = True else: media = sticker_resize(media) - except BaseException: + except BaseException as e: edit(message, f'`{get_translation("stickerError")}`') return From 2ee405704aeadd77a512d5ba5fd6a0dc0f4a2f60 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sun, 6 Mar 2022 16:32:16 +0300 Subject: [PATCH 110/242] Edit spotify help strings Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 082c7f2..d7115ab 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -522,7 +522,7 @@ "speedtestInfo": ".speedtest\nUsage: Does a speedtest and shows results.", "speedtestResultDoc": "%1SpeedTest%1 completed in %2 seconds!", "speedtestResultText": "%1SpeedTest%1 completed in %2 seconds!\nDownload: %3\nUpload: %4\nPing: %5\nISP: %6\nISP rating: %7\n%8", - "spotifyInfo": "Shows user information\n.spoti(ify) show <username>\nDownloads and seding playlist(s) as zip\n.spoti(fy) dl zip <playlisturl>\nDownloads and send playlist(s) as without zip.\n.spoti(fy) dl <playlisturl>", + "spotifyInfo": "Shows user information\n.spoti|spotify show <username>\n\nDownloads and seding playlist as zip\n.spoti|spotify dl zip <playlisturl>\n\nDownloads and send playlist as without zip.\n.spoti|spotify dl <playlisturl>", "spotifyResult": "%1Username:%1 %2%3%2\n%1Profile URL: %4\nPlaylist(s):%1\n%2%5%2\n\n%2%6%2 %1playlists found!%1", "ssInfo": ".ss <url>\nUsage: Takes a screenshot of a website and sends the screenshot.\nExample of a valid URL: https://devotag.com", "ssResult": "Generating screenshot of the page\u2026\nHeight of page = %1px\nWidth of page = %2px\nWaiting %3 for the page to load.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 86b6d38..142ba1c 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -523,7 +523,7 @@ "speedtestInfo": ".speedtest\nKullan\u0131m: Bir speedtest uygular ve sonucu g\u00f6sterir.", "speedtestResultDoc": "%1SpeedTest%1 %2 saniyede tamamland\u0131!", "speedtestResultText": "%1SpeedTest%1 %2 saniyede tamamland\u0131!\n\u0130ndirme: %3\nY\u00fckleme: %4\nGecikme: %5\nISS: %6\nISS puan\u0131: %7\n%8", - "spotifyInfo": "Kullan\u0131c\u0131 bilgilerini g\u00f6sterir\n.spoti(ify) show <username>\nPlaylist(leri) indirir ve zipliyip g\u00f6nderir.\n.spoti(fy) dl zip <playlisturl>\nPlaylist(leri) indirir ve ziplemeden g\u00f6nderir.\n.spoti(fy) dl <playlisturl>", + "spotifyInfo": "Kullan\u0131c\u0131 bilgilerini g\u00f6sterir\n.spoti|spotify show <username>\n\nPlaylist indirir ve zipliyip g\u00f6nderir.\n.spoti|spotify dl zip <playlisturl>\n\nPlaylist indirir ve ziplemeden g\u00f6nderir.\n.spoti|spotify dl <playlisturl>", "spotifyResult": "%1Kullan\u0131c\u0131 Ad\u0131:%1 %2%3%2\n%1Profil Ba\u011flant\u0131s\u0131: %4\n\u00c7alma Listeleri:%1\n%2%5%2\n\n%1Toplamda%1 %2%6%2 %1adet \u00e7alma listesi bulundu!%1", "ssInfo": ".ss <link>\nKullan\u0131m: Belirtilen web sitesinden bir ekran g\u00f6r\u00fcnt\u00fcs\u00fc al\u0131r ve g\u00f6nderir.\nGe\u00e7erli bir site ba\u011flant\u0131s\u0131 \u00f6rne\u011fi: https://devotag.com", "ssResult": "Sayfan\u0131n ekran g\u00f6r\u00fcnt\u00fcs\u00fc olu\u015fturuluyor\u2026\nSayfan\u0131n y\u00fcksekli\u011fi: %1 piksel\nSayfan\u0131n geni\u015fli\u011fi: %2 piksel\nSayfan\u0131n y\u00fcklenmesi i\u00e7in %3 saniye beklendi.", From fc5700881d85712ff65febf8649decfa85f53bf4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 12 Mar 2022 17:49:27 +0300 Subject: [PATCH 111/242] PyroClient: Remove some variables --- sedenbot/__init__.py | 6 +----- session.py | 22 ++++++---------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 5b342a2..d129b9f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -275,11 +275,7 @@ def export_session_string(self): app = PyroClient( SESSION, api_id=API_ID, - api_hash=API_HASH, - app_version='SedenEcem', - device_model='Firefox 91.0.2', - system_version=f'v{BOT_VERSION}', -) + api_hash=API_HASH) # delete these variables to add some security diff --git a/session.py b/session.py index ae2950f..f9f0a4b 100644 --- a/session.py +++ b/session.py @@ -20,7 +20,7 @@ API_ID = '' API_HASH = '' - while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 7: + while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 8: API_ID = input('API ID: ') API_ID = int(API_ID) @@ -29,14 +29,9 @@ API_HASH = input('API HASH: ') app = Client( - 'sedenuserbot', + 'sedenify', api_id=API_ID, - api_hash=API_HASH, - app_version='SedenEcem', - device_model='Firefox 91.0.2', - system_version='Session', - lang_code='en', - ) + api_hash=API_HASH) with app: self = app.get_me() @@ -64,7 +59,7 @@ API_ID = '' API_HASH = '' - while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 7: + while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 8: API_ID = input('API ID: ') API_ID = int(API_ID) @@ -73,14 +68,9 @@ API_HASH = input('API HASH: ') app = Client( - 'sedenuserbot', + 'sedenify', api_id=API_ID, - api_hash=API_HASH, - app_version='SedenEcem', - device_model='Firefox 91.0.2', - system_version='Session', - lang_code='tr', - ) + api_hash=API_HASH) with app: self = app.get_me() From 4c74840893d04c71cff2f088592d2294e2724420 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 12 Mar 2022 18:07:46 +0300 Subject: [PATCH 112/242] Change kick_member to ban_member Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/globals.py | 2 +- sedenbot/modules/spamwatch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 99c9d51..97dc048 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -75,7 +75,7 @@ def gban_user(client, message): try: common_chats = client.get_common_chats(user.id) for i in common_chats: - i.kick_member(user.id) + i.ban_member(user.id) except BaseException: pass sleep(1) diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index 3b09f00..18183b5 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -38,7 +38,7 @@ def spamwatch_action(client, message): else: myself = message.chat.get_member('me') if myself.can_restrict_members: - message.chat.kick_member(uid) + message.chat.ban_member(uid) reply(message, text) else: return From 9716ebff9b4d668e864b7d51db2f877f03f754ba Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 12 Mar 2022 18:19:22 +0300 Subject: [PATCH 113/242] SpeedTest: use reply_img instead of reply_doc Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/speedtest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index d87097c..6b7c49e 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -10,7 +10,7 @@ from datetime import datetime from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, reply_doc, sedenify +from sedenecem.core import edit, extract_args, get_translation, reply_img, sedenify from speedtest import Speedtest @@ -57,10 +57,11 @@ def speed_test(message): ), ) else: - reply_doc( + reply_img( message, speedtest_image, caption=get_translation('speedtestResultDoc', ['**', ms]), + delete_file=True, delete_orig=True, ) except Exception as exc: From fd0743d4dbac7134d15279fb2c72869f0247e90e Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 12 Mar 2022 18:20:20 +0300 Subject: [PATCH 114/242] Release Seden v1.5.5 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index d129b9f..1c8403f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -118,7 +118,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.4' +BOT_VERSION = '1.5.5' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From ba57619b7340d7636409ddf7b1eb7f2f1a0d2840 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sat, 12 Mar 2022 22:17:51 +0300 Subject: [PATCH 115/242] Fix translate string,remove unused import Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/__init__.py | 1 - sedenbot/modules/gdrive.py | 2 +- sedenbot/modules/seden.py | 2 +- sedenecem/translator/en.json | 5 +++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 1c8403f..fc6f5a9 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -30,7 +30,6 @@ from pyrogram import Client, filters from pyrogram.handlers import MessageHandler from requests import get -from subprocess import Popen def reload_env(): diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 313392e..1ec63c6 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -2,7 +2,7 @@ from os import path, remove from queue import Queue from re import findall, search -from shutil import copy, copyfileobj +from shutil import copy from time import time from google.oauth2.credentials import Credentials diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index df00152..27adf96 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -13,7 +13,7 @@ from sedenecem.core import edit, extract_args, get_translation, reply, sedenify -@sedenify(pattern='^.seden') +@sedenify(pattern='^.(seden|help)') def seden(message): seden = extract_args(message).lower() cmds = OrderedDict(sorted(HELP.items())) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index d7115ab..d8d7093 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -202,6 +202,7 @@ "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%%4%2", "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", + "gdriveUsage": ".gauth <token_file>\nReply with token file.It needed for authentication.\n\n.gupload <reply_message> or <link>\nReply files or paste link for uploading to google drive.\n\n.gdownload <link>\nPaste link for uploading files to telegram.", "geniusToken": "Please set the Genius token. Thank you!", "gitAccount": "Account Type", "gitBio": "Bio", @@ -307,7 +308,7 @@ "mediaInvalid": "Invalid media.", "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your bot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", "mirrorError": "Error: Different mirror not found for the link", - "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat/'s ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> \u2026 <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", + "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat's ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> \u2026 <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)", "muteProcess": "Gets a tape!", @@ -543,7 +544,7 @@ "sudoCheck": "This person is Seden admin!", "supportResult": "You can reach our support group [here](http://t.me/%1).", "syntaxError": "Syntax error.", - "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound\u2026 but you don/'t.", + "systemInfo": ".alive\nUsage: Type .alive to see wether your Seden Bot is working or not.\n\n.ping\nUsage: Shows how long it takes to ping your bot.\n\n.echo\nUsage: Repeats text you type.\n\n.botver\nUsage: Shows Seden UserBot version.\n\n.dc\nUsage: Shows nearest data center to your server.\n\n.neofetch\nUsage: Displays system information using Neofetch command.\n\n.eval 2 + 3\nUsage: Evalute mini expressions.\n\n.term echo Hi Seden!\nUsage: Run bash commands and scripts on your server.\n\n.restart\nUsage: Restarts the bot.\n\n.shutdown\nUsage: Sometimes you need to shut down your bot. Sometimes you just hope to hear Windows XP shutdown sound\u2026 but you don't.", "termHelp": "You can look at example by typing .seden system for help.", "termLog": "Terminal command %1 executed successfully", "termNoResult": "No result", From fed2a2f48bf6a9a9cb6c3aafac95fc67e5dec8f6 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sun, 13 Mar 2022 00:08:10 +0300 Subject: [PATCH 116/242] Add translator string,if condition for gauth Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/gdrive.py | 9 ++++++--- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 1ec63c6..fc51760 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -165,9 +165,12 @@ def upload_to_gdrive(self, filename): @sedenify(pattern='.gauth') def drive_auth(message): msg = message.reply_to_message - download_media_wc(data=msg, file_name='token.json') - copy('./downloads/token.json', '.') - edit(message, get_translation('gauthTokenSucces', ['`'])) + if msg and msg.document: + download_media_wc(data=msg, file_name='token.json') + copy('./downloads/token.json', '.') + edit(message, get_translation('gauthTokenSucces', ['`'])) + else: + edit(message, get_translation('gdriveTokenErr', ['`'])) @sedenify(pattern='.gupload') diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index d8d7093..202860a 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -200,6 +200,7 @@ "gbannedUsers": "GBanned Users:", "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", + "gdriveTokenErr": "%1Reply a token file%1", "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%%4%2", "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", "gdriveUsage": ".gauth <token_file>\nReply with token file.It needed for authentication.\n\n.gupload <reply_message> or <link>\nReply files or paste link for uploading to google drive.\n\n.gdownload <link>\nPaste link for uploading files to telegram.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 142ba1c..3727b65 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -201,6 +201,7 @@ "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", + "gdriveTokenErr": "%1Sadece token Dosyas\u0131n\u0131 yan\u0131tlay\u0131n.%1", "gdriveUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi:%1 %2%4%2", "gdriveUpComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya Y\u00fcklendi.%1", "gdriveUsage": ".gauth <token_file>\nToken dosyas\u0131n\u0131 .gauth ile yan\u0131tlay\u0131n.Yetkilendirme i\u00e7in gereklidir.\n\n.gupload <reply_message> yada <link>\nTelegrama at\u0131lm\u0131\u015f bir dosyay\u0131 .gupload ile yan\u0131tlay\u0131n veya link belirtin.Yant\u0131lanan dosyay\u0131 veya linki google drive'a y\u00fckler.\n\n.gdownload <link>\nKi\u015fisel drive'\u0131n\u0131zdan veya google drive linkinden dosyay\u0131 indirir ve telegrama y\u00fckler.", From 8e94652500b3055264e4a2e996e1c9cc5a3ccfb1 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Tue, 15 Mar 2022 18:45:15 +0300 Subject: [PATCH 117/242] Scrapers: Fix urbandictionary (ud) command Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- requirements.txt | 2 +- sedenbot/modules/scrapers.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index eddbafe..21aad9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,6 +30,6 @@ speedtest-cli spotipy sqlalchemy==1.3.23 tgcrypto -urbandict +urbandic wikipedia yt-dlp diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 0465b0b..64900fc 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -35,7 +35,7 @@ sedenify, send_log, ) -from urbandict import define +from urbandic import search from wikipedia import set_lang, summary from wikipedia.exceptions import DisambiguationError, PageError @@ -326,14 +326,17 @@ def urbandictionary(message): edit(message, f'`{get_translation("wrongCommand")}`') return edit(message, f'`{get_translation("processing")}`') + mean = [] + example = [] try: - define(query) + for i in search(query, 5): + mean.append(i.definition + "\n") + example.append(i.example + "\n") except HTTPError: edit(message, get_translation('udResult', ['**', query])) return - mean = define(query) - deflen = sum(len(i) for i in mean[0]['def']) - exalen = sum(len(i) for i in mean[0]['example']) + deflen = sum(len(i) for i in mean) + exalen = sum(len(i) for i in example) meanlen = deflen + exalen if int(meanlen) >= 0: if int(meanlen) >= 4096: @@ -343,10 +346,10 @@ def urbandictionary(message): 'Query: ' + query + '\n\nMeaning: ' - + mean[0]['def'] + + "".join(mean) + '\n\n' + 'Örnek: \n' - + mean[0]['example'] + + "".join(example) ) file.close() reply_doc( @@ -361,7 +364,8 @@ def urbandictionary(message): edit( message, get_translation( - 'sedenQueryUd', ['**', '`', query, mean[0]['def'], mean[0]['example']] + 'sedenQueryUd', + ['**', '`', query, "".join(choice(mean)), "".join(choice(example))], ), ) else: From bcbc0aa6c56bd4d036deeab88c718186ee066c65 Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Tue, 15 Mar 2022 17:48:06 +0300 Subject: [PATCH 118/242] Add imei checker module --- sedenbot/modules/scrapers.py | 33 ++++++++++++++++++++++++++++++++- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 64900fc..ede6ba5 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -22,7 +22,8 @@ from gtts import gTTS from gtts.lang import tts_langs from pyrogram.types import InputMediaPhoto -from requests import get +from requests import get, post +from simplejson import JSONDecodeError from sedenbot import HELP, SEDEN_LANG from sedenecem.core import ( edit, @@ -527,7 +528,37 @@ def doviz(message): edit(message, out) +@sedenify(pattern='^.imeicheck') +def imeichecker(message): + argv = extract_args(message) + imei = argv.split(' ', 1)[0] + edit(message, f'`{get_translation("processing")}`') + print(len(imei)) + if len(imei) != 15: + edit(message, f'`{get_translation("wrongCommand")}`') + return + try: + while True: + response = post(f"https://m.turkiye.gov.tr/api2.php?p=imei-sorgulama&txtImei={imei}").json() + if not response['data']['asyncFinished']: + continue + result = response['data'] + break + reply_text = f"<strong>Sorgu Tarihi</strong> <code>{result['sorguTarihi']}</code>\n\ +<strong>IMEI:</strong> <code>{result['imei'][:-5] + 5*'*'}</code>\n\ +<strong>Durum:</strong> <code>{result['durum']}</code>\n\ +<strong>Cihaz:</strong> <code>{result['markaModel']}</code>\n\n\ +<strong>Powered by </strong><a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden</a>♥" + edit(message, reply_text, parse='HTML', preview=False) + except Exception as e: + raise e + + + + + HELP.update({'img': get_translation('imgInfo')}) +HELP.update({'imeicheck': get_translation('imeiInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) HELP.update({'goolag': get_translation('googleInfo')}) HELP.update({'duckduckgo': get_translation('ddgoInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 202860a..e3657e3 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -246,6 +246,7 @@ "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", + "imeiInfo": ".imeicheck <imeinumber>\nUsage: Check if imei is registered and get device information about model, vendor, etc.", "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 3727b65..20b1e2c 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -248,6 +248,7 @@ "herokuInfo": ".kota\nKullan\u0131m: Heroku kaynak kullan\u0131m\u0131n\u0131z\u0131 g\u00f6sterir.\n\n.drestart\nKullan\u0131m: Heroku dynosunu yeniden ba\u015flat\u0131r.\n\n.logs\nKullan\u0131m: Heroku uygulama g\u00fcnl\u00fc\u011f\u00fc g\u00f6nderir.", "herokuQuotaInHM": "%1s %2d", "herokuQuotaInfo": "%2Heroku Kalan Kota%2\n\n%2Toplam:%2 %1%3%1\n%2Kullan\u0131lan:%2 %1%4 (\u00bd%5)%1\n%2Kalan:%2 %1%6 (\u00bd%7)%1\n%2Uygulama kullan\u0131m\u0131:%2 %1%8 (\u00bd%9)%1", + "imeiInfo": ".imeicheck <imeinumber>\nKullan\u0131m: Imei numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda b\u0131lg\u0131 verir", "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", From 11924fa41d59db0f2dc0156ddf45b37bf3ca9092 Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Tue, 15 Mar 2022 18:14:34 +0300 Subject: [PATCH 119/242] Remove unused import --- sedenbot/modules/scrapers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index ede6ba5..cc90607 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -23,7 +23,6 @@ from gtts.lang import tts_langs from pyrogram.types import InputMediaPhoto from requests import get, post -from simplejson import JSONDecodeError from sedenbot import HELP, SEDEN_LANG from sedenecem.core import ( edit, From 911739b0137bd2dfbdce35c72d8934baa57e14a9 Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Tue, 15 Mar 2022 23:58:51 +0300 Subject: [PATCH 120/242] Add kargotakip module --- sedenbot/modules/kargotakip.py | 81 ++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 3 files changed, 83 insertions(+) create mode 100644 sedenbot/modules/kargotakip.py diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py new file mode 100644 index 0000000..6df267e --- /dev/null +++ b/sedenbot/modules/kargotakip.py @@ -0,0 +1,81 @@ +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from ast import arg +from requests import get, post +from json import JSONDecodeError +from sedenbot import HELP, SEDEN_LANG +from sedenecem.core import ( + edit, + extract_args, + get_translation, + sedenify, +) + + + + +def getSession(): + headers = { + 'accept': 'application/json', + 'accept-encoding': 'gzip', + 'accept-language': 'tr', + 'host': 'customerappapi.yurticikargo.com', + 'user-agent': 'okhttp/4.4.0', + 'yk-app-token': 'Android407', + 'yk-mobile-device-id': 'd2926855-ed39-4f05-afcf-a9f350829427', + 'yk-mobile-os': 'Android',} + + response = post('https://customerappapi.yurticikargo.com/KOPSRestServices/rest/customermobile/auth/session', headers=headers) + return response.json()['sessionToken'] + +def getKargoEntity(kargoId): + try: + headers = { + 'accept': 'application/json', + 'accept-encoding': 'gzip', + 'authorization': getSession(), + 'user-agent': 'okhttp/4.4.0', + 'yk-app-token': 'Android407', + 'yk-mobile-device-id': 'd2926855-ed39-4f05-afcf-a9f350829427', + 'yk-mobile-os': 'Android', + 'accept-language': 'tr', + } + response = get(f'https://customerappapi.yurticikargo.com/KOPSRestServices/rest/customermobile/shipments/tracking/{kargoId}/detail', headers=headers).json() + return response + except JSONDecodeError: + return None + + +@sedenify(pattern='^.yurtici') +def yurticiKargo(message): + edit(message, f"`{get_translation('processing')}`") + argv = extract_args(message) + takipNo = argv.split(' ', 1)[0] + if not takipNo or len(takipNo) != 12: + edit(message, f"`{get_translation('wrongCommand')}`") + return + dateParse = lambda raw_date: "{}.{}.{}".format(*[raw_date[:4], raw_date[4:6], raw_date[6:8]]) + kargo_data = getKargoEntity(takipNo) + text = f""" +<strong>Firma:</strong> <code>Yurtici Kargo</code> +<strong>Durum:</strong> <code>{kargo_data['shipmentStatus'].title()}</code> +<strong>Gönderen:</strong> <code>{kargo_data['sender'].replace("******************", "")}</code> +<strong>Alıcı:</strong> <code>{kargo_data['receiver'].replace("******************", "")}</code> +<strong>Tahmini Teslim:</strong> <code>{dateParse(kargo_data['estimatedDeliveryDate'])}</code> +<strong>Çıkış Tarihi:</strong> <code>{dateParse(kargo_data['shipmentDate'])}</code> +<strong>Gönderi Kodu:</strong> <code>{kargo_data['id']}</code> +<strong>Teslim Birimi:</strong> <code>{kargo_data['deliveryUnitName']}</code> +""" + edit(message, text, parse='HTML') + + + + +HELP.update({'kargotakip': get_translation("shippingTrack")}) \ No newline at end of file diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e3657e3..ad07763 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -498,6 +498,7 @@ "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", + "shippingTrack": ".yurtici <trackNo>\nUsage: Gives yurtici shipping information", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 20b1e2c..04948a0 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -498,6 +498,7 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", + "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini gösterir", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", From 78fea09abec92ccd4b8f51f792f728ce1d2dfc67 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Wed, 16 Mar 2022 01:33:22 +0300 Subject: [PATCH 121/242] Scrapers: check expression exist --- sedenbot/modules/scrapers.py | 11 ++++++----- sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index cc90607..8448e7d 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -329,9 +329,11 @@ def urbandictionary(message): mean = [] example = [] try: - for i in search(query, 5): + for i in search(query, 1): mean.append(i.definition + "\n") example.append(i.example + "\n") + except TypeError: + return edit(message, f'`{get_translation("udNotFound")}`') except HTTPError: edit(message, get_translation('udResult', ['**', query])) return @@ -538,7 +540,9 @@ def imeichecker(message): return try: while True: - response = post(f"https://m.turkiye.gov.tr/api2.php?p=imei-sorgulama&txtImei={imei}").json() + response = post( + f"https://m.turkiye.gov.tr/api2.php?p=imei-sorgulama&txtImei={imei}" + ).json() if not response['data']['asyncFinished']: continue result = response['data'] @@ -553,9 +557,6 @@ def imeichecker(message): raise e - - - HELP.update({'img': get_translation('imgInfo')}) HELP.update({'imeicheck': get_translation('imeiInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index ad07763..cbe66bd 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -571,6 +571,7 @@ "twrpUsage": "Usage: .twrp <codename> eg: .twrp raphael", "udInfo": ".ud <query>\nDoes a search on Urban Dictionary.", "udNoResult": "%1No result for%1 %2", + "udNotFound": "Expression not found.", "udResult": "Sorry, No results were found for %1%2%1", "unbanProcess": "Unbanning\u2026", "unbanResult": "%1[%2](tg://user?id=%3)%1 %4unbanned!%4", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 04948a0..13fa921 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -498,7 +498,7 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", - "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini gösterir", + "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini g\u00f6sterir", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", @@ -571,6 +571,7 @@ "twrpUsage": "Kullan\u0131m: .twrp <kod ad\u0131> \u00d6rnek: .twrp raphael", "udInfo": ".ud <terim>\nKullan\u0131m: Urban Dictionary aramas\u0131 yapman\u0131n kolay yolu.", "udNoResult": "%2 %1i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131%1", + "udNotFound": "\u0130fade bulunamad\u0131.", "udResult": "\u00dczg\u00fcn\u00fcm, %1%2%1 i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131.", "unbanProcess": "Yasak kald\u0131r\u0131l\u0131yor\u2026", "unbanResult": "%1[%2](tg://user?id=%3)%1 %4i\u00e7in yasaklama ba\u015far\u0131yla kald\u0131r\u0131ld\u0131!%4", From 68d63b7a53d79a3b7b9ba863e9047cc6b343b6a8 Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Wed, 16 Mar 2022 15:03:59 +0300 Subject: [PATCH 122/242] Cargo tracking module has been updated, other companies have been added. --- sedenbot/modules/kargotakip.py | 118 +++++++++++++++++++-------------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 71 insertions(+), 51 deletions(-) diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index 6df267e..338b9ba 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -6,76 +6,96 @@ # # All rights reserved. See COPYING, AUTHORS. # - -from ast import arg -from requests import get, post +from requests import get from json import JSONDecodeError from sedenbot import HELP, SEDEN_LANG from sedenecem.core import ( edit, - extract_args, get_translation, sedenify, ) +def parseShipEntity(jsonEntity: dict) -> str: + if SEDEN_LANG == 'en': + text = f""" +<strong>Company:</strong> <code>{jsonEntity['data']['company'].title()}</code> +<strong>Track ID:</strong> <code>{jsonEntity['data']['tracking_no']}</code> +<strong>Status:</strong> <code>{jsonEntity['data']['status']}</code> +<strong>Sender:</strong> <code>{jsonEntity['data']['sender_name']}</code> +<strong>Receiver:</strong> <code>{jsonEntity['data']['receiver_name']}</code> +<strong>Sender Unit:</strong> <code>{jsonEntity['data']['sender_unit']}</code> +<strong>Receiver Unit:</strong> <code>{jsonEntity['data']['receiver_unit']}</code> +<strong>Sended Date:</strong> <code>{jsonEntity['data']['sended_date']}</code> +<strong>Delivered Date:</strong> <code>{jsonEntity['data']['delivered_date']}</code> + +<strong><u>Last movement</u></strong> +""" + movements = "" + for movement in jsonEntity['data']['movements'][-1:]: + movements += f"<code>Unit: {movement['unit']}\nStatus: {movement['status']}\nDate: {movement['date']}\nTime: {movement['time']}\nAction: {movement['action']}</code>\n\n" + + if SEDEN_LANG == 'tr': + text = f""" +<strong>Firma:</strong> <code>{jsonEntity['data']['company'].title()}</code> +<strong>Takip No:</strong> <code>{jsonEntity['data']['tracking_no']}</code> +<strong>Durum:</strong> <code>{jsonEntity['data']['status']}</code> +<strong>Gönderici:</strong> <code>{jsonEntity['data']['sender_name']}</code> +<strong>Alıcı:</strong> <code>{jsonEntity['data']['receiver_name']}</code> +<strong>Gönderim yeri:</strong> <code>{jsonEntity['data']['sender_unit']}</code> +<strong>Alım yeri:</strong> <code>{jsonEntity['data']['receiver_unit']}</code> +<strong>Gönderi tarihi:</strong> <code>{jsonEntity['data']['sended_date']}</code> +<strong>Teslim tarihi:</strong> <code>{jsonEntity['data']['delivered_date']}</code> -def getSession(): - headers = { - 'accept': 'application/json', - 'accept-encoding': 'gzip', - 'accept-language': 'tr', - 'host': 'customerappapi.yurticikargo.com', - 'user-agent': 'okhttp/4.4.0', - 'yk-app-token': 'Android407', - 'yk-mobile-device-id': 'd2926855-ed39-4f05-afcf-a9f350829427', - 'yk-mobile-os': 'Android',} +<strong><u>Son hareket</u></strong> + +""" + movements = "" + for movement in jsonEntity['data']['movements'][-1:]: + movements += f"<code>Yer: {movement['unit']}\nDurum: {movement['status']}\nTarih: {movement['date']}\nZaman: {movement['time']}\nİşlem: {movement['action']}</code>\n\n" + + text += movements + return text - response = post('https://customerappapi.yurticikargo.com/KOPSRestServices/rest/customermobile/auth/session', headers=headers) - return response.json()['sessionToken'] -def getKargoEntity(kargoId): +def getShipEntity(company: str, trackId: int or str) -> dict or None: + headers: dict = { + 'platform': 'Android', + 'public': 'lfJGmU9XpGZcMwyLtZBk', + 'secret': 'sMPMnQuc51nmcBbaeOK1', + 'unique': 'afb612018716663e', + } + response = get(f"https://tapi.kolibu.com/{company}/{trackId}", headers=headers) try: - headers = { - 'accept': 'application/json', - 'accept-encoding': 'gzip', - 'authorization': getSession(), - 'user-agent': 'okhttp/4.4.0', - 'yk-app-token': 'Android407', - 'yk-mobile-device-id': 'd2926855-ed39-4f05-afcf-a9f350829427', - 'yk-mobile-os': 'Android', - 'accept-language': 'tr', - } - response = get(f'https://customerappapi.yurticikargo.com/KOPSRestServices/rest/customermobile/shipments/tracking/{kargoId}/detail', headers=headers).json() - return response + return response.json() if response.json()['success'] else None except JSONDecodeError: return None - -@sedenify(pattern='^.yurtici') -def yurticiKargo(message): +@sedenify(pattern='^.(yurtici|aras|ptt)') +def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") - argv = extract_args(message) - takipNo = argv.split(' ', 1)[0] - if not takipNo or len(takipNo) != 12: + argv = message.text.split(' ') + if len(argv) > 2: edit(message, f"`{get_translation('wrongCommand')}`") return - dateParse = lambda raw_date: "{}.{}.{}".format(*[raw_date[:4], raw_date[4:6], raw_date[6:8]]) - kargo_data = getKargoEntity(takipNo) - text = f""" -<strong>Firma:</strong> <code>Yurtici Kargo</code> -<strong>Durum:</strong> <code>{kargo_data['shipmentStatus'].title()}</code> -<strong>Gönderen:</strong> <code>{kargo_data['sender'].replace("******************", "")}</code> -<strong>Alıcı:</strong> <code>{kargo_data['receiver'].replace("******************", "")}</code> -<strong>Tahmini Teslim:</strong> <code>{dateParse(kargo_data['estimatedDeliveryDate'])}</code> -<strong>Çıkış Tarihi:</strong> <code>{dateParse(kargo_data['shipmentDate'])}</code> -<strong>Gönderi Kodu:</strong> <code>{kargo_data['id']}</code> -<strong>Teslim Birimi:</strong> <code>{kargo_data['deliveryUnitName']}</code> -""" - edit(message, text, parse='HTML') + comp, trackId = argv + if not trackId: + edit(message, f"`{get_translation('wrongCommand')}`") + return + if comp == '.yurtici': + kargo_data = getShipEntity(company="yurtici", trackId=trackId) + if comp == '.aras': + kargo_data = getShipEntity(company="aras", trackId=trackId) + if comp == '.ptt': + kargo_data = getShipEntity(company="ptt", trackId=trackId) + if kargo_data: + text = parseShipEntity(kargo_data) + edit(message, text, parse='HTML') + return + edit(message, '`Tracking information not found!`' if SEDEN_LANG == 'en' else '`Takip bilgisi bulunamadı!`') -HELP.update({'kargotakip': get_translation("shippingTrack")}) \ No newline at end of file +HELP.update({'shippingtrack': get_translation("shippingTrack")}) \ No newline at end of file diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index cbe66bd..82ffe69 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -498,7 +498,7 @@ "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", - "shippingTrack": ".yurtici <trackNo>\nUsage: Gives yurtici shipping information", + "shippingTrack": ".yurtici <trackNo>\nUsage: Shows yurtici shipping information.\n\n.aras <trackNo>\nUsage: Shows aras shipping information.\n\n.ptt <trackNo>\nUsage: Shows ptt shipping information.\n\n", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 13fa921..22a00b0 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -498,7 +498,7 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", - "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini g\u00f6sterir", + "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini g\u00f6sterir.\n\n.aras <takipNo>\nKullan\u0131m: Aras kargo bilgilerini g\u00f6sterir.\n\n.ptt <takipNo>\nKullan\u0131m: PTT kargo bilgilerini g\u00f6sterir.\n\n", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", From 0d3ad5576ed3e98e50622fb53b3c247d67ce7eed Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 16 Mar 2022 23:52:49 +0300 Subject: [PATCH 123/242] Corrections * Lint fixes * Translation fixes Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/kargotakip.py | 85 ++++++++++++++++------------------ sedenecem/translator/en.json | 5 +- sedenecem/translator/tr.json | 5 +- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index 338b9ba..ceaa33f 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -6,55 +6,51 @@ # # All rights reserved. See COPYING, AUTHORS. # -from requests import get + from json import JSONDecodeError -from sedenbot import HELP, SEDEN_LANG -from sedenecem.core import ( - edit, - get_translation, - sedenify, -) + +from requests import get +from sedenbot import HELP +from sedenecem.core import edit, get_translation, sedenify def parseShipEntity(jsonEntity: dict) -> str: - if SEDEN_LANG == 'en': - text = f""" -<strong>Company:</strong> <code>{jsonEntity['data']['company'].title()}</code> -<strong>Track ID:</strong> <code>{jsonEntity['data']['tracking_no']}</code> -<strong>Status:</strong> <code>{jsonEntity['data']['status']}</code> -<strong>Sender:</strong> <code>{jsonEntity['data']['sender_name']}</code> -<strong>Receiver:</strong> <code>{jsonEntity['data']['receiver_name']}</code> -<strong>Sender Unit:</strong> <code>{jsonEntity['data']['sender_unit']}</code> -<strong>Receiver Unit:</strong> <code>{jsonEntity['data']['receiver_unit']}</code> -<strong>Sended Date:</strong> <code>{jsonEntity['data']['sended_date']}</code> -<strong>Delivered Date:</strong> <code>{jsonEntity['data']['delivered_date']}</code> - -<strong><u>Last movement</u></strong> + text = get_translation( + 'shippingResult', + [ + '<b>', + '</b>', + '<code>', + '</code>', + jsonEntity['data']['company'].title(), + jsonEntity['data']['tracking_no'], + jsonEntity['data']['status'], + jsonEntity['data']['sender_name'], + jsonEntity['data']['receiver_name'], + jsonEntity['data']['sender_unit'], + jsonEntity['data']['receiver_unit'], + jsonEntity['data']['sended_date'], + jsonEntity['data']['delivered_date'] or get_translation('notFound'), + '<u>', + '</u>', + ], + ) -""" movements = "" for movement in jsonEntity['data']['movements'][-1:]: - movements += f"<code>Unit: {movement['unit']}\nStatus: {movement['status']}\nDate: {movement['date']}\nTime: {movement['time']}\nAction: {movement['action']}</code>\n\n" - - if SEDEN_LANG == 'tr': - text = f""" -<strong>Firma:</strong> <code>{jsonEntity['data']['company'].title()}</code> -<strong>Takip No:</strong> <code>{jsonEntity['data']['tracking_no']}</code> -<strong>Durum:</strong> <code>{jsonEntity['data']['status']}</code> -<strong>Gönderici:</strong> <code>{jsonEntity['data']['sender_name']}</code> -<strong>Alıcı:</strong> <code>{jsonEntity['data']['receiver_name']}</code> -<strong>Gönderim yeri:</strong> <code>{jsonEntity['data']['sender_unit']}</code> -<strong>Alım yeri:</strong> <code>{jsonEntity['data']['receiver_unit']}</code> -<strong>Gönderi tarihi:</strong> <code>{jsonEntity['data']['sended_date']}</code> -<strong>Teslim tarihi:</strong> <code>{jsonEntity['data']['delivered_date']}</code> - -<strong><u>Son hareket</u></strong> + movements += get_translation( + 'shippingMovements', + [ + '<code>', + '</code>', + movement['unit'], + movement['status'], + movement['date'], + movement['time'], + movement['action'], + ], + ) -""" - movements = "" - for movement in jsonEntity['data']['movements'][-1:]: - movements += f"<code>Yer: {movement['unit']}\nDurum: {movement['status']}\nTarih: {movement['date']}\nZaman: {movement['time']}\nİşlem: {movement['action']}</code>\n\n" - text += movements return text @@ -72,6 +68,7 @@ def getShipEntity(company: str, trackId: int or str) -> dict or None: except JSONDecodeError: return None + @sedenify(pattern='^.(yurtici|aras|ptt)') def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") @@ -93,9 +90,7 @@ def shippingTrack(message): text = parseShipEntity(kargo_data) edit(message, text, parse='HTML') return - edit(message, '`Tracking information not found!`' if SEDEN_LANG == 'en' else '`Takip bilgisi bulunamadı!`') - - + edit(message, f"`{get_translation('shippingNoResult')}`") -HELP.update({'shippingtrack': get_translation("shippingTrack")}) \ No newline at end of file +HELP.update({'shippingtrack': get_translation("shippingTrack")}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 82ffe69..2976fd2 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -246,7 +246,7 @@ "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", - "imeiInfo": ".imeicheck <imeinumber>\nUsage: Check if imei is registered and get device information about model, vendor, etc.", + "imeiInfo": ".imeicheck <imeinumber>\nUsage: Check if IMEI is registered and get device information about model, vendor, etc.", "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", @@ -498,6 +498,9 @@ "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", + "shippingMovements": "\n\n%1Unit: %3\nStatus: %4\nDate: %5\nTime: %6\nAction: %7%2", + "shippingNoResult": "Tracking information not found!", + "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4\n\n%1%14Last movement%15%2", "shippingTrack": ".yurtici <trackNo>\nUsage: Shows yurtici shipping information.\n\n.aras <trackNo>\nUsage: Shows aras shipping information.\n\n.ptt <trackNo>\nUsage: Shows ptt shipping information.\n\n", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 22a00b0..20effe9 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -248,7 +248,7 @@ "herokuInfo": ".kota\nKullan\u0131m: Heroku kaynak kullan\u0131m\u0131n\u0131z\u0131 g\u00f6sterir.\n\n.drestart\nKullan\u0131m: Heroku dynosunu yeniden ba\u015flat\u0131r.\n\n.logs\nKullan\u0131m: Heroku uygulama g\u00fcnl\u00fc\u011f\u00fc g\u00f6nderir.", "herokuQuotaInHM": "%1s %2d", "herokuQuotaInfo": "%2Heroku Kalan Kota%2\n\n%2Toplam:%2 %1%3%1\n%2Kullan\u0131lan:%2 %1%4 (\u00bd%5)%1\n%2Kalan:%2 %1%6 (\u00bd%7)%1\n%2Uygulama kullan\u0131m\u0131:%2 %1%8 (\u00bd%9)%1", - "imeiInfo": ".imeicheck <imeinumber>\nKullan\u0131m: Imei numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda b\u0131lg\u0131 verir", + "imeiInfo": ".imeicheck <imeinumber>\nKullan\u0131m: IMEI numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda bilgi verir", "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", @@ -498,6 +498,9 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", + "shippingMovements": "\n\n%1Yer: %3\nDurum: %4\nTarih: %5\nZaman: %6\n\u0130\u015flem: %7%2", + "shippingNoResult": "Takip bilgisi bulunamad\u0131!", + "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4\n\n%1%14Son hareket%15%2", "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini g\u00f6sterir.\n\n.aras <takipNo>\nKullan\u0131m: Aras kargo bilgilerini g\u00f6sterir.\n\n.ptt <takipNo>\nKullan\u0131m: PTT kargo bilgilerini g\u00f6sterir.\n\n", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", From c742d2d8303797f5bb088504e8a86a59526cb9a8 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 16 Mar 2022 23:55:10 +0300 Subject: [PATCH 124/242] Release Seden v1.5.6 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index fc6f5a9..75631ac 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.5' +BOT_VERSION = '1.5.6' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 694cc96d5d7378b4497d8239eafae399a94dc52b Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Thu, 17 Mar 2022 19:04:20 +0300 Subject: [PATCH 125/242] New companies have been added (kargotakip), the index error in the last movements has been fixed (kargotakip), the help text has been updated. (kargotakip) --- sedenbot/modules/kargotakip.py | 27 +++++++++++++++++---------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index ceaa33f..dcd57e7 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -35,19 +35,16 @@ def parseShipEntity(jsonEntity: dict) -> str: '</u>', ], ) - - movements = "" - for movement in jsonEntity['data']['movements'][-1:]: - movements += get_translation( + movements = get_translation( 'shippingMovements', [ '<code>', '</code>', - movement['unit'], - movement['status'], - movement['date'], - movement['time'], - movement['action'], + jsonEntity['data']['movements'][0]['unit'], + jsonEntity['data']['movements'][0]['status'], + jsonEntity['data']['movements'][0]['date'], + jsonEntity['data']['movements'][0]['time'], + jsonEntity['data']['movements'][0]['action'], ], ) @@ -69,7 +66,7 @@ def getShipEntity(company: str, trackId: int or str) -> dict or None: return None -@sedenify(pattern='^.(yurtici|aras|ptt)') +@sedenify(pattern='^.(yurtici|aras|ptt|mng|ups|surat|trendyol|hepsiburada)') def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") argv = message.text.split(' ') @@ -86,6 +83,16 @@ def shippingTrack(message): kargo_data = getShipEntity(company="aras", trackId=trackId) if comp == '.ptt': kargo_data = getShipEntity(company="ptt", trackId=trackId) + if comp == '.mng': + kargo_data = getShipEntity(company="mng", trackId=trackId) + if comp == '.ups': + kargo_data = getShipEntity(company="ups", trackId=trackId) + if comp == '.surat': + kargo_data = getShipEntity(company="surat", trackId=trackId) + if comp == '.trendyol': + kargo_data = getShipEntity(company="trendyolexpress", trackId=trackId) + if comp == '.hepsiburada': + kargo_data = getShipEntity(company="hepsijet", trackId=trackId) if kargo_data: text = parseShipEntity(kargo_data) edit(message, text, parse='HTML') diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 2976fd2..f5c0195 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -501,7 +501,7 @@ "shippingMovements": "\n\n%1Unit: %3\nStatus: %4\nDate: %5\nTime: %6\nAction: %7%2", "shippingNoResult": "Tracking information not found!", "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4\n\n%1%14Last movement%15%2", - "shippingTrack": ".yurtici <trackNo>\nUsage: Shows yurtici shipping information.\n\n.aras <trackNo>\nUsage: Shows aras shipping information.\n\n.ptt <trackNo>\nUsage: Shows ptt shipping information.\n\n", + "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 20effe9..2659a41 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -501,7 +501,7 @@ "shippingMovements": "\n\n%1Yer: %3\nDurum: %4\nTarih: %5\nZaman: %6\n\u0130\u015flem: %7%2", "shippingNoResult": "Takip bilgisi bulunamad\u0131!", "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4\n\n%1%14Son hareket%15%2", - "shippingTrack": ".yurtici <takipNo>\nKullan\u0131m: Yurti\u00e7i kargo bilgilerini g\u00f6sterir.\n\n.aras <takipNo>\nKullan\u0131m: Aras kargo bilgilerini g\u00f6sterir.\n\n.ptt <takipNo>\nKullan\u0131m: PTT kargo bilgilerini g\u00f6sterir.\n\n", + "shippingTrack": ".<firma> <takipNo>\n\nKullan\u0131m: Kargo bilgilerini g\u00f6sterir.\n\n\u00d6rnek: `.ups 1234567890`\n\nDesteklenen firmalar:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", From 498238d825a6e2c89054b45881b4c6b41132af1b Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Thu, 24 Mar 2022 15:44:44 +0300 Subject: [PATCH 126/242] Add paste module Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/paste.py | 38 ++++++++++++++++++++++++++++++++++++ sedenecem/translator/en.json | 2 ++ sedenecem/translator/tr.json | 2 ++ 3 files changed, 42 insertions(+) create mode 100644 sedenbot/modules/paste.py diff --git a/sedenbot/modules/paste.py b/sedenbot/modules/paste.py new file mode 100644 index 0000000..6842025 --- /dev/null +++ b/sedenbot/modules/paste.py @@ -0,0 +1,38 @@ +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# +from requests import post +from sedenecem.core import edit, get_translation, sedenify + + +@sedenify(pattern="^.paste") +def paste(message): + + text = message.text.strip() + if len(text) <= 6: + return edit(message, f'`{get_translation("pasteErr")}`') + + paste = text.replace('.paste ', '').encode('utf-8') + + url = "https://www.toptal.com/developers/hastebin/documents" + + try: + r = post( + url=url, + data=paste, + ) + except BaseException as e: + edit(message, f'`{get_translation("pasteConErr")}`') + + try: + resp = r.json() + key = resp['key'] + new_url = f"https://www.toptal.com/developers/hastebin/{key}" + return edit(message, new_url, False) + except BaseException as e: + raise e diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index f5c0195..8c2cb9f 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -354,6 +354,8 @@ "packinfoError": "I can't fetch info from nothing, can I ?!", "packinfoProcess": "Fetching details of the sticker pack, please wait..", "packinfoResult": "%1Sticker Title:%1 %2%3%2\n%1Sticker Short Name:%1 %2%4%2\n%1Official:%1 %2%5%2\n%1Archived:%1 %2%6%2\n%1Animated:%1 %2%7%2\n%1Stickers In Pack:%1 %2%8%2\n%1Emojis In Pack:%1\n%9", + "pasteConnErr": "Could not connect to server.", + "pasteErr": "Enter text for paste.", "phhError": "%1Couldn't find anything for%1 %2%3%2", "picspamLog": "#PICSPAM\nPicSpam was executed successfully", "pinLog": "#PIN\nGROUP: %1 (%2%3%2)", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2659a41..ee03810 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -355,6 +355,8 @@ "packinfoErrer2": "Paket detaylar\u0131n\u0131 g\u00f6rmek i\u00e7in bir \u00e7\u0131kartmay\u0131 yan\u0131tlay\u0131n", "packinfoError": "Hi\u00e7likten bir bilgi \u00e7ekemem, sence yapabilir miyim?!", "packinfoResult": "%1\u00c7\u0131kartma paketi ba\u015fl\u0131\u011f\u0131:%1 %2%3%2\n%1\u00c7\u0131kartma paketi k\u0131sa ad\u0131:%1 %2%4%2\n%1Resmi paket mi:%1 %2%5%2\n%1Ar\u015fivlenmi\u015f mi:%1 %2%6%2\n%1Animasyonlu mu:%1 %2%7%2\n%1Pakettki \u00e7\u0131kartma say\u0131s\u0131:%1 %2%8%2\n%1Paketteki emoji say\u0131s\u0131:%1\n%9", + "pasteConnErr": "Sunucuya ba\u011flan\u0131lamad\u0131", + "pasteErr": "Yap\u0131\u015ft\u0131rmak i\u00e7in bir \u015fey yaz\u0131n.", "phhError": "%2%3%2 %1aramas\u0131 i\u00e7in bir ROM bulunamad\u0131%1", "picspamLog": "#PICSPAM\nPicSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "pinLog": "#PIN\nGRUP: %1 (%2%3%2)", From bdbce5767cdd192579f810409a4fadb7ae413707 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 29 Mar 2022 23:16:17 +0300 Subject: [PATCH 127/242] Update sqlalchemy, pyrogram Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 7 +++---- sample_config.env | 2 +- sedenbot/__init__.py | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 21aad9c..9b0ad0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ - bs4 cowpy emoji @@ -18,7 +17,7 @@ psycopg2 pybase64 pydrive pylast -pyrogram==1.4.8 +pyrogram==1.4.12 python-barcode python-dotenv qrcode @@ -28,8 +27,8 @@ selenium spamwatch speedtest-cli spotipy -sqlalchemy==1.3.23 +sqlalchemy tgcrypto urbandic wikipedia -yt-dlp +yt-dlp \ No newline at end of file diff --git a/sample_config.env b/sample_config.env index cf51d37..cbd4d66 100644 --- a/sample_config.env +++ b/sample_config.env @@ -8,7 +8,7 @@ API_HASH='' SESSION='' # Your Database URL -# Example: 'postgres://sedenbot:sedenbot@localhost:5432/sedenbot' +# Example: 'postgresql://sedenbot:sedenbot@localhost:5432/sedenbot' DATABASE_URL='' # Chat ID for Log Group diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 75631ac..372ee9b 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -161,6 +161,8 @@ def set_logger(): # SQL Database URL DATABASE_URL = environ.get('DATABASE_URL', None) +if DATABASE_URL.startswith('postgres://'): + DATABASE_URL = DATABASE_URL.replace('postgres://', 'postgresql://', 1) # SedenBot Session SESSION = environ.get('SESSION', 'sedenify') From 045dc036e72d9a370360ec5e02f412400a99b5de Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 29 Mar 2022 23:17:07 +0300 Subject: [PATCH 128/242] Release Seden v1.5.7 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 372ee9b..2d6eca1 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.6' +BOT_VERSION = '1.5.7' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From ce2c94a0e6cabb190a04eba9f1b74672499a5237 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 2 Apr 2022 20:20:40 +0300 Subject: [PATCH 129/242] stickers: Bug fix Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/stickers.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 3ee6163..ea715c5 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -54,25 +54,26 @@ def kang(client, message): if reply.photo or reply.video or reply.animation or reply.document or reply.sticker: edit(message, f'`{choice(DIZCILIK)}`') anim = reply.sticker and reply.sticker.is_animated - video = reply.animation or reply.sticker and reply.sticker.is_video + video = ( + reply.animation or reply.video or reply.sticker and reply.sticker.is_video + ) media = download_media_wc(reply, sticker_orig=anim or video) else: edit(message, f'`{get_translation("stickerError")}`') return - if not anim and reply.sticker: + if not reply.sticker: try: if ( reply.video or reply.animation - or reply.document - and 'video' in reply.document.mime_type + or (reply.document and 'video' in reply.document.mime_type) ): media = video_convert(media) video = True else: media = sticker_resize(media) - except BaseException as e: + except BaseException: edit(message, f'`{get_translation("stickerError")}`') return From f0c9a8f38a24d1e2eba827c8d8d633297e33407d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sat, 2 Apr 2022 20:21:28 +0300 Subject: [PATCH 130/242] Release Seden v1.5.8 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 2d6eca1..74c4221 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.7' +BOT_VERSION = '1.5.8' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 36a8f4e7f31d38a19b64b7b9e7df71f208d1a4af Mon Sep 17 00:00:00 2001 From: fatih <fatih@192.168.1.37> Date: Mon, 4 Apr 2022 22:43:15 +0300 Subject: [PATCH 131/242] memes: add mem command --- sedenbot/modules/memes.py | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index c329703..b57b15d 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -18,10 +18,13 @@ from requests import get from sedenbot import HELP from sedenecem.core import ( + download_media_wc, edit, extract_args, + get_download_dir, get_translation, parse_cmd, + reply_img, reply_sticker, sedenify, ) @@ -891,4 +894,84 @@ def payf(message): edit(message, pay) +@sedenify(pattern='.mem') +def meme_maker(message): + args = extract_args(message).upper().split(',') + reply = message.reply_to_message + font = 'sedenecem/fonts/impact.ttf' + if len(args) == 2: + top, bottom = args[0], args[1] + else: + bottom = args[0 if args[1] == '' else 1] + + if ( + reply + and reply.media + and ( + reply.photo + or (reply.sticker and not reply.sticker.is_animated) + or (reply.document and 'image' in reply.document.mime_type) + ) + ): + media = download_media_wc(reply, f'{get_download_dir()}/image.jpg') + image = Image.open(media) + draw = ImageDraw.Draw(image) + width, height = image.size + estimated_font_size = find_font_size(''.join(args), font, image, 1) + + text_font = ImageFont.truetype(font, estimated_font_size) + text_per_line = width // text_font.size + top_text = wrap(top, width=(text_per_line + 5)) + bottom_text = wrap(bottom, width=(text_per_line + 5)) + y = 10 + for text in top_text: + text_width, text_height = text_font.getsize(text) + x = (width - text_width) / 2 + draw.text( + (x, y), + text, + fill='white', + font=text_font, + stroke_width=3, + stroke_fill='black', + ) + y += text_height + y = height - text_height * len(bottom_text) - 15 + for text in bottom_text: + text_width, text_height = text_font.getsize(text) + x = (width - text_width) / 2 + draw.text( + (x, y), + text, + fill='white', + font=text_font, + stroke_width=3, + stroke_fill='black', + ) + y += text_height + + image.convert('RGB').save(media, 'JPEG') + reply_img(reply or message, media, delete_file=True) + message.delete() + else: + edit(message, 'Lütfen bir resim yanıtlayın.') + + +def get_text_size(text, image, font): + im = Image.new('RGB', (image.width, image.height)) + draw = ImageDraw.Draw(im) + return draw.textsize(text, font) + + +def find_font_size(text, font, image, target_width_ratio): + # https://stackoverflow.com/a/66091387 + tested_font_size = 100 + tested_font = ImageFont.truetype(font, tested_font_size) + observed_width, observed_height = get_text_size(text, image, tested_font) + estimated_font_size = ( + tested_font_size / (observed_width / image.width) * target_width_ratio + ) + return round(estimated_font_size) + + HELP.update({'memes': get_translation('memesInfo')}) From 937c39d7300c9af2ac996f33ebc22bacaa216ec3 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Mon, 4 Apr 2022 22:44:10 +0300 Subject: [PATCH 132/242] ezanvakti: add ramazan command --- sedenbot/modules/ezanvakti.py | 124 +++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index ec9868c..8f32692 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -7,32 +7,23 @@ # All rights reserved. See COPYING, AUTHORS. # +from datetime import datetime from functools import reduce from re import DOTALL, sub from bs4 import BeautifulSoup from requests import get from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, sedenify +from sedenecem.core import edit, extract_args, sedenify -@sedenify(pattern='^.ezanvakti') +@sedenify(pattern='^.ezan(|vakti)') def ezanvakti(message): konum = extract_args(message).lower() if len(konum) < 1: - edit(message, f'`{get_translation("ezanvaktiKonum")}`') - return - - try: - knum = find_loc(konum) - if knum < 0: - raise ValueError - request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') - result = BeautifulSoup(request.text, 'html.parser') - except BaseException: - edit(message, f'`{get_translation("ezanvaktiErrorInfo", [konum])}`') - return + return edit(message, '`Lütfen komutun yanına bir şehir belirtin.`') + result = get_result(konum) res1 = result.body.findAll('div', {'class': ['body-content']}) res1 = res1[0].findAll('script') res1 = sub( @@ -46,9 +37,68 @@ def get_val(st): res2 = get_val(res1[1]) res3 = get_val(res1[2]) - vakitler = get_translation( - 'ezanvaktiShowInfo', - ['**', '`', res2[1], res3[0], res3[1], res3[2], res3[3], res3[4], res3[5]], + vakitler = ( + '**Diyanet Namaz Vakitleri**\n\n' + + f'📍 **Yer:** `{res2[1]}`\n\n' + + f'🏙 **İmsak:** `{res3[0]}`\n' + + f'🌅 **Güneş:** `{res3[1]}`\n' + + f'🌇 **Öğle:** `{res3[2]}`\n' + + f'🌆 **İkindi:** `{res3[3]}`\n' + + f'🌃 **Akşam:** `{res3[4]}`\n' + + f'🌌 **Yatsı:** `{res3[5]}`' + ) + + edit(message, vakitler) + + +@sedenify(pattern='^.ramazan') +def ramazan(message): + konum = extract_args(message).lower() + if len(konum) < 1: + return edit(message, '`Lütfen komutun yanına bir şehir belirtin.`') + + result = get_result(konum) + saat_imsak = ( + result.find('div', {'data-vakit-name': 'imsak'}) + .find('div', {'class': 'tpt-time'}) + .get_text() + ) + + saat_aksam = ( + result.find('div', {'data-vakit-name': 'aksam'}) + .find('div', {'class': 'tpt-time'}) + .get_text() + ) + + saat_yatsi = ( + result.find('div', {'data-vakit-name': 'yatsi'}) + .find('div', {'class': 'tpt-time'}) + .get_text() + ) + + sehir = ( + result.find('table', {'class': 'table vakit-table'}) + .find('caption') + .get_text() + .split(' ')[0] + ) + + saatler_yarin = result.find_all('tr')[2] + aksam_yarin = saatler_yarin.get_text().split('\n')[6] + yatsi_yarin = saatler_yarin.get_text().split('\n')[7] + imsak_yarin = saatler_yarin.get_text().split('\n')[2] + + iftar_saat = calculate_time(saat_aksam, aksam_yarin) + yatsi_saat = calculate_time(saat_yatsi, yatsi_yarin) + imsak_saat = calculate_time(saat_imsak, imsak_yarin) + + vakitler = ( + '**Diyanet Ramazan Vakitleri**\n\n' + + f'📍 **Yer:** `{sehir}`\n\n' + + f'🏙 **Sahur:** `{saat_imsak} ({imsak_saat[0]}s {imsak_saat[1]}dk kaldı)`\n' + + f'🌃 **İftar:** `{saat_aksam} ({iftar_saat[0]}s {iftar_saat[1]}dk kaldı)`\n' + + f'🌌 **Teravih:** `{saat_yatsi} ({yatsi_saat[0]}s {yatsi_saat[1]}dk kaldı)`\n\n' + + '**Hayırlı Ramazanlar**' ) edit(message, vakitler) @@ -72,6 +122,41 @@ def find_loc(konum): return -1 +def get_result(konum): + try: + knum = find_loc(konum) + if knum < 0: + raise ValueError + request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') + return BeautifulSoup(request.content, 'lxml') + except TypeError: + return f'`{konum} için bir bilgi bulunamadı.`' + + +def calculate_time(saat, yarin_saat): + now = datetime.now().timestamp() + now_t = datetime.fromtimestamp(now).strftime('%d.%m.%Y') + iftar_vakit = datetime.strptime(f'{saat} {now_t}', '%H:%M %d.%m.%Y').timestamp() + if iftar_vakit < now: + temp = now + 24 * 60 * 60 + tomorrow = datetime.fromtimestamp(temp).strftime('%d.%m.%Y') + yarin_iftar = datetime.strptime( + f'{yarin_saat} {tomorrow}', '%H:%M %d.%m.%Y' + ).timestamp() + sonuc = yarin_iftar - now + kalan_saat, kalan_dk = timedelta(sonuc) + else: + sonuc = iftar_vakit - now + kalan_saat, kalan_dk = timedelta(sonuc) + return kalan_saat, kalan_dk + + +def timedelta(time): + saat = int(time / 3600) + dakika = int((time % 3600) / 60) + return saat, dakika + + sehirler = [ '01 Adana 9146', '02 Adiyaman 9158', @@ -160,6 +245,9 @@ def find_loc(konum): { "ezanvakti": ".ezanvakti <şehir> \ \nKullanım: Belirtilen şehir için namaz vakitlerini gösterir. \ - \nÖrnek: .ezanvakti istanbul veya .ezanvakti 34" + \nÖrnek: .ezanvakti istanbul \ + \n.ramazan <şehir> \ + \nKullanım: Belirtilen şehir için ramazan vakitlerini gösterir. \ + \nÖrnek: .ramazan istanbul" } ) From f72794d3c118abfdab6b409a200e5c980febc927 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Mon, 4 Apr 2022 22:44:37 +0300 Subject: [PATCH 133/242] Release Seden v1.5.9 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 74c4221..b20321a 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.8' +BOT_VERSION = '1.5.9' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From b1a903f23c1d778c2d3c4f72a173ed769e5de58e Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Mon, 4 Apr 2022 23:22:26 +0300 Subject: [PATCH 134/242] Add Impact font --- sedenecem/fonts/impact.ttf | Bin 0 -> 136076 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sedenecem/fonts/impact.ttf diff --git a/sedenecem/fonts/impact.ttf b/sedenecem/fonts/impact.ttf new file mode 100644 index 0000000000000000000000000000000000000000..114e6c1968b663086409144e50e7a592bff1d6c2 GIT binary patch literal 136076 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%0Aus|o$luyX&D&6 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 VQlm?y$QD;A35 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 literal 0 HcmV?d00001 From 325bae7cb94522e390e5173da59d45d963a3cd48 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 5 Apr 2022 13:10:21 +0300 Subject: [PATCH 135/242] ezanvakti: change bs4 parser Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/ezanvakti.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 8f32692..cd56fca 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -128,7 +128,7 @@ def get_result(konum): if knum < 0: raise ValueError request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') - return BeautifulSoup(request.content, 'lxml') + return BeautifulSoup(request.content, 'html.parser') except TypeError: return f'`{konum} için bir bilgi bulunamadı.`' From 56d09d3541e782f1267e55f0bdeada8b9b784bc6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 5 Apr 2022 13:11:46 +0300 Subject: [PATCH 136/242] Release Seden v1.6.0 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index b20321a..3965628 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.5.9' +BOT_VERSION = '1.6.0' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 650561228924f93ad243f9261b0d79545bc7b387 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 12 Apr 2022 23:51:49 +0300 Subject: [PATCH 137/242] update pyrogram Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9b0ad0d..d068ab4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2 pybase64 pydrive pylast -pyrogram==1.4.12 +pyrogram==1.4.15 python-barcode python-dotenv qrcode @@ -31,4 +31,4 @@ sqlalchemy tgcrypto urbandic wikipedia -yt-dlp \ No newline at end of file +yt-dlp From 89b1cd66ef95d1b82ccc53abee1c17c3bb28a2dd Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 13 Apr 2022 00:26:14 +0300 Subject: [PATCH 138/242] autopp: change font Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/autopp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index dbba4fc..66175d6 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -47,7 +47,7 @@ def autopic(client, message): edit(message, f'`{get_translation("autoppProcess")}`') - FONT_FILE = 'sedenecem/fonts/GoogleSans.ttf' + FONT_FILE = 'sedenecem/fonts/OpenSans.ttf' downloaded_file_name = 'oldpp.png' photo = 'newpp.png' From bd1274d3b387db2f0bb19e472014bd720f1da4c3 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 13 Apr 2022 00:26:48 +0300 Subject: [PATCH 139/242] Release Seden v1.6.1 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 3965628..1c24bf0 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.0' +BOT_VERSION = '1.6.1' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 001cbf3df2f80a16f50cd8b3d5cb22c17c7f9633 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 01:48:43 +0300 Subject: [PATCH 140/242] treewide updates for pyrogram 2.0 * I think this is a bit experimental so let us know any issues you might encounter. Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 4 +-- sedenbot/__init__.py | 5 +-- sedenbot/modules/afk.py | 3 +- sedenbot/modules/android.py | 16 +++++----- sedenbot/modules/ban.py | 57 ++++++++++++++++++----------------- sedenbot/modules/chat.py | 2 +- sedenbot/modules/filters.py | 2 +- sedenbot/modules/globals.py | 34 ++++++++++----------- sedenbot/modules/info.py | 19 ++++++------ sedenbot/modules/misc.py | 4 +-- sedenbot/modules/notes.py | 2 +- sedenbot/modules/pmpermit.py | 32 ++++++++------------ sedenbot/modules/profile.py | 23 +++++++------- sedenbot/modules/purge.py | 13 ++++---- sedenbot/modules/snips.py | 2 +- sedenbot/modules/spammer.py | 2 +- sedenbot/modules/spamwatch.py | 13 ++++++-- sedenbot/modules/stickers.py | 4 +-- sedenbot/modules/system.py | 3 +- sedenecem/core/conv.py | 2 +- sedenecem/core/misc.py | 23 ++++++++++---- sedenecem/core/replier.py | 13 ++++---- sedenecem/core/sedenify.py | 20 ++++++------ sedenecem/core/webdriver.py | 1 - 24 files changed, 157 insertions(+), 142 deletions(-) diff --git a/requirements.txt b/requirements.txt index d068ab4..42c15e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,11 +13,11 @@ humanize image_to_ascii lyricsgenius pillow -psycopg2 +psycopg2-binary pybase64 pydrive pylast -pyrogram==1.4.15 +pyrogram==2.0.13 python-barcode python-dotenv qrcode diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 1c24bf0..2675c63 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -273,10 +273,7 @@ def export_session_string(self): raise NotImplementedError -app = PyroClient( - SESSION, - api_id=API_ID, - api_hash=API_HASH) +app = PyroClient('sedenify', api_id=API_ID, api_hash=API_HASH, session_string=SESSION) # delete these variables to add some security diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 6326690..1991418 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -32,7 +32,6 @@ @sedenify( incoming=True, outgoing=False, - disable_edited=True, private=False, bot=False, disable_notify=True, @@ -155,7 +154,7 @@ def afk_on_pm(message): raise ContinuePropagation -@sedenify(pattern=r'^.afk') +@sedenify(pattern='^.afk') def set_afk(message): args = extract_args(message) if len(args) > 0: diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 6ff4d4c..f54491b 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -68,7 +68,7 @@ def phh(message): edit(message, releases, preview=False) -@sedenify(pattern=r'^.device') +@sedenify(pattern='^.device') def device(message): textx = message.reply_to_message codename = extract_args(message) @@ -98,7 +98,7 @@ def device(message): edit(message, reply) -@sedenify(pattern=r'^.codename') +@sedenify(pattern='^.codename') def codename(message): textx = message.reply_to_message arr = extract_args(message) @@ -145,7 +145,7 @@ def codename(message): edit(message, reply) -@sedenify(pattern=r'^.twrp') +@sedenify(pattern='^.twrp') def twrp(message): textx = message.reply_to_message device = extract_args(message) @@ -173,7 +173,7 @@ def twrp(message): edit(message, reply) -@sedenify(pattern=r'^.o(range|)f(ox|rp)') +@sedenify(pattern='^.o(range|)f(ox|rp)') def ofox(message): if len(args := extract_args(message)) < 1: edit(message, f'`{get_translation("ofrpUsage")}`') @@ -206,7 +206,7 @@ def ofox(message): edit(message, f'**OrangeFox Recovery ({args}):**\n{out}') -@sedenify(pattern=r'^.specs') +@sedenify(pattern='^.specs') def specs(message): args = extract_args(message) if len(args) < 1: @@ -301,7 +301,7 @@ def get_spec(query, key='data-spec', cls='td'): def find_device(query, proxy): - """@frknkrc44, GSMArena üzerinden cihaz bulma""" + """Find device from GSMArena by @frknkrc44""" raw_query = query.lower() def replace_query(query): @@ -318,12 +318,12 @@ def replace_query(query): ) soup = BeautifulSoup(req.text, features='html.parser') - if 'Too' in soup.find('title').text: # GSMArena geçici ban atarsa + if 'Too' in soup.find('title').text: # if temp banned by GSMArena return None res = soup.findAll('div', {'class': ['makers']}) - if not res or len(res) < 1: # hiçbir cihaz bulunamazsa + if not res or len(res) < 1: # if no device found return None res = res[0].findAll('li') diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 6925978..4e7f805 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -11,13 +11,14 @@ from time import sleep from PIL import Image +from pyrogram import enums from pyrogram.errors import ( ImageProcessFailed, MessageTooLong, PhotoCropSizeSmall, UserAdminInvalid, ) -from pyrogram.types import ChatPermissions +from pyrogram.types import ChatPermissions, ChatPrivileges from sedenbot import BRAIN, HELP from sedenecem.core import ( download_media_wc, @@ -182,7 +183,7 @@ def mute_user(client, message): try: from sedenecem.sql import mute_sql as sql except BaseException: - edit(message,f'`{get_translation("nonSqlMode")}`') + edit(message, f'`{get_translation("nonSqlMode")}`') return args = extract_args(message) @@ -240,7 +241,7 @@ def unmute_user(client, message): try: from sedenecem.sql import mute_sql as sql except BaseException: - edit(message,f'`{get_translation("nonSqlMode")}`') + edit(message, f'`{get_translation("nonSqlMode")}`') return args = extract_args(message) @@ -312,12 +313,14 @@ def promote_user(client, message): client.promote_chat_member( chat_id, user.id, - can_change_info=True, - can_delete_messages=True, - can_restrict_members=True, - can_invite_users=True, - can_pin_messages=True, - can_promote_members=True, + privileges=ChatPrivileges( + can_change_info=True, + can_delete_messages=True, + can_restrict_members=True, + can_invite_users=True, + can_pin_messages=True, + can_promote_members=True, + ), ) if rank is not None: if len(rank) > 16: @@ -369,12 +372,14 @@ def demote_user(client, message): client.promote_chat_member( chat_id, user.id, - can_change_info=False, - can_delete_messages=False, - can_restrict_members=False, - can_invite_users=False, - can_pin_messages=False, - can_promote_members=False, + privileges=ChatPrivileges( + can_change_info=False, + can_delete_messages=False, + can_restrict_members=False, + can_invite_users=False, + can_pin_messages=False, + can_promote_members=False, + ), ) edit( message, @@ -394,14 +399,13 @@ def pin_message(client, message): try: chat_id = message.chat.id - message_id = reply.message_id - client.pin_chat_message(chat_id, message_id) + message_id = reply.id + client.pin_chat_message(chat_id, message_id, disable_notification=True) edit(message, f'`{get_translation("pinResult")}`') sleep(1) send_log(get_translation('pinLog', [message.chat.title, '`', chat_id])) except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + return edit(message, get_translation('banError', ['`', '**', e])) @sedenify(pattern='^.unpin', compat=False, private=False, admin=True) @@ -427,7 +431,6 @@ def unpin_message(client, message): edit(message, f'`{get_translation("wrongCommand")}`') - @sedenify(pattern='^.(admins|bots|user(s|sdel))$', compat=False, private=False) def get_users(client, message): args = message.text.split(' ', 1) @@ -447,17 +450,17 @@ def get_users(client, message): message.chat.title, ], ) - filtr = 'all' + filtr = enums.ChatMembersFilter.SEARCH elif admins: out = get_translation('adminlist', ['**', '`', message.chat.title]) - filtr = 'administrators' + filtr = enums.ChatMembersFilter.ADMINISTRATORS elif bots: out = get_translation('botlist', ['**', '`', message.chat.title]) - filtr = 'bots' + filtr = enums.ChatMembersFilter.BOTS try: chat_id = message.chat.id - find = client.iter_chat_members(chat_id, filter=filtr) + find = client.get_chat_members(chat_id, filter=filtr) for i in find: if not i.user.is_deleted and showdel: continue @@ -499,7 +502,7 @@ def zombie_accounts(client, message): if args != 'clean': edit(message, f'`{get_translation("zombiesFind")}`') - for i in client.iter_chat_members(chat_id): + for i in client.get_chat_members(chat_id): if i.user.is_deleted: count += 1 sleep(1) @@ -515,7 +518,7 @@ def zombie_accounts(client, message): count = 0 users = 0 - for i in client.iter_chat_members(chat_id): + for i in client.get_chat_members(chat_id): if i.user.is_deleted: try: client.ban_chat_member(chat_id, i.user.id) @@ -600,7 +603,7 @@ def mute_check(client, message): try: user_id = message.from_user.id chat_id = message.chat.id - client.restrict_chat_member(chat_id, user_id, ChatPermissions()) + client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions()) except BaseException: pass diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat.py index df5cc72..24a1f4f 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat.py @@ -73,7 +73,7 @@ def keep_read(client, message): return if is_muted(message.chat.id): - client.read_history(message.chat.id) + client.read_chat_history(message.chat.id) message.continue_propagation() diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index a83cadd..b725a06 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -104,7 +104,7 @@ def add_filter(message): if not msg_o: edit(message, f'`{get_translation("filterError")}`') return - msg_id = msg_o.message_id + msg_id = msg_o.message.id send_log(get_translation('filterLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 97dc048..76e9a5e 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -9,6 +9,7 @@ from time import sleep +from pyrogram import enums from pyrogram.types import ChatPermissions from sedenbot import BRAIN, HELP, LOGS, TEMP_SETTINGS from sedenecem.core import edit, extract_args, get_translation, sedenify, send_log @@ -59,8 +60,7 @@ def gban_user(client, message): if user.id in BRAIN: return edit( message, - get_translation( - 'brainError', ['`', '**', user.first_name, user.id]), + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -69,8 +69,7 @@ def gban_user(client, message): sql.gban(user.id) edit( message, - get_translation( - 'gbanResult', ['**', user.first_name, user.id, '`']), + get_translation('gbanResult', ['**', user.first_name, user.id, '`']), ) try: common_chats = client.get_common_chats(user.id) @@ -123,20 +122,23 @@ def find_me(dialog): def find_member(dialog): try: - return (dialog.chat.get_member(user.id) + return ( + dialog.chat.get_member(user.id) and dialog.chat.get_member(user.id).restricted_by - and dialog.chat.get_member(user.id).restricted_by.id == me_id) + and dialog.chat.get_member(user.id).restricted_by.id == me_id + ) except BaseException: return False try: - dialogs = client.iter_dialogs() + dialogs = client.get_dialogs() me_id = TEMP_SETTINGS['ME'].id chats = [ dialog.chat for dialog in dialogs if ( - 'group' in dialog.chat.type + dialog.chat.type + in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP] and find_me(dialog) and find_member(dialog) ) @@ -147,8 +149,7 @@ def find_member(dialog): pass edit( message, - get_translation( - 'unbanResult', ['**', user.first_name, user.id, '`']), + get_translation('unbanResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) @@ -209,8 +210,7 @@ def gmute_user(client, message): if user.id in BRAIN: return edit( message, - get_translation( - 'brainError', ['`', '**', user.first_name, user.id]), + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) try: @@ -219,13 +219,12 @@ def gmute_user(client, message): sql2.gmute(user.id) edit( message, - get_translation( - 'gmuteResult', ['**', user.first_name, user.id, '`']), + get_translation('gmuteResult', ['**', user.first_name, user.id, '`']), ) try: common_chats = client.get_common_chats(user.id) for i in common_chats: - i.restrict_member(user.id, ChatPermissions()) + i.restrict_member(user.id, permissions=ChatPermissions()) except BaseException: pass sleep(1) @@ -272,8 +271,7 @@ def ungmute_user(client, message): pass edit( message, - get_translation('unmuteResult', [ - '**', user.first_name, user.id, '`']), + get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), ) except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) @@ -302,7 +300,7 @@ def gmute_check(client, message): try: user_id = message.from_user.id chat_id = message.chat.id - client.restrict_chat_member(chat_id, user_id, ChatPermissions()) + client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions()) except BaseException: pass diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 81428ec..91abdbf 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -7,6 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # +from pyrogram import enums from pyrogram.errors import PeerIdInvalid from pyrogram.raw.functions.messages import GetOnlines from sedenbot import BLACKLIST, BRAIN, HELP @@ -26,7 +27,7 @@ def who_is(client, message): reply = message.reply_to_message edit(message, f'`{get_translation("whoisProcess")}`') media_perm = True - if 'group' in message.chat.type: + if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages @@ -59,7 +60,7 @@ def who_is(client, message): else get_translation('notSet') ) user_id = reply_user.id - photos = client.get_profile_photos_count(user_id) + photos = client.get_chat_photos_count(user_id) dc_id = reply_user.dc_id bot = reply_user.is_bot scam = reply_user.is_scam @@ -103,15 +104,15 @@ def who_is(client, message): def LastSeen(bot, status): if bot: return 'BOT' - elif status == 'online': + elif status == enums.UserStatus.ONLINE: return get_translation('statusOnline') - elif status == 'recently': + elif status == enums.UserStatus.RECENTLY: return get_translation('statusRecently') - elif status == 'within_week': + elif status == enums.UserStatus.LAST_WEEK: return get_translation('statusWeek') - elif status == 'within_month': + elif status == enums.UserStatus.LAST_MONTH: return get_translation('statusMonth') - elif status == 'long_time_ago': + elif status == enums.UserStatus.LONG_AGO: return get_translation('statusLong') @@ -140,12 +141,12 @@ def get_chat_info(client, message): return media_perm = True - if 'group' in message.chat.type: + if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages try: - online_users = client.send(GetOnlines(peer=peer)) + online_users = client.invoke(GetOnlines(peer=peer)) online = online_users.onlines except PeerIdInvalid: edit(message, f'`{get_translation("groupNotFound")}`') diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index bfca486..927a9dd 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -141,7 +141,7 @@ def tagall(client, message): msg = '@tag' chat = message.chat.id length = 0 - for member in client.iter_chat_members(chat): + for member in client.get_chat_members(chat): if length < 4092: msg += f'[\u2063](tg://user?id={member.user.id})' length += 1 @@ -152,7 +152,7 @@ def tagall(client, message): def report_admin(client, message): msg = '@admin' chat = message.chat.id - for member in client.iter_chat_members(chat, filter='administrators'): + for member in client.get_chat_members(chat, filter='administrators'): msg += f'[\u2063](tg://user?id={member.user.id})' re_msg = message.reply_to_message reply(re_msg if re_msg else message, msg) diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index 905b3e9..782ef7f 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -79,7 +79,7 @@ def save_note(message): if not msg_o: edit(message, f'`{get_translation("noteError")}`') return - msg_id = msg_o.message_id + msg_id = msg_o.message.id send_log(get_translation('notesLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 9909e93..4706f3f 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -7,15 +7,8 @@ # All rights reserved. See COPYING, AUTHORS. # -from re import VERBOSE -from sedenbot import ( - HELP, - LOGS, - PM_AUTO_BAN, - PM_MSG_COUNT, - PM_UNAPPROVED, - TEMP_SETTINGS, -) +from pyrogram import enums +from sedenbot import HELP, LOGS, PM_AUTO_BAN, PM_MSG_COUNT, PM_UNAPPROVED, TEMP_SETTINGS from sedenbot.modules.chat import is_muted from sedenecem.core import edit, get_translation, reply, sedenify, send_log from sqlalchemy.exc import IntegrityError @@ -43,7 +36,6 @@ def pmpermit_init(): @sedenify( incoming=True, outgoing=True, - disable_edited=True, disable_notify=True, group=False, compat=False, @@ -71,7 +63,9 @@ def permitpm(client, message): if message.text != prevmsg: for message in _find_unapproved_msg(client, message.chat.id): message.delete() - if TEMP_SETTINGS['PM_COUNT'][message.chat.id] < (PM_MSG_COUNT - 1): + if TEMP_SETTINGS['PM_COUNT'][message.chat.id] < ( + PM_MSG_COUNT - 1 + ): ret = reply(message, UNAPPROVED_MSG) TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text else: @@ -80,12 +74,14 @@ def permitpm(client, message): TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text if notifsoff: - client.read_history(message.chat.id) + client.read_chat_history(message.chat.id) if message.chat.id not in TEMP_SETTINGS['PM_COUNT']: TEMP_SETTINGS['PM_COUNT'][message.chat.id] = 1 else: - TEMP_SETTINGS['PM_COUNT'][message.chat.id] = TEMP_SETTINGS['PM_COUNT'][message.chat.id] + 1 + TEMP_SETTINGS['PM_COUNT'][message.chat.id] = ( + TEMP_SETTINGS['PM_COUNT'][message.chat.id] + 1 + ) if TEMP_SETTINGS['PM_COUNT'][message.chat.id] > (PM_MSG_COUNT - 1): reply(message, f'`{get_translation("pmpermitBlock")}`') @@ -119,14 +115,12 @@ def auto_accept(client, message): if is_approved(chat.id): return True - for msg in client.get_history(chat.id, limit=1, reverse=True): + for msg in client.get_chat_history(chat.id, limit=1): # chat.id in TEMP_SETTINGS['PM_LAST_MSG'] # and msg.text != UNAPPROVED_MSG # and - if ( - msg.from_user.id == self_user.id - ): + if msg.from_user.id == self_user.id: try: del TEMP_SETTINGS['PM_COUNT'][chat.id] del TEMP_SETTINGS['PM_LAST_MSG'][chat.id] @@ -190,7 +184,7 @@ def approvepm(client, message): uid = replied_user.id else: aname = message.chat - if not aname.type == 'private': + if not aname.type == enums.ChatType.PRIVATE: edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -226,7 +220,7 @@ def disapprovepm(message): uid = replied_user.id else: aname = message.chat - if not aname.type == 'private': + if not aname.type == enums.ChatType.PRIVATE: edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 495bb60..f31c4b3 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -11,6 +11,7 @@ from time import sleep from PIL import Image +from pyrogram import enums from pyrogram.errors import UsernameOccupied from pyrogram.raw.functions.account import UpdateProfile, UpdateStatus, UpdateUsername from pyrogram.raw.functions.channels import GetAdminedPublicChannels @@ -32,7 +33,7 @@ @sedenify(pattern='^.reserved$', compat=False) def reserved(client, message): - sonuc = client.send(GetAdminedPublicChannels()) + sonuc = client.invoke(GetAdminedPublicChannels()) mesaj = '' for channel_obj in sonuc.chats: mesaj += f'{channel_obj.title}\n@{channel_obj.username}\n\n' @@ -50,7 +51,7 @@ def name(client, message): firstname = namesplit[0] lastname = namesplit[1] - client.send(UpdateProfile(first_name=firstname, last_name=lastname)) + client.invoke(UpdateProfile(first_name=firstname, last_name=lastname)) edit(message, f'`{get_translation("nameOk")}`') @@ -98,7 +99,7 @@ def remove_profilepic(client, message): lim = 1 count = 0 - for photo in client.iter_profile_photos('me', limit=lim): + for photo in client.get_chat_photos('me', limit=lim): client.delete_profile_photos(photo.file_id) count += 1 edit(message, f'`{get_translation("ppDeleted", [count])}`') @@ -107,7 +108,7 @@ def remove_profilepic(client, message): @sedenify(pattern='^.setbio', compat=False) def setbio(client, message): newbio = extract_args(message) - client.send(UpdateProfile(about=newbio)) + client.invoke(UpdateProfile(about=newbio)) edit(message, f'`{get_translation("bioSuccess")}`') @@ -115,7 +116,7 @@ def setbio(client, message): def username(client, message): newusername = extract_args(message) try: - client.send(UpdateUsername(username=newusername)) + client.invoke(UpdateUsername(username=newusername)) edit(message, f'`{get_translation("usernameSuccess")}`') except UsernameOccupied: edit(message, f'`{get_translation("usernameTaken")}`') @@ -134,7 +135,7 @@ def blockpm(client, message): uid = replied_user.id else: aname = message.chat - if not aname.type == 'private': + if not aname.type == enums.ChatType.PRIVATE: edit(message, f'`{get_translation("pmApproveError")}`') return name0 = aname.first_name @@ -194,7 +195,7 @@ def online(client, message): while ALWAYS_ONLINE in TEMP_SETTINGS: try: - client.send(UpdateStatus(offline=False)) + client.invoke(UpdateStatus(offline=False)) sleep(5) except BaseException: return @@ -211,13 +212,13 @@ def user_stats(client, message): bots = 0 unread = 0 user = [] - for i in client.iter_dialogs(): + for i in client.get_dialogs(): chats += 1 - if i.chat.type == 'channel': + if i.chat.type == enums.ChatType.CHANNEL: channels += 1 - elif i.chat.type == 'group': + elif i.chat.type == enums.ChatType.GROUP: groups += 1 - elif i.chat.type == 'supergroup': + elif i.chat.type == enums.ChatType.SUPERGROUP: sgroups += 1 else: pms += 1 diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index 0b174c9..33354c9 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -25,19 +25,17 @@ def purge(client, message): msg = message.reply_to_message if msg: - itermsg = client.iter_history( - message.chat.id, offset_id=msg.message_id, reverse=True - ) + itermsg = list(range(msg.id, message.id)) else: edit(message, f'`{get_translation("purgeUsage")}`') return count = 0 - for message in itermsg: + for i in itermsg: try: count = count + 1 - client.delete_messages(message.chat.id, message.message_id) + client.delete_messages(chat_id=message.chat.id, message_ids=i, revoke=True) except FloodWait as e: sleep(e.x) except Exception as e: @@ -47,6 +45,7 @@ def purge(client, message): done = reply(message, get_translation('purgeResult', ['**', '`', str(count)])) send_log(get_translation('purgeLog', ['**', '`', str(count)])) sleep(2) + message.delete() done.delete() @@ -57,7 +56,7 @@ def purgeme(client, message): return edit(message, f'`{get_translation("purgemeUsage")}`') i = 1 - itermsg = client.get_history(message.chat.id) + itermsg = client.get_chat_history(message.chat.id) for message in itermsg: if i > int(count) + 1: break @@ -77,7 +76,7 @@ def delete(client, message): if msg_src: if msg_src.from_user.id: try: - client.delete_messages(message.chat.id, msg_src.message_id) + client.delete_messages(message.chat.id, msg_src.id) message.delete() send_log(f'`{get_translation("delResultLog")}`') except BaseException: diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 4df58b1..3b307c9 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -61,7 +61,7 @@ def save_snip(message): if not msg_o: edit(message, f'`{get_translation("snipError")}`') return - msg_id = msg_o.message_id + msg_id = msg_o.message.id send_log(get_translation('snipsLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index 728df85..d9fbea4 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -95,7 +95,7 @@ def picspam(message): @sedenify(pattern='^.delayspam') def delayspam(message): - # Copyright (c) @ReversedPosix | 2020-2021 + # Copyright (c) @ReversedPosix | 2020-2022 delayspam = extract_args(message) arr = delayspam.split() if len(arr) < 3 or not arr[0].isdigit() or not arr[1].isdigit(): diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index 18183b5..feadb30 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -7,8 +7,10 @@ # All rights reserved. See COPYING, AUTHORS. # +from pyrogram import enums from sedenbot import BRAIN, HELP, SPAMWATCH_KEY from sedenecem.core import get_translation, is_admin_myself, reply, sedenify, send_log + from spamwatch import Client as SpamWatch @@ -16,7 +18,13 @@ class SWClient: spamwatch_client = SpamWatch(SPAMWATCH_KEY) if SPAMWATCH_KEY else None -@sedenify(compat=False, outgoing=False, incoming=True, disable_notify=True, disable_edited=True) +@sedenify( + compat=False, + outgoing=False, + incoming=True, + disable_notify=True, + disable_edited=True, +) def spamwatch_action(client, message): if not SWClient.spamwatch_client: message.continue_propagation() @@ -32,7 +40,7 @@ def spamwatch_action(client, message): if is_admin_myself(message.chat): text = get_translation('spamWatchBan', [message.from_user.first_name, uid]) - if 'private' == message.chat.type: + if message.chat.type == enums.ChatType.PRIVATE: reply(message, text) client.block_user(uid) else: @@ -45,4 +53,5 @@ def spamwatch_action(client, message): send_log(text) + HELP.update({'spamwatch': get_translation('spamWatchInfo')}) diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index ea715c5..59651b0 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -119,7 +119,7 @@ def pack_created(pname): try: set_name = InputStickerSetShortName(short_name=TEMP_SETTINGS[pname]) set = GetStickerSet(stickerset=set_name, hash=0) - client.send(data=set) + client.invoke(query=set) return True except BaseException: return False @@ -263,7 +263,7 @@ def packinfo(client, message): edit(message, f'`{get_translation("processing")}`') - get_stickerset = client.send( + get_stickerset = client.invoke( GetStickerSet( stickerset=InputStickerSetShortName(short_name=reply.sticker.set_name), hash=0, diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 1757f87..4a8a2d5 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -101,7 +101,7 @@ def test_echo(message): @sedenify(pattern='^.dc$', compat=False) def data_center(client, message): - result = client.send(GetNearestDc()) + result = client.invoke(GetNearestDc()) edit( message, @@ -135,6 +135,7 @@ def terminal(message): result = get_translation("termNoResult") try: from sedenecem.core.misc import __status_out__ + _, result = __status_out__(command) except BaseException as e: pass diff --git a/sedenecem/core/conv.py b/sedenecem/core/conv.py index 6319f14..d68b7a6 100644 --- a/sedenecem/core/conv.py +++ b/sedenecem/core/conv.py @@ -40,7 +40,7 @@ def recv_msg(self, read=True): msg = conv[self.count] self.count = len(conv) if read: - self.client.read_history(chat_id=self.chat_id) + self.client.read_chat_history(chat_id=self.chat_id) return msg def forward_msg(self, msg): diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 5c79b87..cdb7d7e 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -9,15 +9,19 @@ from os import makedirs from re import escape, sub -from subprocess import CalledProcessError, check_output, STDOUT +from subprocess import STDOUT, CalledProcessError, check_output +from pyrogram import enums from pyrogram.types import Message from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, TEMP_SETTINGS, app MARKDOWN_FIX_CHAR = '\u2064' SPAM_COUNT = [0] _parsed_prefix = escape(BOT_PREFIX) if BOT_PREFIX else r'\.' -_admin_status_list = ['creator', 'administrator'] +_admin_status_list = [ + enums.ChatMemberStatus.OWNER, + enums.ChatMemberStatus.ADMINISTRATOR, +] google_domains = [ 'www.google.com', 'www.google.ad', @@ -212,7 +216,12 @@ def reply( - message, text, preview=True, fix_markdown=False, delete_orig=False, parse='md' + message, + text, + preview=True, + fix_markdown=False, + delete_orig=False, + parse=enums.ParseMode.MARKDOWN, ): try: if fix_markdown: @@ -246,7 +255,9 @@ def extract_args_arr(message, markdown=True): return extract_args(message, markdown).split() -def edit(message, text, preview=True, fix_markdown=False, parse='md'): +def edit( + message, text, preview=True, fix_markdown=False, parse=enums.ParseMode.MARKDOWN +): try: if fix_markdown: text += MARKDOWN_FIX_CHAR @@ -351,7 +362,7 @@ def parse_cmd(text): def is_admin(message): - if not 'group' in message.chat.type: + if not message.chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: return True user = app.get_chat_member(chat_id=message.chat.id, user_id=message.from_user.id) @@ -359,7 +370,7 @@ def is_admin(message): def is_admin_myself(chat): - if not 'group' in chat.type: + if not chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: return True user = app.get_chat_member(chat_id=chat.id, user_id='me') diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 50edf08..f24beaf 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -7,13 +7,14 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import remove, path +from os import path, remove +from pyrogram import enums from pyrogram.types import Message - -from .misc import MARKDOWN_FIX_CHAR, get_duration, __status_out__ from sedenbot import LOG_VERBOSE +from .misc import MARKDOWN_FIX_CHAR, __status_out__, get_duration + def reply_img( message, @@ -22,7 +23,7 @@ def reply_img( fix_markdown=False, delete_orig=False, delete_file=False, - parse='md', + parse=enums.ParseMode.MARKDOWN, ): try: if len(caption) > 0 and fix_markdown: @@ -71,7 +72,7 @@ def reply_video( progress=None, delete_orig=False, delete_file=False, - parse='md', + parse=enums.ParseMode.MARKDOWN, ): try: if not thumb: @@ -182,7 +183,7 @@ def reply_sticker(message, sticker, delete_orig=False, delete_file=False): def reply_msg(message: Message, message2: Message, delete_orig=False): try: - message2.copy(chat_id=message.chat.id, reply_to_message_id=message.message_id) + message2.copy(chat_id=message.chat.id, reply_to_message_id=message.message.id) if delete_orig: message.delete() diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index f7bf7cc..2a2bf0d 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -13,7 +13,7 @@ from time import gmtime, strftime from traceback import format_exc -from pyrogram import ContinuePropagation, StopPropagation, filters +from pyrogram import ContinuePropagation, StopPropagation, enums, filters from pyrogram.handlers import MessageHandler from sedenbot import BLACKLIST, BOT_VERSION, BRAIN, TEMP_SETTINGS, app, get_translation @@ -25,7 +25,6 @@ def sedenify(**args): pattern = args.get('pattern', None) outgoing = args.get('outgoing', True) incoming = args.get('incoming', False) - disable_edited = args.get('disable_edited', False) disable_notify = args.get('disable_notify', False) compat = args.get('compat', True) brain = args.get('brain', False) @@ -57,18 +56,24 @@ def wrap(client, message): if message.service and not service: return - if message.chat.type == 'channel': + if message.chat.type == enums.ChatType.CHANNEL: return - if not bot and message.chat.type == 'bot': + if not bot and message.chat.type == enums.ChatType.BOT: message.continue_propagation() - if not private and message.chat.type in ['private', 'bot']: + if not private and message.chat.type in [ + enums.ChatType.PRIVATE, + enums.ChatType.BOT, + ]: if not disable_notify: edit(message, f'`{get_translation("groupUsage")}`') message.continue_propagation() - if not group and 'group' in message.chat.type: + if not group and message.chat.type == [ + enums.ChatType.SUPERGROUP, + enums.ChatType.GROUP, + ]: if not disable_notify: edit(message, f'`{get_translation("privateUsage")}`') message.continue_propagation() @@ -153,9 +158,6 @@ def wrap(client, message): else: filter = (filters.me | filters.incoming) & ~filters.bot - if disable_edited: - filter &= ~filters.edited - app.add_handler(MessageHandler(wrap, filter)) return msg_decorator diff --git a/sedenecem/core/webdriver.py b/sedenecem/core/webdriver.py index 44228d9..3848c26 100644 --- a/sedenecem/core/webdriver.py +++ b/sedenecem/core/webdriver.py @@ -24,4 +24,3 @@ def get_webdriver(): return Chrome(executable_path=CHROME_DRIVER, options=options) except BaseException: raise Exception('CHROME_DRIVER not found!') - return None From 413adaa5dbebb1af0f5d2ab660333c8555135869 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 01:49:37 +0300 Subject: [PATCH 141/242] Release Seden v1.6.2 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 2675c63..f4bca09 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.1' +BOT_VERSION = '1.6.2' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 1c6dbc473b58db468fea8e6b9e53729f3d59cff4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 13:47:23 +0300 Subject: [PATCH 142/242] little fixes Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/filters.py | 2 +- sedenbot/modules/notes.py | 2 +- sedenbot/modules/snips.py | 2 +- sedenbot/modules/spamwatch.py | 1 - sedenbot/modules/spotify_api.py | 6 +++++- sedenecem/core/sedenify.py | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index b725a06..04d6e43 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -104,7 +104,7 @@ def add_filter(message): if not msg_o: edit(message, f'`{get_translation("filterError")}`') return - msg_id = msg_o.message.id + msg_id = msg_o.id send_log(get_translation('filterLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index 782ef7f..faffaea 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -79,7 +79,7 @@ def save_note(message): if not msg_o: edit(message, f'`{get_translation("noteError")}`') return - msg_id = msg_o.message.id + msg_id = msg_o.id send_log(get_translation('notesLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 3b307c9..8599319 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -61,7 +61,7 @@ def save_snip(message): if not msg_o: edit(message, f'`{get_translation("snipError")}`') return - msg_id = msg_o.message.id + msg_id = msg_o.id send_log(get_translation('snipsLog', ['`', message.chat.id, keyword])) else: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index feadb30..9a89a96 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -23,7 +23,6 @@ class SWClient: outgoing=False, incoming=True, disable_notify=True, - disable_edited=True, ) def spamwatch_action(client, message): if not SWClient.spamwatch_client: diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index 3352fde..2c0b2b6 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -14,6 +14,7 @@ from urllib.request import urlretrieve from zipfile import ZipFile +from pyrogram import enums from requests import get from sedenbot import HELP from sedenecem.core import ( @@ -202,7 +203,10 @@ def show_users_detail( ) media_perm = True - if 'group' in message.chat.type: + if message.chat.type == [ + enums.ChatType.SUPERGROUP, + enums.ChatType.GROUP, + ]: perm = message.chat.permissions media_perm = perm.can_send_media_messages diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 2a2bf0d..aad6a52 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -70,7 +70,7 @@ def wrap(client, message): edit(message, f'`{get_translation("groupUsage")}`') message.continue_propagation() - if not group and message.chat.type == [ + if not group and message.chat.type in [ enums.ChatType.SUPERGROUP, enums.ChatType.GROUP, ]: From d0dd1ca6c3d5153320ebd98bd56bbdcd0dddbc5d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 14:08:06 +0300 Subject: [PATCH 143/242] fix reply_msg Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenecem/core/replier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index f24beaf..503dfa5 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -183,7 +183,7 @@ def reply_sticker(message, sticker, delete_orig=False, delete_file=False): def reply_msg(message: Message, message2: Message, delete_orig=False): try: - message2.copy(chat_id=message.chat.id, reply_to_message_id=message.message.id) + message2.copy(chat_id=message.chat.id, reply_to_message_id=message.id) if delete_orig: message.delete() From 8b6e21f97dfea0de492ad8660b01a485c945df38 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 14:08:23 +0300 Subject: [PATCH 144/242] Release Seden v1.6.3 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index f4bca09..3776e7a 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -117,7 +117,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.2' +BOT_VERSION = '1.6.3' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 6fa8b71b2eadaf810d70fbe1175dd6b283c8d962 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 26 Apr 2022 14:20:42 +0300 Subject: [PATCH 145/242] close asyncio logs Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 3776e7a..8fd8e45 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -103,6 +103,9 @@ def set_logger(): pyrogram_auth = getLogger('pyrogram.session.auth') pyrogram_auth.setLevel(CRITICAL) + asyncio = getLogger('asyncio') + asyncio.setLevel(CRITICAL) + set_logger() From 897c461d2fcc1249f36dd9a5e1e7cc780ee6175a Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 27 Apr 2022 23:50:28 +0300 Subject: [PATCH 146/242] spotify: fix mistake Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/spotify_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index 2c0b2b6..ad64b1c 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -203,7 +203,7 @@ def show_users_detail( ) media_perm = True - if message.chat.type == [ + if message.chat.type in [ enums.ChatType.SUPERGROUP, enums.ChatType.GROUP, ]: From db3a7f6d80b365b75bec0a3557e4441c037224d7 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 27 Apr 2022 23:52:37 +0300 Subject: [PATCH 147/242] pmpermit: report as spam Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/pmpermit.py | 4 ++++ sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 4706f3f..6397f1a 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -8,6 +8,8 @@ # from pyrogram import enums +from pyrogram.raw.functions.messages import ReportSpam +from pyrogram.raw.types import InputPeerUser from sedenbot import HELP, LOGS, PM_AUTO_BAN, PM_MSG_COUNT, PM_UNAPPROVED, TEMP_SETTINGS from sedenbot.modules.chat import is_muted from sedenecem.core import edit, get_translation, reply, sedenify, send_log @@ -93,6 +95,8 @@ def permitpm(client, message): pass client.block_user(message.chat.id) + peer: InputPeerUser = client.resolve_peer(message.chat.id) + client.invoke(ReportSpam(peer=peer)) send_log( get_translation( diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 8c2cb9f..016e6b4 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -373,7 +373,7 @@ "pmUnblocked": "You've been unblocked.", "pmUnblockedLog": "[%1](tg://user?id=%2) was unblocc'd!", "pmUnblockedUsage": "You should reply message of the person you are unblocking.", - "pmpermitBlock": "You were spamming my F\u00fchrer's PM, which I don't like.\nI'mma block you.", + "pmpermitBlock": "You were spamming my F\u00fchrer's PM, which I don't like.\nI'mma block you and reporting as SPAM.", "pmpermitInfo": ".approve\nUsage: Approves the replied person to PM.\n\n.disapprove\nUsage: Disapproves the replied person to PM.\n\n.block\nUsage: Blocks the person.\n\n.unblock\nUsage: Unblocks the person so they can PM you.\n\n.notifoff\nUsage: Clears/Disables any notifications of unapproved PMs.\n\n.notifon\nUsage: Allows notifications for unapproved PMs.", "pmpermitLog": "[%1](tg://user?id=%2) was just another retarded nibba", "pmpermitMessage": "%1Bleep blop! This is a bot. Don't fret.\n\nMy F\u00fchrer hasn't approved you to PM.\nPlease wait for my F\u00fchrer to look in, he mostly approves PMs.\n\nAs far as I know, he doesn't usually approve retards though.%1", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index ee03810..54335b2 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -374,7 +374,7 @@ "pmUnblocked": "Engelin kald\u0131r\u0131ld\u0131.", "pmUnblockedLog": "[%1](tg://user?id=%2) ki\u015fisinin engeli kald\u0131r\u0131ld\u0131.", "pmUnblockedUsage": "Engelini kald\u0131raca\u011f\u0131n ki\u015finin mesaj\u0131n\u0131 al\u0131nt\u0131lamal\u0131s\u0131n.", - "pmpermitBlock": "Sen benim sahibimin PM'ini spaml\u0131yorsun, bu benim ho\u015fuma gitmiyor.\n\u015eu an ENGELLENDIN, ileride de\u011fi\u015fiklik olmad\u0131\u011f\u0131 s\u00fcrece\u2026", + "pmpermitBlock": "Sen benim sahibimin PM'ini spaml\u0131yorsun, bu benim ho\u015fuma gitmiyor.\n\u015eu an ENGELLENDIN ve SPAM olarak bildirildin, ileride de\u011fi\u015fiklik olmad\u0131\u011f\u0131 s\u00fcrece\u2026", "pmpermitInfo": ".approve\nKullan\u0131m: Yan\u0131t verilen kullan\u0131c\u0131ya PM atma izni verilir.\n\n.disapprove\nKullan\u0131m: Yan\u0131t verilen kullan\u0131c\u0131n\u0131n PM onay\u0131n\u0131 kald\u0131r\u0131r.\n\n.notifoff\nKullan\u0131m: Onaylanmam\u0131\u015f \u00f6zel mesajlar\u0131n bildirimlerini temizler ya da devre d\u0131\u015f\u0131 b\u0131rak\u0131r.\n\n.notifon\nKullan\u0131m: Onaylanmam\u0131\u015f \u00f6zel mesajlar\u0131n bildirim g\u00f6ndermesine izin verir.", "pmpermitLog": "[%1](tg://user?id=%2) ki\u015fisi sadece bir hayal k\u0131r\u0131kl\u0131\u011f\u0131yd\u0131.\nPM'ni me\u015fgul etti\u011fi i\u00e7in engellendi.", "pmpermitMessage": "%1Hey! Bu bir bot. Endi\u015felenme.\n\nSahibim sana PM atma izni vermedi.\nL\u00fctfen sahibimin aktif olmas\u0131n\u0131 bekleyin, o genellikle PM'leri onaylar.\n\nBildi\u011fim kadar\u0131yla o kafay\u0131 yemi\u015f insanlara PM izni vermiyor.%1", From 4867a9f8929358bc254bcc55b1368ea42e29472d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 27 Apr 2022 23:53:30 +0300 Subject: [PATCH 148/242] Update pyrogram Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 42c15e0..9e97df8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.13 +pyrogram==2.0.14 python-barcode python-dotenv qrcode From b85efd24f11a3188fdf14635aedff3898434201f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 27 Apr 2022 23:55:15 +0300 Subject: [PATCH 149/242] Release Seden v1.6.4 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 8fd8e45..a2f0b62 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.3' +BOT_VERSION = '1.6.4' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From bb2885f15ace6cdf5da6e9ac3c89eebe83174027 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 1 May 2022 17:42:15 +0300 Subject: [PATCH 150/242] Bug fixes + improvements * improved ban.py with extract_user * pin command now have loud pin support * disable_edited is back! Co-authored-by: fatihesergg <fatihesergg@gmail.com> Signed-off-by: NaytSeyd <naytseyd@yandex.com> Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- requirements.txt | 2 +- sedenbot/modules/afk.py | 1 + sedenbot/modules/ban.py | 593 +++++++++++++++++---------------- sedenbot/modules/ezanvakti.py | 23 +- sedenbot/modules/globals.py | 348 +++++++++---------- sedenbot/modules/kargotakip.py | 16 +- sedenbot/modules/locks.py | 92 ++--- sedenbot/modules/pmpermit.py | 1 + sedenbot/modules/spamwatch.py | 3 +- sedenecem/core/misc.py | 40 ++- sedenecem/core/sedenify.py | 5 +- sedenecem/translator/en.json | 31 +- sedenecem/translator/tr.json | 31 +- 13 files changed, 617 insertions(+), 569 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9e97df8..3e229ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.14 +pyrogram==2.0.16 python-barcode python-dotenv qrcode diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 1991418..9d1d384 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -32,6 +32,7 @@ @sedenify( incoming=True, outgoing=False, + disable_edited=True, private=False, bot=False, disable_notify=True, diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 4e7f805..183c4ef 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -24,6 +24,8 @@ download_media_wc, edit, extract_args, + extract_args_arr, + extract_user, get_download_dir, get_translation, is_admin, @@ -33,23 +35,34 @@ ) -@sedenify(pattern='^.ban', compat=False, private=False, admin=True) -def ban_user(client, message): +def get_reason(message): args = extract_args(message) reply = message.reply_to_message - edit(message, f'`{get_translation("banProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) + if reply: + if args: + reason = get_translation('banReason', ['**', '`', args]) + else: + reason = '' else: - edit(message, f'`{get_translation("banFailUser")}`') - return + text = args.split(' ', 1) + if len(text) > 1: + reason = get_translation('banReason', ['**', '`', text[1]]) + else: + reason = '' + pass + return reason + + +@sedenify(pattern='^.ban', private=False, admin=True) +def ban_user(message): + reply = message.reply_to_message + edit(message, f'`{get_translation("banProcess")}`') + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + + reason = get_reason(message) try: replied_user = reply.from_user @@ -58,49 +71,54 @@ def ban_user(client, message): except BaseException: pass - if user.id in BRAIN: - return edit( - message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), - ) - - try: - chat_id = message.chat.id - client.ban_chat_member(chat_id, user.id) - edit( - message, get_translation('banResult', ['**', user.first_name, user.id, '`']) - ) - sleep(1) - send_log( - get_translation( - 'banLog', - [user.first_name, user.id, message.chat.title, '`', chat_id], + for user in find_user: + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) - ) - except UserAdminInvalid: - edit(message, f'`{get_translation("banAdminError")}`') - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + try: + chat = message.chat + chat.ban_member(user.id) + edit( + message, + get_translation( + 'banResult', + ['**', user.first_name, user.id, '`', reason if reason else ''], + ), + ) + sleep(1) + send_log( + get_translation( + 'banLog', + [ + user.first_name, + user.id, + chat.title, + '`', + chat.id, + reason if reason else '', + ], + ) + ) + except UserAdminInvalid: + edit(message, f'`{get_translation("banAdminError")}`') + except BaseException as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.unban', compat=False, private=False, admin=True) -def unban_user(client, message): - args = extract_args(message) +@sedenify(pattern='^.unban', private=False, admin=True) +def unban_user(message): + args = extract_args_arr(message) + if len(args) > 1: + return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message edit(message, f'`{get_translation("unbanProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') try: replied_user = reply.from_user @@ -109,35 +127,29 @@ def unban_user(client, message): except BaseException: pass - try: - chat_id = message.chat.id - client.unban_chat_member(chat_id, user.id) - edit( - message, - get_translation('unbanResult', ['**', user.first_name, user.id, '`']), - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + for user in find_user: + try: + chat = message.chat + chat.unban_member(user.id) + edit( + message, + get_translation('unbanResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.kick', compat=False, private=False, admin=True) -def kick_user(client, message): - args = extract_args(message) +@sedenify(pattern='^.kick', private=False, admin=True) +def kick_user(message): reply = message.reply_to_message edit(message, f'`{get_translation("kickProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + + reason = get_reason(message) try: replied_user = reply.from_user @@ -146,61 +158,58 @@ def kick_user(client, message): except BaseException: pass - if user.id in BRAIN: - return edit( - message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), - ) - - try: - chat_id = message.chat.id - client.ban_chat_member(chat_id, user.id) - client.unban_chat_member(chat_id, user.id) - edit( - message, - get_translation('kickResult', ['**', user.first_name, user.id, '`']), - ) - sleep(1) - send_log( - get_translation( - 'kickLog', - [ - user.first_name, - user.id, - message.chat.title, - '`', - message.chat.id, - ], + for user in find_user: + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + try: + chat = message.chat + chat.ban_member(user.id) + chat.unban_member(user.id) + edit( + message, + get_translation( + 'kickResult', + ['**', user.first_name, user.id, '`', reason if reason else ''], + ), + ) + sleep(1) + send_log( + get_translation( + 'kickLog', + [ + user.first_name, + user.id, + chat.title, + '`', + chat.id, + reason if reason else '', + ], + ) + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.mute', compat=False, private=False, admin=True) -def mute_user(client, message): +@sedenify(pattern='^.mute', private=False, admin=True) +def mute_user(message): try: from sedenecem.sql import mute_sql as sql except BaseException: edit(message, f'`{get_translation("nonSqlMode")}`') return - args = extract_args(message) reply = message.reply_to_message edit(message, f'`{get_translation("muteProcess")}`') - if len(args): - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + + reason = get_reason(message) try: replied_user = reply.from_user @@ -209,75 +218,80 @@ def mute_user(client, message): except BaseException: pass - if user.id in BRAIN: - return edit( - message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), - ) - - try: - chat_id = message.chat.id - if sql.is_muted(chat_id, user.id): - return - sql.mute(chat_id, user.id) - edit( - message, - get_translation('muteResult', ['**', user.first_name, user.id, '`']), - ) - sleep(1) - send_log( - get_translation( - 'muteLog', - [user.first_name, user.id, message.chat.title, '`', chat_id], + for user in find_user: + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), ) - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + try: + chat = message.chat + if sql.is_muted(chat.id, user.id): + return edit(message, 'kullanici zaten susturulmus') + sql.mute(chat.id, user.id) + edit( + message, + get_translation( + 'muteResult', + ['**', user.first_name, user.id, '`', reason if reason else ''], + ), + ) + sleep(1) + send_log( + get_translation( + 'muteLog', + [ + user.first_name, + user.id, + chat.title, + '`', + chat.id, + reason if reason else '', + ], + ) + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.unmute', compat=False, private=False, admin=True) -def unmute_user(client, message): +@sedenify(pattern='^.unmute', private=False, admin=True) +def unmute_user(message): try: from sedenecem.sql import mute_sql as sql except BaseException: edit(message, f'`{get_translation("nonSqlMode")}`') return - args = extract_args(message) + args = extract_args_arr(message) + if len(args) > 1: + return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message edit(message, f'`{get_translation("unmuteProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') try: replied_user = reply.from_user if replied_user.is_self: - return edit(message, f'`{get_translation("cannotUnbanMyself")}`') + return edit(message, f'`{get_translation("cannotMuteMyself")}`') except BaseException: pass - try: - chat_id = message.chat.id - sql.unmute(chat_id, user.id) - client.unban_chat_member(chat_id, user.id) - edit( - message, - get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + for user in find_user: + try: + chat = message.chat + sql.unmute(chat.id, user.id) + chat.unban_member(user.id) + edit( + message, + get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return @sedenify(pattern='^.promote', admin=True, private=False, compat=False) @@ -286,79 +300,70 @@ def promote_user(client, message): reply = message.reply_to_message rank = None edit(message, f'`{get_translation("promoteProcess")}`') + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + if reply: - try: - user_id = reply.from_user.id - user = client.get_users(user_id) + if args: rank = args - except Exception: - return edit(message, f'`{get_translation("banFailUser")}`') - elif ' ' not in args: - try: - user = client.get_users(args) - except Exception: - return edit(message, f'`{get_translation("banFailUser")}`') - elif args: - try: - arr = args.split(' ', 1) - user = client.get_users(arr[0]) - rank = arr[1] - except Exception: - return edit(message, f'`{get_translation("banFailUser")}`') + else: + rank = '' else: - return edit(message, f'`{get_translation("banFailUser")}`') + text = args.split(' ', 1) + if len(text) > 1: + rank = text[1] + else: + rank = '' - try: - chat_id = message.chat.id - client.promote_chat_member( - chat_id, - user.id, - privileges=ChatPrivileges( - can_change_info=True, - can_delete_messages=True, - can_restrict_members=True, - can_invite_users=True, - can_pin_messages=True, - can_promote_members=True, - ), - ) - if rank is not None: - if len(rank) > 16: - rank = rank[:16] - client.set_administrator_title(chat_id, user.id, rank) - edit( - message, - get_translation('promoteResult', ['**', user.first_name, user.id, '`']), - ) - sleep(1) - send_log( - get_translation( - 'promoteLog', - [user.first_name, user.id, message.chat.title, '`', chat_id], + for user in find_user: + try: + chat = message.chat + chat.promote_member( + user.id, + privileges=ChatPrivileges( + can_manage_chat=True, + can_delete_messages=True, + can_manage_video_chats=True, + can_restrict_members=True, + can_change_info=True, + can_invite_users=True, + can_pin_messages=True, + can_promote_members=True, + ), ) - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + if rank is not None: + if len(rank) > 16: + rank = rank[:16] + client.set_administrator_title(chat.id, user.id, rank) + edit( + message, + get_translation('promoteResult', ['**', user.first_name, user.id, '`']), + ) + sleep(1) + send_log( + get_translation( + 'promoteLog', + [user.first_name, user.id, chat.title, '`', chat.id], + ) + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.demote', compat=False, private=False, admin=True) -def demote_user(client, message): - args = extract_args(message) +@sedenify(pattern='^.demote', private=False, admin=True) +def demote_user(message): + args = extract_args_arr(message) + if len(args) > 1: + return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message edit(message, f'`{get_translation("demoteProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') try: replied_user = reply.from_user @@ -367,50 +372,55 @@ def demote_user(client, message): except BaseException: pass - try: - chat_id = message.chat.id - client.promote_chat_member( - chat_id, - user.id, - privileges=ChatPrivileges( - can_change_info=False, - can_delete_messages=False, - can_restrict_members=False, - can_invite_users=False, - can_pin_messages=False, - can_promote_members=False, - ), - ) - edit( - message, - get_translation('demoteResult', ['**', user.first_name, user.id, '`']), - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + for user in find_user: + try: + chat = message.chat + chat.promote_member( + user.id, + privileges=ChatPrivileges( + can_manage_chat=False, + can_delete_messages=False, + can_manage_video_chats=False, + can_restrict_members=False, + can_change_info=False, + can_invite_users=False, + can_pin_messages=False, + can_promote_members=False, + ), + ) + edit( + message, + get_translation('demoteResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return -@sedenify(pattern='^.pin$', compat=False, private=False, admin=True) -def pin_message(client, message): +@sedenify(pattern='^.pin', private=False, admin=True) +def pin_message(message): + args = extract_args(message).lower() reply = message.reply_to_message - if not reply: - return edit(message, f'`{get_translation("wrongCommand")}`') - - try: - chat_id = message.chat.id - message_id = reply.id - client.pin_chat_message(chat_id, message_id, disable_notification=True) - edit(message, f'`{get_translation("pinResult")}`') - sleep(1) - send_log(get_translation('pinLog', [message.chat.title, '`', chat_id])) - except Exception as e: - return edit(message, get_translation('banError', ['`', '**', e])) + if reply: + try: + chat = message.chat + if args == 'loud': + reply.pin(disable_notification=False) + else: + reply.pin(disable_notification=True) + edit(message, f'`{get_translation("pinResult")}`') + sleep(1) + send_log(get_translation('pinLog', [message.chat.title, '`', chat.id])) + except Exception as e: + return edit(message, get_translation('banError', ['`', '**', e])) + else: + edit(message, f'`{get_translation("wrongCommand")}`') -@sedenify(pattern='^.unpin', compat=False, private=False, admin=True) -def unpin_message(client, message): - args = extract_args(message) +@sedenify(pattern='^.unpin', private=False, admin=True) +def unpin_message(message): + args = extract_args(message).lower() reply = message.reply_to_message chat = message.chat if reply: @@ -422,7 +432,7 @@ def unpin_message(client, message): return elif 'all' in args: try: - client.unpin_all_chat_messages(chat.id) + chat.unpin_all_messages() message.delete() except Exception as e: edit(message, get_translation('banError', ['`', '**', e])) @@ -496,13 +506,13 @@ def get_users(client, message): @sedenify(pattern='^.zombies', private=False, compat=False) def zombie_accounts(client, message): args = extract_args(message).lower() - chat_id = message.chat.id + chat = message.chat count = 0 msg = f'`{get_translation("zombiesNoAccount")}`' if args != 'clean': edit(message, f'`{get_translation("zombiesFind")}`') - for i in client.get_chat_members(chat_id): + for i in client.get_chat_members(chat.id): if i.user.is_deleted: count += 1 sleep(1) @@ -518,16 +528,16 @@ def zombie_accounts(client, message): count = 0 users = 0 - for i in client.get_chat_members(chat_id): + for i in client.get_chat_members(chat.id): if i.user.is_deleted: try: - client.ban_chat_member(chat_id, i.user.id) + chat.ban_member(i.user.id) except UserAdminInvalid: count -= 1 users += 1 except BaseException: return edit(message, f'`{get_translation("zombiesError")}`') - client.unban_chat_member(chat_id, i.user.id) + chat.unban_member(i.user.id) count += 1 if count > 0: @@ -540,13 +550,11 @@ def zombie_accounts(client, message): sleep(2) message.delete() - send_log( - get_translation('zombiesLog', ['**', '`', count, message.chat.title, chat_id]) - ) + send_log(get_translation('zombiesLog', ['**', '`', count, chat.title, chat.id])) -@sedenify(pattern='^.setgpic$', compat=False, admin=True, private=False) -def set_group_photo(client, message): +@sedenify(pattern='^.setgpic$', admin=True, private=False) +def set_group_photo(message): reply = message.reply_to_message photo = None if ( @@ -572,7 +580,8 @@ def set_group_photo(client, message): new_photo = f'{get_download_dir()}/group_photo_new.png' image.save(new_photo) try: - client.set_chat_photo(chat_id=message.chat.id, photo=new_photo) + chat = message.chat + chat.set_photo(photo=new_photo) remove(photo) remove(new_photo) edit(message, f'`{get_translation("groupPicChanged")}`') @@ -587,23 +596,23 @@ def set_group_photo(client, message): edit(message, f'`{get_translation("ppError")}`') -@sedenify(incoming=True, outgoing=False, compat=False) -def mute_check(client, message): +@sedenify(incoming=True, outgoing=False) +def mute_check(message): try: from sedenecem.sql import mute_sql as sql except BaseException: return - muted = sql.is_muted(message.chat.id, message.from_user.id) + chat = message.chat + user = message.from_user + muted = sql.is_muted(chat.id, user.id) if muted: sleep(0.1) message.delete() try: - user_id = message.from_user.id - chat_id = message.chat.id - client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions()) + chat.restrict_member(user.id, permissions=ChatPermissions()) except BaseException: pass diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index cd56fca..508af14 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -23,7 +23,10 @@ def ezanvakti(message): if len(konum) < 1: return edit(message, '`Lütfen komutun yanına bir şehir belirtin.`') - result = get_result(konum) + try: + result = get_result(konum) + except BaseException: + return edit(message, f'`{konum} için bir bilgi bulunamadı.`') res1 = result.body.findAll('div', {'class': ['body-content']}) res1 = res1[0].findAll('script') res1 = sub( @@ -57,7 +60,10 @@ def ramazan(message): if len(konum) < 1: return edit(message, '`Lütfen komutun yanına bir şehir belirtin.`') - result = get_result(konum) + try: + result = get_result(konum) + except BaseException: + return edit(message, f'`{konum} için bir bilgi bulunamadı.`') saat_imsak = ( result.find('div', {'data-vakit-name': 'imsak'}) .find('div', {'class': 'tpt-time'}) @@ -123,14 +129,11 @@ def find_loc(konum): def get_result(konum): - try: - knum = find_loc(konum) - if knum < 0: - raise ValueError - request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') - return BeautifulSoup(request.content, 'html.parser') - except TypeError: - return f'`{konum} için bir bilgi bulunamadı.`' + knum = find_loc(konum) + if knum < 0: + raise ValueError + request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') + return BeautifulSoup(request.content, 'html.parser') def calculate_time(saat, yarin_saat): diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 76e9a5e..2ab43b9 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -12,7 +12,15 @@ from pyrogram import enums from pyrogram.types import ChatPermissions from sedenbot import BRAIN, HELP, LOGS, TEMP_SETTINGS -from sedenecem.core import edit, extract_args, get_translation, sedenify, send_log +from sedenbot.modules.ban import get_reason +from sedenecem.core import ( + edit, + extract_args_arr, + extract_user, + get_translation, + sedenify, + send_log, +) def globals_init(): @@ -34,21 +42,14 @@ def globals_init(): @sedenify(pattern='^.gban', compat=False) def gban_user(client, message): - args = extract_args(message) reply = message.reply_to_message edit(message, f'`{get_translation("banProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + + reason = get_reason(message) try: replied_user = reply.from_user @@ -57,50 +58,51 @@ def gban_user(client, message): except BaseException: pass - if user.id in BRAIN: - return edit( - message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), - ) - - try: - if sql.is_gbanned(user.id): - return edit(message, f'`{get_translation("alreadyBanned")}`') - sql.gban(user.id) - edit( - message, - get_translation('gbanResult', ['**', user.first_name, user.id, '`']), - ) + for user in find_user: + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), + ) try: - common_chats = client.get_common_chats(user.id) - for i in common_chats: - i.ban_member(user.id) - except BaseException: - pass - sleep(1) - send_log(get_translation('gbanLog', [user.first_name, user.id, '`'])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + if sql.is_gbanned(user.id): + return edit(message, f'`{get_translation("alreadyBanned")}`') + sql.gban(user.id) + edit( + message, + get_translation( + 'gbanResult', + ['**', user.first_name, user.id, '`', reason if reason else ''], + ), + ) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.ban_member(user.id) + except BaseException: + pass + sleep(1) + send_log( + get_translation( + 'gbanLog', [user.first_name, user.id, '`', reason if reason else ''] + ) + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return @sedenify(pattern='^.(ung|gun)ban', compat=False) def ungban_user(client, message): - args = extract_args(message) + args = extract_args_arr(message) + if len(args) > 1: + return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message edit(message, f'`{get_translation("unbanProcess")}`') - if args: - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') try: replied_user = reply.from_user @@ -109,51 +111,57 @@ def ungban_user(client, message): except BaseException: pass - try: - if not sql.is_gbanned(user.id): - return edit(message, f'`{get_translation("alreadyUnbanned")}`') - sql.ungban(user.id) - - def find_me(dialog): - try: - return dialog.chat.get_member(me_id).can_restrict_members - except BaseException: - return False + for user in find_user: + try: + if not sql.is_gbanned(user.id): + return edit(message, f'`{get_translation("alreadyUnbanned")}`') + sql.ungban(user.id) + + def find_me(dialog): + try: + return ( + dialog.chat.get_member(me_id).privileges + and dialog.chat.get_member( + me_id + ).privileges.can_restrict_members + ) + except BaseException: + return False + + def find_member(dialog): + try: + return ( + dialog.chat.get_member(user.id) + and dialog.chat.get_member(user.id).restricted_by + and dialog.chat.get_member(user.id).restricted_by.id == me_id + ) + except BaseException: + return False - def find_member(dialog): try: - return ( - dialog.chat.get_member(user.id) - and dialog.chat.get_member(user.id).restricted_by - and dialog.chat.get_member(user.id).restricted_by.id == me_id - ) + dialogs = client.get_dialogs() + me_id = TEMP_SETTINGS['ME'].id + chats = [ + dialog.chat + for dialog in dialogs + if ( + dialog.chat.type + in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP] + and find_me(dialog) + and find_member(dialog) + ) + ] + for chat in chats: + chat.unban_member(user.id) except BaseException: - return False - - try: - dialogs = client.get_dialogs() - me_id = TEMP_SETTINGS['ME'].id - chats = [ - dialog.chat - for dialog in dialogs - if ( - dialog.chat.type - in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP] - and find_me(dialog) - and find_member(dialog) - ) - ] - for chat in chats: - chat.unban_member(user.id) - except BaseException: - pass - edit( - message, - get_translation('unbanResult', ['**', user.first_name, user.id, '`']), - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + pass + edit( + message, + get_translation('unbanResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return @sedenify(pattern='^.listgban$') @@ -169,36 +177,29 @@ def gbanlist(message): return edit(message, gban_list) -@sedenify(incoming=True, outgoing=False, compat=False) -def gban_check(client, message): - if sql.is_gbanned(message.from_user.id): +@sedenify(incoming=True, outgoing=False) +def gban_check(message): + user = message.from_user + if sql.is_gbanned(user.id): try: - user_id = message.from_user.id - chat_id = message.chat.id - client.ban_chat_member(chat_id, user_id) + chat = message.chat + chat.ban_member(user.id) except BaseException: pass message.continue_propagation() -@sedenify(pattern='^.gmute', compat=False) +@sedenify(pattern='^.(ung|gun)mute', compat=False) def gmute_user(client, message): - args = extract_args(message) reply = message.reply_to_message edit(message, f'`{get_translation("muteProcess")}`') - if len(args): - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + + reason = get_reason(message) try: replied_user = reply.from_user @@ -207,50 +208,52 @@ def gmute_user(client, message): except BaseException: pass - if user.id in BRAIN: - return edit( - message, - get_translation('brainError', ['`', '**', user.first_name, user.id]), - ) - - try: - if sql2.is_gmuted(user.id): - return edit(message, f'`{get_translation("alreadyMuted")}`') - sql2.gmute(user.id) - edit( - message, - get_translation('gmuteResult', ['**', user.first_name, user.id, '`']), - ) + for user in find_user: + if user.id in BRAIN: + return edit( + message, + get_translation('brainError', ['`', '**', user.first_name, user.id]), + ) try: - common_chats = client.get_common_chats(user.id) - for i in common_chats: - i.restrict_member(user.id, permissions=ChatPermissions()) - except BaseException: - pass - sleep(1) - send_log(get_translation('gmuteLog', [user.first_name, user.id, '`'])) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + if sql2.is_gmuted(user.id): + return edit(message, f'`{get_translation("alreadyMuted")}`') + sql2.gmute(user.id) + edit( + message, + get_translation( + 'gmuteResult', + ['**', user.first_name, user.id, '`', reason if reason else ''], + ), + ) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.restrict_member(user.id, permissions=ChatPermissions()) + except BaseException: + pass + sleep(1) + send_log( + get_translation( + 'gmuteLog', + [user.first_name, user.id, '`', reason if reason else ''], + ) + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return @sedenify(pattern='^.ungmute', compat=False) def ungmute_user(client, message): - args = extract_args(message) + args = extract_args_arr(message) + if len(args) > 1: + return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message edit(message, f'`{get_translation("unmuteProcess")}`') - if len(args): - try: - user = client.get_users(args) - except Exception: - edit(message, f'`{get_translation("banFailUser")}`') - return - elif reply: - user_id = reply.from_user.id - user = client.get_users(user_id) - else: - edit(message, f'`{get_translation("banFailUser")}`') - return + + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') try: replied_user = reply.from_user @@ -259,23 +262,24 @@ def ungmute_user(client, message): except BaseException: pass - try: - if not sql2.is_gmuted(user.id): - return edit(message, f'`{get_translation("alreadyUnmuted")}`') - sql2.ungmute(user.id) + for user in find_user: try: - common_chats = client.get_common_chats(user.id) - for i in common_chats: - i.unban_member(user.id) - except BaseException: - pass - edit( - message, - get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), - ) - except Exception as e: - edit(message, get_translation('banError', ['`', '**', e])) - return + if not sql2.is_gmuted(user.id): + return edit(message, f'`{get_translation("alreadyUnmuted")}`') + sql2.ungmute(user.id) + try: + common_chats = client.get_common_chats(user.id) + for i in common_chats: + i.unban_member(user.id) + except BaseException: + pass + edit( + message, + get_translation('unmuteResult', ['**', user.first_name, user.id, '`']), + ) + except Exception as e: + edit(message, get_translation('banError', ['`', '**', e])) + return @sedenify(pattern='^.listgmute$') @@ -291,16 +295,16 @@ def gmutelist(message): return edit(message, gmute_list) -@sedenify(incoming=True, outgoing=False, compat=False) -def gmute_check(client, message): - if sql2.is_gmuted(message.from_user.id): +@sedenify(incoming=True, outgoing=False) +def gmute_check(message): + user = message.from_user + if sql2.is_gmuted(user.id): sleep(0.1) message.delete() try: - user_id = message.from_user.id - chat_id = message.chat.id - client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions()) + chat = message.chat + chat.restrict_member(user.id, permissions=ChatPermissions()) except BaseException: pass diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index dcd57e7..f84fa54 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -9,12 +9,14 @@ from json import JSONDecodeError +from pyrogram import enums from requests import get from sedenbot import HELP from sedenecem.core import edit, get_translation, sedenify def parseShipEntity(jsonEntity: dict) -> str: + text = get_translation( 'shippingResult', [ @@ -35,7 +37,8 @@ def parseShipEntity(jsonEntity: dict) -> str: '</u>', ], ) - movements = get_translation( + if jsonEntity['data']['movements']: + movements = get_translation( 'shippingMovements', [ '<code>', @@ -47,8 +50,8 @@ def parseShipEntity(jsonEntity: dict) -> str: jsonEntity['data']['movements'][0]['action'], ], ) + text += movements - text += movements return text @@ -73,7 +76,12 @@ def shippingTrack(message): if len(argv) > 2: edit(message, f"`{get_translation('wrongCommand')}`") return - comp, trackId = argv + + try: + comp, trackId = argv + except ValueError: + return edit(message, f"`{get_translation('wrongCommand')}`") + if not trackId: edit(message, f"`{get_translation('wrongCommand')}`") return @@ -95,7 +103,7 @@ def shippingTrack(message): kargo_data = getShipEntity(company="hepsijet", trackId=trackId) if kargo_data: text = parseShipEntity(kargo_data) - edit(message, text, parse='HTML') + edit(message, text, parse=enums.ParseMode.HTML) return edit(message, f"`{get_translation('shippingNoResult')}`") diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/locks.py index 2744df3..eeb5da0 100644 --- a/sedenbot/modules/locks.py +++ b/sedenbot/modules/locks.py @@ -13,7 +13,7 @@ @sedenify(pattern=r'^.(un|)lock', compat=False, private=False, admin=True) -def lock(client, message): +def lock_unlock_chat(client, message): text = (message.text or message.caption).replace(r'\s+', ' ').split(' ', 1) unlock = parse_cmd(text[0])[:2] == 'un' @@ -21,86 +21,71 @@ def lock(client, message): edit(message, f"`{get_translation('wrongCommand')}`") return - kilit = text[1].lower() + args = text[1].lower() msg = None media = None - sticker = None - gif = None - gamee = None - ainline = None + other = None webprev = None gpoll = None adduser = None cpin = None changeinfo = None - if kilit == 'msg': + if args == 'msg': msg = unlock - kullanim = get_translation('lockMsg') - elif kilit == 'media': + usage = get_translation('lockMsg') + elif args == 'media': media = unlock - kullanim = get_translation('lockMedia') - elif kilit == 'gif': - gif = unlock - sticker = gif - kullanim = get_translation('lockGif') - elif kilit == 'game': - gamee = unlock - kullanim = get_translation('lockGame') - elif kilit == 'inline': - ainline = unlock - kullanim = get_translation('lockInline') - elif kilit == 'web': + usage = get_translation('lockMedia') + elif args == 'other': + other = unlock + usage = get_translation('lockOther') + elif args == 'web': webprev = unlock - kullanim = get_translation('lockWeb') - elif kilit == 'poll': + usage = get_translation('lockWeb') + elif args == 'poll': gpoll = unlock - kullanim = get_translation('lockPoll') - elif kilit == 'invite': + usage = get_translation('lockPoll') + elif args == 'invite': adduser = unlock - kullanim = get_translation('lockInvite') - elif kilit == 'pin': + usage = get_translation('lockInvite') + elif args == 'pin': cpin = unlock - kullanim = get_translation('lockPin') - elif kilit == 'info': + usage = get_translation('lockPin') + elif args == 'info': changeinfo = unlock - kullanim = get_translation('lockInformation') - elif kilit == 'all': + usage = get_translation('lockInformation') + elif args == 'all': msg = unlock media = unlock - gif = unlock - gamee = unlock - ainline = unlock + other = unlock webprev = unlock gpoll = unlock adduser = unlock cpin = unlock changeinfo = unlock - kullanim = get_translation('lockAll') + usage = get_translation('lockAll') else: - if not kilit: + if not args: edit( message, - get_translation('locksUnlockNoArgs' if unlock else 'locksLockNoArgs'), + get_translation('locksUnlockNoArgs' if usage else 'locksLockNoArgs'), ) return else: - edit(message, get_translation('lockError', ['`', kilit])) + edit(message, get_translation('lockError', ['`', args])) return - kilitle = client.get_chat(message.chat.id) + chat = client.get_chat(message.chat.id) - msg = get_on_none(msg, kilitle.permissions.can_send_messages) - media = get_on_none(media, kilitle.permissions.can_send_media_messages) - sticker = get_on_none(sticker, kilitle.permissions.can_send_stickers) - gif = get_on_none(gif, kilitle.permissions.can_send_animations) - gamee = get_on_none(gamee, kilitle.permissions.can_send_games) - ainline = get_on_none(ainline, kilitle.permissions.can_use_inline_bots) - webprev = get_on_none(webprev, kilitle.permissions.can_add_web_page_previews) - gpoll = get_on_none(gpoll, kilitle.permissions.can_send_polls) - adduser = get_on_none(adduser, kilitle.permissions.can_invite_users) - cpin = get_on_none(cpin, kilitle.permissions.can_pin_messages) - changeinfo = get_on_none(changeinfo, kilitle.permissions.can_change_info) + msg = get_on_none(msg, chat.permissions.can_send_messages) + media = get_on_none(media, chat.permissions.can_send_media_messages) + other = get_on_none(other, chat.permissions.can_send_other_messages) + webprev = get_on_none(webprev, chat.permissions.can_add_web_page_previews) + gpoll = get_on_none(gpoll, chat.permissions.can_send_polls) + adduser = get_on_none(adduser, chat.permissions.can_invite_users) + cpin = get_on_none(cpin, chat.permissions.can_pin_messages) + changeinfo = get_on_none(changeinfo, chat.permissions.can_change_info) try: client.set_chat_permissions( @@ -108,10 +93,7 @@ def lock(client, message): ChatPermissions( can_send_messages=msg, can_send_media_messages=media, - can_send_stickers=sticker, - can_send_animations=gif, - can_send_games=gamee, - can_use_inline_bots=ainline, + can_send_other_messages=other, can_add_web_page_previews=webprev, can_send_polls=gpoll, can_change_info=changeinfo, @@ -122,7 +104,7 @@ def lock(client, message): edit( message, get_translation( - 'locksUnlockSuccess' if unlock else 'locksLockSuccess', ['`', kullanim] + 'locksUnlockSuccess' if unlock else 'locksLockSuccess', ['`', usage] ), ) except BaseException as e: diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 6397f1a..283eb79 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -38,6 +38,7 @@ def pmpermit_init(): @sedenify( incoming=True, outgoing=True, + disable_edited=True, disable_notify=True, group=False, compat=False, diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index 9a89a96..0bcdf23 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -22,6 +22,7 @@ class SWClient: compat=False, outgoing=False, incoming=True, + disable_edited=True, disable_notify=True, ) def spamwatch_action(client, message): @@ -44,7 +45,7 @@ def spamwatch_action(client, message): client.block_user(uid) else: myself = message.chat.get_member('me') - if myself.can_restrict_members: + if myself.privileges and myself.privileges.can_restrict_members: message.chat.ban_member(uid) reply(message, text) else: diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index cdb7d7e..b4b4352 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -10,9 +10,10 @@ from os import makedirs from re import escape, sub from subprocess import STDOUT, CalledProcessError, check_output +from typing import List from pyrogram import enums -from pyrogram.types import Message +from pyrogram.types import Message, User from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, TEMP_SETTINGS, app MARKDOWN_FIX_CHAR = '\u2064' @@ -406,3 +407,40 @@ def __status_out__(cmd, encoding='utf-8'): if encoding != 'latin-1': return __status_out__(cmd, 'latin-1') raise e + + +def extract_user(message: Message) -> List[User]: + users: List[User] = [] + mentions = None + + if message.text and not mentions: + try: + users.append(message._client.get_users(message.text.split()[1])) + except BaseException: + pass + + if message.reply_to_message: + users.append(message.reply_to_message.from_user) + + if message.entities: + mentions = [ + entity + for entity in message.entities + if entity.type == enums.MessageEntityType.TEXT_MENTION + ] + no_username = [ + i.user for i in mentions if i.type == enums.MessageEntityType.TEXT_MENTION + ] + users += no_username + + for i in mentions: + try: + users.append( + message._client.get_users( + message.text[i.offset : i.offset + i.length] + ) + ) + except BaseException: + pass + + return users diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index aad6a52..ab9a498 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -14,7 +14,7 @@ from traceback import format_exc from pyrogram import ContinuePropagation, StopPropagation, enums, filters -from pyrogram.handlers import MessageHandler +from pyrogram.handlers import MessageHandler, EditedMessageHandler from sedenbot import BLACKLIST, BOT_VERSION, BRAIN, TEMP_SETTINGS, app, get_translation from .misc import _parsed_prefix, edit, get_cmd, is_admin @@ -25,6 +25,7 @@ def sedenify(**args): pattern = args.get('pattern', None) outgoing = args.get('outgoing', True) incoming = args.get('incoming', False) + disable_edited = args.get('disable_edited', False) disable_notify = args.get('disable_notify', False) compat = args.get('compat', True) brain = args.get('brain', False) @@ -158,6 +159,8 @@ def wrap(client, message): else: filter = (filters.me | filters.incoming) & ~filters.bot + if not disable_edited: + app.add_handler(EditedMessageHandler(wrap, filter)) app.add_handler(MessageHandler(wrap, filter)) return msg_decorator diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 016e6b4..8f75736 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -1,6 +1,6 @@ { "added": "added", - "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply>\nUsage: Pins the replied message.\n\n.unpin <reply> & .unpin all\nUsage: Unpin the pinned message(s).\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.\n\n.setgpic\nUsage: Changes group picture.", + "adminInfo": ".promote <username/reply> <custom rank (optional)>\nUsage: Provides admin rights to the person in the chat.\n\n.demote <username/reply>\nUsage: Revokes the person's admin permissions in the chat.\n\n.ban <username/reply>\nUsage: Bans the person off your chat.\n\n.unban <username/reply>\nUsage: Removes the ban from the person in the chat.\n\n.mute <username/reply>\nUsage: Mutes the person in the chat.\n\n.unmute <username/reply>\nRemoves the person from the muted list.\n\n.kick <username/reply>\nUsage: Kicks user.\n\n.pin <reply> & .pin loud <reply>\nUsage: Pins the replied message.\n\n.unpin <reply> & .unpin all\nUsage: Unpin the pinned message(s).\n\n.admins\nUsage: Retrieves a list of admins in the chat.\n\n.users\nUsage: Retrieves all users in the chat.\n\n.usersdel\nUsage: Retrieves all deleted users in the chat.\n\n.bots\nUsage: Retrieves a list of bots in the chat.\n\n.zombies\nUsage: Finds deleted account(s) that are in a group. Use '.zombies clean' command to remove deleted account(s) from the group.\n\n.setgpic\nUsage: Changes group picture.", "adminUsage": "I'm not admin!", "adminlist": "%1Admins in%1 %2%3%2%1:%1", "afkEnd": "I'm no longer AFK.", @@ -60,9 +60,10 @@ "banAdminError": "I guess i haven't enough perm for this.", "banError": "%1Something went wrong!%1\n\n%2%3%2", "banFailUser": "Please specify a valid user!", - "banLog": "#BAN\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)", + "banLog": "#BAN\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)\n%6", "banProcess": "Whacking the pest!", - "banResult": "%1[%2](tg://user?id=%3)%1 %4banned!%4", + "banReason": "%1Reason:%1 %2%3%2", + "banResult": "%1[%2](tg://user?id=%3)%1 %4banned!%4\n%5", "barcodeInfo": ".barcode <text>\nUsage: Make a barcode from provided content.\nEg: .barcode https://devotag.com\n\nNote: Use .decode command to get decoded content.", "barcodeUsage": "%1Usage:%1 %2.barcode <text>%2", "bioSuccess": "Successfully changed Bio.", @@ -195,8 +196,8 @@ "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 is %2\u00bd gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 This person is %1\u00bd gay!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 I am %1\u00bd gay!", - "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)", - "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4", + "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)\n%4", + "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4\n%5", "gbannedUsers": "GBanned Users:", "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", @@ -230,8 +231,8 @@ "gitWebsite": "Website", "globalsInfo": ".gban <username/reply>\nUsage: Bans the person in all groups you have in common with them.\n\n.ungban <username/reply>\nUsage: Removes the person from the gbanned list\n\n.listgban\nUsage: List GBanned users.\n\n.gmute <username/reply>\nUsage: Mutes the person in all groups you have in common with them.\n\n.ungmute <username/reply>\nUsage: Removes the person from the gmuted list\n\n.listgmute\nUsage: List GMuted users.", "globalsSqlLog": "Unable to run GBan and GMute command, no SQL connection found", - "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2) (%3%2%3)", - "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4", + "gmuteLog": "#GMUTE\nUSER: [%1](tg://user?id=%2) (%3%2%3)\n%4", + "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4globally muted!%4\n%5", "gmutedUsers": "GMuted Users:", "goodbyeMsg": "Goodbye\u2026", "googleDesc": "No description found.", @@ -264,9 +265,9 @@ "kangstr7": "Ay look over there (\u2609\uff61\u2609)!\u2192\nWhile I kang this\u2026", "kangstr8": "Roses are red violets are blue, kanging this sticker so my pacc looks cool", "kangstr9": "Imprisoning this sticker\u2026", - "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%3%4)", + "kickLog": "#KICK\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)\n%6", "kickProcess": "Kicking\u2026", - "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4", + "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4\n%5", "kickmeResult": "Goodbye.. i go away \ud83e\udd20", "langName": "English", "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key missing! Add it to config vars.%2", @@ -279,14 +280,12 @@ "loadedModulesError": "An error occurred while loading %1", "lockAll": "all", "lockError": "%1Invalid media type:%1 %2", - "lockGame": "game", - "lockGif": "gif", - "lockInfo": ".lock <all (or) type(s)> or .unlock <all (or) type(s)>\nUsage: Allows you to lock/unlock some common message types in the chat.\n[NOTE: Requires proper admin rights in the chat!]\n\nAvailable message types to lock/unlock are:\nall, msg, media, sticker, gif, game, inline, web, poll, invite, pin, info", + "lockInfo": ".lock <all (or) type(s)> or .unlock <all (or) type(s)>\nUsage: Allows you to lock/unlock some common message types in the chat.\n[NOTE: Requires proper admin rights in the chat!]\n\nAvailable message types to lock/unlock are:\nall, msg, media, other, web, poll, invite, pin, info", "lockInformation": "info", - "lockInline": "inline", "lockInvite": "invite", "lockMedia": "media", "lockMsg": "message", + "lockOther": "GIF, games, inline bots and sticker", "lockPerm": "%1Are you sure you have the rights to do this?%1\n%2Error:%2 %3", "lockPin": "pin", "lockPoll": "poll", @@ -308,13 +307,13 @@ "makeqrInfo": ".makeqr <text>\nUsage: Make a QR code from provided content.\nEg: .makeqr https://devotag.com\n\nNote: Use .decode command to get decoded content.", "makeqrUsage": "%1Usage:%1 %2.makeqr <text>%2", "mediaInvalid": "Invalid media.", - "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your bot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus", + "memesInfo": ".cowsay\nUsage: cow which says things.\n\n:/\nUsage: Check yourself ;)\n\n.cp\nUsage: Copypasta the famous meme\n\n.vapor\nUsage: Vaporize everything!\n\n.str\nUsage: Stretch it.\n\n.10iq\nUsage: You retard !!\n\n.mizah\nUsage: Measure your stupidity !!\n\n.zal\nUsage: Invoke the feeling of chaos.\n\noof\nUsage: Ooooof\n\nskrrt\nUsage: skrrrrt\n\n.owo\nUsage: UwU\n\n.react\nUsage: Make your bot react to everything.\n\n.cry\nUsage: y u du dis, i cri.\n\n.shg\nUsage: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nUsage: Let Me Run, run, RUNNN!\n\n.mock\nUsage: Do it and find the real fun.\n\n.clap\nUsage: Praise people!\n\n.f <emoji/character>\nUsage: Pay Respects.\n\n.type\nUsage: Just a small command to make your keyboard become a typewriter!\n\n.lfy <query>\nUsage: Let me Google that for you real quick !!\n\n.xda or reply to someone's text with .xda\nUsage: Famous words of XDA.\n\n.amogus <text>\nUsage: Converts typed text to amogus\n\n.mem <top text>,<bottom text>\nUsage: Make meme with top and bottom text", "mirrorError": "Error: Different mirror not found for the link", "miscInfo": ".id\nUsage: Fetches the ID of the user in reply, if its a forwarded message, finds the ID for the source.\n\n.chatid\nUsage: Fetches the current chat's ID\n\n.kickme\nUsage: Leave from a targeted group.\n\n.repeat <count> <text>\nUsage: Repeats the text for a number of times. Don't confuse this with spam tho.\n\n.random <item1> <item2> \u2026 <itemN>\nUsage: Get a random item from the list of items.\n\n.founder\nUsage: Know who created this awesome bot :-)\n\n.support\nUsage: Use this command if you need help.\n\n.repo\nUsage: Seden UserBot GitHub Repo\n\n.readme\nUsage: A link to the README file of the Seden bot on GitHub.\n\n.tagall\nUsage: When you use this command, it tags everyone in the chat.\n\n.admin\nUsage: When you use this command, it tags all admins in the chat.\n\n.hash\nUsage: Find the md5, sha1, sha256, sha512 of the string when written into a txt file.\n\n.base64\nUsage: Find the base64 encoding of the given string. Eg: .base64 en hello\n\n.ascii\nUsage: Sends the replied photo or sticker in ASCII.\n\n.invitelink\nUsage: Gives invite link on current chat.", "mockUsage": "gIvE sOMEtHInG tO MoCk!", - "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)", + "muteLog": "#MUTE\nUSER: [%1](tg://user?id=%2) (%4%2%4)\nGROUP: %3 (%4%5%4)\n%6", "muteProcess": "Gets a tape!", - "muteResult": "%1[%2](tg://user?id=%3)%1 %4muted!%4", + "muteResult": "%1[%2](tg://user?id=%3)%1 %4muted!%4\n%5", "nameOk": "Your name was succesfully changed.", "neofetchNotFound": "Please install neofetch.", "noFilter": "There are no filters in this chat.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 54335b2..4658d52 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -1,6 +1,6 @@ { "added": "eklendi", - "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin <yan\u0131tlama> & .unpin all\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.\n\n.setgpic\nKullan\u0131m: Grubun foto\u011fraf\u0131n\u0131 de\u011fi\u015ftirir.", + "adminInfo": ".promote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama> <\u00f6zel isim (iste\u011fe ba\u011fl\u0131)>\nKullan\u0131m: Sohbetteki ki\u015fiye y\u00f6netici haklar\u0131 sa\u011flar.\n\n.demote <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin y\u00f6netici izinlerini iptal eder.\n\n.ban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi gruptan yasaklar.\n\n.unban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015finin yasa\u011f\u0131n\u0131 kald\u0131r\u0131r.\n\n.mute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Sohbetteki ki\u015fiyi susturur.\n\n.unmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi sessize al\u0131nanlar listesinden kald\u0131r\u0131r.\n\n.kick <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Kullan\u0131c\u0131y\u0131 gruba geri kat\u0131labilecek \u015fekilde \u00e7\u0131kart\u0131r.\n\n.pin <yan\u0131tlama> & .pin loud <yan\u0131tlama>\nKullan\u0131m: Yan\u0131tlanan mesaj\u0131 sabitler.\n\n.unpin <yan\u0131tlama> & .unpin all\nKullan\u0131m: Sabitlenen mesaj\u0131 sabitden kald\u0131r\u0131r.\n\n.admins\nKullan\u0131m: Sohbetteki y\u00f6neticileri listeler.\n\n.users\nKullan\u0131m: Sohbetteki kullan\u0131c\u0131lar\u0131 listeler.\n\n.usersdel\nKullan\u0131m: Sohbetteki silinmi\u015f hesaplar\u0131 listeler.\n\n.bots\nKullan\u0131m: Sohbetteki botlar\u0131 listeler.\n\n.zombies\nKullan\u0131m: Bir grupta olan silinmi\u015f hesaplar\u0131 bulur. Silinmi\u015f hesaplar\u0131 gruptan atmak i\u00e7in '.zombies clean' komutunu kullan.\n\n.setgpic\nKullan\u0131m: Grubun foto\u011fraf\u0131n\u0131 de\u011fi\u015ftirir.", "adminUsage": "Y\u00f6netici de\u011filim!", "adminlist": "%2%3%2 %1sohbetinde bulunan y\u00f6neticiler:%1", "afkEnd": "Art\u0131k AFK de\u011filim.", @@ -60,9 +60,10 @@ "banAdminError": "San\u0131r\u0131m bunun i\u00e7in yeterli yetkim olmayabilir.", "banError": "%1Bir hata olu\u015ftu!%1\n\n%2%3%2", "banFailUser": "Ge\u00e7erli bir kullan\u0131c\u0131 belirtiniz!", - "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", + "banLog": "#BAN\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)\n%6", "banProcess": "D\u00fc\u015fman vuruldu!", - "banResult": "%1[%2](tg://user?id=%3)%1 %1yasakland\u0131!%1", + "banReason": "%1Sebep:%1 %2%3%2", + "banResult": "%1[%2](tg://user?id=%3)%1 %1yasakland\u0131!%1\n%5", "barcodeInfo": ".barcode <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir barkod yap\u0131n.\n\u00d6rnek: .barcode https://devotag.com\n\nNot: \u00e7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", "barcodeUsage": "%1Kullan\u0131m:%1 %2.barcode <metin>%2", "base64Info": ".base64\nKullan\u0131m: Verilen dizenin base64 kodlamas\u0131n\u0131 bulun. \u00d6rnek .base64 en merhaba", @@ -196,8 +197,8 @@ "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 \u00bd%2 gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 Sen \u00bd%1 gaysin!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 \u00bd%1 gayim!", - "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", - "gbanResult": "%1[%2](tg://user?id=%3)%1 (%4%3%4) %4k\u00fcresel yasakland\u0131!%4", + "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)\n%4", + "gbanResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel yasakland\u0131!%4\n%5", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", @@ -231,8 +232,8 @@ "gitWebsite": "Website", "globalsInfo": ".gban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan yasaklar.\n\n.ungban <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak yasaklananlar listesinden kald\u0131r\u0131r.\n\n.listgban\nKullan\u0131m: K\u00fcresel yasaklanan kullan\u0131c\u0131lar\u0131 listeler.\n\n.gmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi y\u00f6netici oldu\u011funuz t\u00fcm gruplardan susturur.\n\n.ungmute <kullan\u0131c\u0131 ad\u0131/yan\u0131tlama>\nKullan\u0131m: Ki\u015fiyi k\u00fcresel olarak susturulanlar listesinden kald\u0131r\u0131r.\n\n.listgmute\nKullan\u0131m: K\u00fcresel susturulan kullan\u0131c\u0131lar\u0131 listeler.", "globalsSqlLog": "GBan ve GMute komutu \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", - "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)", - "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4", + "gmuteLog": "#GMUTE\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)\n%4", + "gmuteResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel susturuldu!%4\n%5", "gmutedUsers": "K\u00fcresel susturulan kullan\u0131c\u0131lar:", "goodbyeMsg": "G\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "googleDesc": "A\u00e7\u0131klama bulunamad\u0131.", @@ -266,9 +267,9 @@ "kangstr7": "Hey \u015furaya bak. (\u2609\uff61\u2609)!\u2192\nBen bunu d\u0131zlarken\u2026", "kangstr8": "G\u00fcller k\u0131rm\u0131z\u0131 menek\u015feler mavi, bu \u00e7\u0131kartmay\u0131 paketime d\u0131zlayarak haval\u0131 olaca\u011f\u0131m\u2026", "kangstr9": "\u00c7\u0131kartma hapsediliyor\u2026", - "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", + "kickLog": "#KICK\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)\n%6", "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor\u2026", - "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4", + "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4\n%5", "kickmeResult": "G\u00fcle G\u00fcle ben gidiyorum \ud83e\udd20", "langName": "T\u00fcrk\u00e7e", "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key eksik! L\u00fctfen ekleyin.%2", @@ -281,14 +282,12 @@ "loadedModulesError": "%1 mod\u00fcl\u00fc y\u00fcklenirken bir hata olu\u015ftu.", "lockAll": "her \u015fey", "lockError": "%1Ge\u00e7ersiz medya tipi:%1 %2", - "lockGame": "oyun", - "lockGif": "GIF ve \u00e7\u0131kartma yollama", - "lockInfo": ".lock <kilitlenecek medya tipi> veya .unlock <kilitlenecek medya tipi>\nKullan\u0131m: Sohbetteki birtak\u0131m \u015feyleri engelleyebilmeni sa\u011flar. (sticker atmak, oyun oynamak vs.)\n[Not: Y\u00f6netici haklar\u0131 gerektirir!]\n\nKilitleyebilece\u011fin ve kilidini a\u00e7abileceklerin \u015funlard\u0131r:\nall, msg, media, sticker, gif, game, inline, web, poll, invite, pin, info", + "lockInfo": ".lock <kilitlenecek medya tipi> veya .unlock <kilitlenecek medya tipi>\nKullan\u0131m: Sohbetteki birtak\u0131m \u015feyleri engelleyebilmeni sa\u011flar. (sticker atmak, oyun oynamak vs.)\n[Not: Y\u00f6netici haklar\u0131 gerektirir!]\n\nKilitleyebilece\u011fin ve kilidini a\u00e7abileceklerin \u015funlard\u0131r:\nall, msg, media, other, web, poll, invite, pin, info", "lockInformation": "sohbet bilgisi de\u011fi\u015ftirme", - "lockInline": "sohbet i\u00e7i botlar", "lockInvite": "davet etme", "lockMedia": "medya yollama", "lockMsg": "mesaj atma", + "lockOther": "GIF, oyunlar, sohbet i\u00e7i botlar ve \u00e7\u0131kartma yollama", "lockPerm": "%1Bunun i\u00e7in gerekli haklara sahip oldu\u011funa emin misin?%1\n%2Hata:%2 %3", "lockPin": "sabitleme", "lockPoll": "anket yollama", @@ -310,13 +309,13 @@ "makeqrInfo": ".makeqr <metin>\nKullan\u0131m: Verilen i\u00e7erikten bir QR kodu yap\u0131n.\n\u00d6rnek: .makeqr https://devotag.com\n\nNot: \u00c7\u00f6z\u00fclm\u00fc\u015f i\u00e7erik almak i\u00e7in .decode komutunu kullan\u0131n.", "makeqrUsage": "%1Kullan\u0131m:%1 %2.makeqr <metin>%2", "mediaInvalid": "Medya ge\u00e7erli de\u011fil.", - "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.\n\n.amogus <metin>\nKullan\u0131m: Yaz\u0131lan metni amogus yapar", + "memesInfo": ".cowsay\nKullan\u0131m: bir \u015feyler s\u00f6yleyen inek.\n\n:/\nKullan\u0131m: Kendinizi kontrol edin ;)\n\n.cp\nKullan\u0131m: Me\u015fhur copypasta mod\u00fcl\u00fc\n\n.vapor\nKullan\u0131m: Her \u015feyi vaporla\u015ft\u0131r\u0131n!\n\n.str\nKullan\u0131m: Mesaj\u0131 iyice uzat\u0131n.\n\n.10iq\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.mizah\nKullan\u0131m: Aptall\u0131k seviyenizi \u00f6l\u00e7\u00fcn !!\n\n.zal\nKullan\u0131m: Kaos duygusunu \u00e7a\u011f\u0131r\u0131n.\n\noof\nKullan\u0131m: Ooooof\n\nskrrt\nKullan\u0131m: skrrrrt\n\n.owo\nKullan\u0131m: UwU\n\n.react\nKullan\u0131m: UserBot'un her \u015feye tepki vermesini sa\u011flay\u0131n.\n\n.cry\nKullan\u0131m: bunu yaparsan, her zaman a\u011flar\u0131m.\n\n.shg\nKullan\u0131m: \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f\n\n.run\nKullan\u0131m: Seden UserBot'un ko\u015fmas\u0131n\u0131 sa\u011flar!\n\n.mock\nKullan\u0131m: Yap ve ger\u00e7ek e\u011flenceyi bul.\n\n.clap\nKullan\u0131m: \u0130nsanlar\u0131 \u00f6v\u00fcn!\n\n.f <emoji/karakter>\nKullan\u0131m: Sayg\u0131lar..\n\n.type\nKullan\u0131m: Klavyenizi daktilo haline getirmek i\u00e7in k\u00fc\u00e7\u00fck bir komut!\n\n.lfy <sorgu>\nKullan\u0131m: B\u0131rak\u0131n Google bunu sizin i\u00e7in ara\u015ft\u0131rs\u0131n.\n\n.xda veya .xda ile birinin metnine cevap verin.\nKullan\u0131m: XDA'n\u0131n me\u015fhur s\u00f6zleri.\n\n.amogus <metin>\nKullan\u0131m: Yaz\u0131lan metni amogus yapar\n\n.mem <\u00fcst metin>,<alt metin>\nKullan\u0131m: Alt ve \u00fcst metin ile caps yapar", "mirrorError": "Hata: link i\u00e7in farkl\u0131 mirror bulunamad\u0131", "miscInfo": ".id\nKullan\u0131m: Belirlenen kullan\u0131c\u0131n\u0131n ID numaras\u0131n\u0131 verir.\n\n.chatid\nKullan\u0131m: Belirlenen grubun ID numaras\u0131n\u0131 verir\n\n.kickme\nKullan\u0131m: Belirlenen gruptan ayr\u0131lman\u0131z\u0131 sa\u011flar.\n\n.repeat <say\u0131> <metin>\nKullan\u0131m: Bir metni belli bir say\u0131da tekrar eder. Spam komutu ile kar\u0131\u015ft\u0131rma!\n\n.random <e\u015fya1> <e\u015fya2> \u2026 <e\u015fyaN>\nKullan\u0131m: E\u015fya listesinden rastgele bir e\u015fya se\u00e7er\n\n.founder\nKullan\u0131m: Bu g\u00fczel botu kimlerin olu\u015fturdu\u011funu \u00f6\u011fren :-)\n\n.support\nKullan\u0131m: Yard\u0131ma ihtiyac\u0131n olursa bu komutu kullan.\n\n.repo\nKullan\u0131m: Seden UserBot GitHub Depo\n\n.readme\nKullan\u0131m: Seden botunun GitHub'daki README dosyas\u0131na giden bir ba\u011flant\u0131.\n\n.tagall\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki herkesi etiketler.\n\n.admin\nKullan\u0131m: Bu komutu kulland\u0131\u011f\u0131n\u0131zda sohbet i\u00e7erisinde ki y\u00f6neticileri etiketler.\n\n.ascii\nKullan\u0131m: Yan\u0131tlanan foto\u011fraf veya \u00e7\u0131kartmay\u0131 ASCII olarak g\u00f6nderir.\n\n.invitelink\nKullan\u0131m: Mevcut grubun davet ba\u011flant\u0131s\u0131 verir.", "mockUsage": "bANa bIr mETin vEr!", - "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)", + "muteLog": "#MUTE\nKULLANICI: [%1](tg://user?id=%2) (%4%2%4)\nGRUP: %3 (%4%5%4)\n%6", "muteProcess": "Sessize al\u0131n\u0131yor\u2026", - "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4", + "muteResult": "%1[%2](tg://user?id=%3)%1 %4susturuldu!%4\n%5", "nameOk": "Ad\u0131n ba\u015far\u0131yla de\u011fi\u015ftirildi.", "neofetchNotFound": "L\u00fctfen neofetch y\u00fckleyin.", "noFilter": "Bu sohbette hi\u00e7 filtre yok.", From 7d17ed06c385717fb708299acb035775550094b6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 1 May 2022 18:07:12 +0300 Subject: [PATCH 151/242] Release Seden v1.6.5 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index a2f0b62..f9224ca 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.4' +BOT_VERSION = '1.6.5' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 934c945180579ca130f45b141b4751113e974a18 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 1 May 2022 20:38:40 +0300 Subject: [PATCH 152/242] ezanvakti: remove ramazan command Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/ezanvakti.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 508af14..e6bb832 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -54,6 +54,7 @@ def get_val(st): edit(message, vakitler) +""" @sedenify(pattern='^.ramazan') def ramazan(message): konum = extract_args(message).lower() @@ -108,6 +109,7 @@ def ramazan(message): ) edit(message, vakitler) +""" def find_loc(konum): @@ -135,7 +137,7 @@ def get_result(konum): request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') return BeautifulSoup(request.content, 'html.parser') - +""" def calculate_time(saat, yarin_saat): now = datetime.now().timestamp() now_t = datetime.fromtimestamp(now).strftime('%d.%m.%Y') @@ -158,7 +160,7 @@ def timedelta(time): saat = int(time / 3600) dakika = int((time % 3600) / 60) return saat, dakika - +""" sehirler = [ '01 Adana 9146', @@ -248,9 +250,6 @@ def timedelta(time): { "ezanvakti": ".ezanvakti <şehir> \ \nKullanım: Belirtilen şehir için namaz vakitlerini gösterir. \ - \nÖrnek: .ezanvakti istanbul \ - \n.ramazan <şehir> \ - \nKullanım: Belirtilen şehir için ramazan vakitlerini gösterir. \ - \nÖrnek: .ramazan istanbul" + \nÖrnek: .ezanvakti istanbul" } ) From 8fb686148392efc5d50f742995b75e05764700f9 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Tue, 3 May 2022 00:45:47 +0300 Subject: [PATCH 153/242] Fix ungmute regex --- sedenbot/modules/globals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 2ab43b9..98e5779 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -190,7 +190,7 @@ def gban_check(message): message.continue_propagation() -@sedenify(pattern='^.(ung|gun)mute', compat=False) +@sedenify(pattern='^.gmute', compat=False) def gmute_user(client, message): reply = message.reply_to_message edit(message, f'`{get_translation("muteProcess")}`') @@ -243,7 +243,7 @@ def gmute_user(client, message): return -@sedenify(pattern='^.ungmute', compat=False) +@sedenify(pattern='^.(ung|gun)mute', compat=False) def ungmute_user(client, message): args = extract_args_arr(message) if len(args) > 1: From e3605b83fbc5f236d6cd74fad8a882ba9e51ebf2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 3 May 2022 12:28:14 +0300 Subject: [PATCH 154/242] info: improved whois, ginfo Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/info.py | 136 +++++++++++++++++------------------ sedenecem/translator/en.json | 3 +- sedenecem/translator/tr.json | 3 +- 3 files changed, 69 insertions(+), 73 deletions(-) diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 91abdbf..72b9f85 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -14,6 +14,7 @@ from sedenecem.core import ( download_media_wc, edit, + extract_user, extract_args, get_translation, reply_img, @@ -23,82 +24,73 @@ @sedenify(pattern='^.whois', compat=False) def who_is(client, message): - user_info = extract_args(message) + find_user = extract_user(message) reply = message.reply_to_message + media_perm = None edit(message, f'`{get_translation("whoisProcess")}`') - media_perm = True + + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages - if user_info: + for reply_user in find_user: try: - reply_user = client.get_users(user_info) - reply_chat = client.get_chat(user_info) + reply_chat = client.get_chat(reply_user.id) except Exception: - edit(message, f'`{get_translation("whoisError")}`') - return - elif reply: - reply_user = client.get_users(reply.from_user.id) - reply_chat = client.get_chat(reply.from_user.id) + return edit(message, f'`{get_translation("whoisError")}`') + if reply_user or reply_chat is not None: + try: + user_photo = reply_user.photo.big_file_id + photo = download_media_wc(user_photo, 'photo.png') + except BaseException: + photo = None + pass + + first_name = reply_user.first_name or get_translation('notSet') + last_name = reply_user.last_name or get_translation('notSet') + username = ( + f'@{reply_user.username}' + if reply_user.username + else get_translation('notSet') + ) + user_id = reply_user.id + photos = client.get_chat_photos_count(user_id) + dc_id = reply_user.dc_id or get_translation('notSet') + bot = reply_user.is_bot + chats = len(client.get_common_chats(user_id)) + bio = reply_chat.bio or get_translation('notSet') + status = reply_user.status + last_seen = LastSeen(bot, status) + sudo = SudoCheck(user_id) + blacklist = BlacklistCheck(user_id) + + caption = get_translation( + 'whoisResult', + [ + '**', + '`', + first_name, + last_name, + username, + user_id, + photos, + dc_id, + chats, + bio, + last_seen, + sudo if sudo else '', + blacklist if blacklist else '', + ], + ) + + if photo and media_perm: + reply_img(reply or message, photo, caption=caption, delete_file=True) + message.delete() else: - edit(message, f'`{get_translation("whoisError")}`') - return - if reply_user or reply_chat is not None: - try: - user_photo = reply_user.photo.big_file_id - photo = download_media_wc(user_photo, 'photo.png') - except BaseException: - photo = None - pass - - first_name = reply_user.first_name or get_translation('notSet') - last_name = reply_user.last_name or get_translation('notSet') - username = ( - f'@{reply_user.username}' - if reply_user.username - else get_translation('notSet') - ) - user_id = reply_user.id - photos = client.get_chat_photos_count(user_id) - dc_id = reply_user.dc_id - bot = reply_user.is_bot - scam = reply_user.is_scam - verified = reply_user.is_verified - chats = len(client.get_common_chats(user_id)) - bio = reply_chat.bio or get_translation('notSet') - status = reply_user.status - last_seen = LastSeen(bot, status) - sudo = SudoCheck(user_id) - blacklist = BlacklistCheck(user_id) - - caption = get_translation( - 'whoisResult', - [ - '**', - '`', - first_name, - last_name, - username, - user_id, - photos, - dc_id, - bot, - scam, - verified, - chats, - bio, - last_seen, - sudo if sudo else '', - blacklist if blacklist else '', - ], - ) - - if photo and media_perm: - reply_img(reply or message, photo, caption=caption, delete_file=True) - message.delete() - else: - return edit(message, caption) + return edit(message, caption) def LastSeen(bot, status): @@ -106,6 +98,8 @@ def LastSeen(bot, status): return 'BOT' elif status == enums.UserStatus.ONLINE: return get_translation('statusOnline') + elif status == enums.UserStatus.OFFLINE: + return get_translation('statusOffline') elif status == enums.UserStatus.RECENTLY: return get_translation('statusRecently') elif status == enums.UserStatus.LAST_WEEK: @@ -130,17 +124,17 @@ def BlacklistCheck(user_id): def get_chat_info(client, message): args = extract_args(message) reply = message.reply_to_message - group_id = message.chat.id + chat_id = message.chat.id + media_perm = None edit(message, f'`{get_translation("processing")}`') try: - reply_chat = client.get_chat(args or group_id) - peer = client.resolve_peer(args or group_id) + reply_chat = client.get_chat(args or chat_id) + peer = client.resolve_peer(args or chat_id) except PeerIdInvalid: edit(message, f'`{get_translation("groupNotFound")}`') return - media_perm = True if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 8f75736..8a0366e 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -539,6 +539,7 @@ "statsResult": "%1Total Chats:%1 %2%3%2\n%1Channels:%1 %2%4%2\n%1Groups:%1 %2%5%2\n%1Super Groups:%1 %2%6%2\n%1Bots:%1 %2%7%2\n%1PM's:%1 %2%8%2\n%1Unread Messages:%1 %2%9%2", "statusLong": "A long time ago", "statusMonth": "Within the last month", + "statusOffline": "Offline", "statusOnline": "Online", "statusRecently": "Recently", "statusWeek": "Within the last week", @@ -626,7 +627,7 @@ "weatherErrorServer": "Weather information couldn't be retrieved.", "whoisError": "Failed to fetching user..", "whoisProcess": "Fetching user information..", - "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Is Bot:%1 %2%9%2\n%1Is Restricted:%1 %2%10%2\n%1Is Verified by Telegram:%1 %2%11%2\n%1Common chats:%1 %2%12%2\n\n%1Bio:%1 %2%13%2\n%1Last Seen:%1 %2%14%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%15\n%16%1", + "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Common chats:%1 %2%9%2\n\n%1Bio:%1 %2%10%2\n%1Last Seen:%1 %2%11%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%12\n%13%1", "wikiError": "Disambiguated page found.\n\n%1", "wikiError2": "Page not found.\n\n%1", "wikiInfo": ".wiki <query>\nUsage: Does a search on Wikipedia.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 4658d52..0e1c702 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -539,6 +539,7 @@ "statsResult": "%1Toplam Sohbet:%1 %2%3%2\n%1Kanallar:%1 %2%4%2\n%1Gruplar:%1 %2%5%2\n%1S\u00fcper Gruplar:%1 %2%6%2\n%1Botlar:%1 %2%7%2\n%1\u00d6zel Mesajlar:%1 %2%8%2\n%1Okunmam\u0131\u015f Mesajlar:%1 %2%9%2", "statusLong": "Uzun zaman \u00f6nce", "statusMonth": "Bir ay i\u00e7inde", + "statusOffline": "\u00c7evrimd\u0131\u015f\u0131", "statusOnline": "\u00c7evrimi\u00e7i", "statusRecently": "Yak\u0131nlarda", "statusWeek": "Bir hafta i\u00e7inde", @@ -626,7 +627,7 @@ "weatherErrorServer": "Hava durumu bilgisi al\u0131namad\u0131.", "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", "whoisProcess": "Kullan\u0131c\u0131 bilgisi getiriliyor..", - "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Bot Mu:%1 %2%9%2\n%1K\u0131s\u0131tl\u0131 M\u0131:%1 %2%10%2\n%1Telegram Taraf\u0131ndan Onayl\u0131 M\u0131:%1 %2%11%2\n%1Ortak Sohbetler:%1 %2%12%2\n\n%1Biyografi:%1 %2%13%2\n%1Son G\u00f6r\u00fclme:%1 %2%14%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%15\n%16%1", + "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Ortak Sohbetler:%1 %2%9%2\n\n%1Biyografi:%1 %2%10%2\n%1Son G\u00f6r\u00fclme:%1 %2%11%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%12\n%13%1", "wikiError": "Belirsiz bir sayfa bulundu.\n\n%1", "wikiError2": "Arad\u0131\u011f\u0131n\u0131z sayfa bulunamad\u0131.\n\n%1", "wikiInfo": ".wiki <terim>\nKullan\u0131m: Bir Vikipedi aramas\u0131 ger\u00e7ekle\u015ftirir.", From 90a821499710ba01f5429a3ea079868cd8f9c78a Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 3 May 2022 12:28:53 +0300 Subject: [PATCH 155/242] Release Seden v1.6.6 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index f9224ca..5aad4f0 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.5' +BOT_VERSION = '1.6.6' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 185c8f68c2b520b6758cdad604bf68058e053cbd Mon Sep 17 00:00:00 2001 From: Can <proenes03@gmail.com> Date: Sat, 7 May 2022 11:05:46 +0300 Subject: [PATCH 156/242] update pyrogram --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e229ad..75e4e14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.16 +pyrogram==2.0.19 python-barcode python-dotenv qrcode From b99ebf86bda95f75204e5a5cfb7c45ab4965c131 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 8 May 2022 16:17:50 +0300 Subject: [PATCH 157/242] info: fix wrong operator Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 72b9f85..17abd83 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -32,7 +32,7 @@ def who_is(client, message): if len(find_user) < 1: return edit(message, f'`{get_translation("banFailUser")}`') - if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: + if message.chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages @@ -135,7 +135,7 @@ def get_chat_info(client, message): edit(message, f'`{get_translation("groupNotFound")}`') return - if message.chat.type == [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: + if message.chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: perm = message.chat.permissions media_perm = perm.can_send_media_messages From 676d590c609bed4ea9348f19b64d262714618772 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Fri, 20 May 2022 18:33:51 +0300 Subject: [PATCH 158/242] gdrive: Download method changed Drive credentials adding to heroku db default Links download by pysmartDL --- requirements.txt | 1 + sample_config.env | 6 + sedenbot/__init__.py | 8 +- sedenbot/modules/gdrive.py | 366 ++++++++++++++++++++--------------- sedenecem/translator/en.json | 19 +- sedenecem/translator/tr.json | 13 +- 6 files changed, 242 insertions(+), 171 deletions(-) diff --git a/requirements.txt b/requirements.txt index 75e4e14..4096c7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,3 +32,4 @@ tgcrypto urbandic wikipedia yt-dlp +pysmartDL diff --git a/sample_config.env b/sample_config.env index cbd4d66..26df3bb 100644 --- a/sample_config.env +++ b/sample_config.env @@ -47,6 +47,12 @@ SPOTIPY_CLIENT_ID='' # Spotify Client Secret SPOTIPY_CLIENT_SECRET='' +# Gdrive Client ID +DRIVE_CLIENT='' + +# Gdrive Secret +DRIVE_SECRET='' + # Gdrive Folder ID GDRIVE_FOLDER_ID='' diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 5aad4f0..71f5537 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -133,9 +133,15 @@ def set_logger(): # Spoify Client ID SPOTIPY_CLIENT_ID = environ.get('SPOTIPY_CLIENT_ID') -# SPotify Client SECRET +# Spotify Client SECRET SPOTIPY_CLIENT_SECRET = environ.get('SPOTIPY_CLIENT_SECRET') +# Gdrive Client +DRIVE_CLIENT = environ.get('DRIVE_CLIENT') + +# Gdrive Secret +DRIVE_SECRET = environ.get('DRIVE_SECRET') + # Gdrive Folder ID GDRIVE_FOLDER_ID = environ.get('GDRIVE_FOLDER_ID', None) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index fc51760..122b9b4 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -1,16 +1,17 @@ -from io import FileIO -from os import path, remove -from queue import Queue -from re import findall, search -from shutil import copy +import pickle +from os import path, remove, replace +from re import match, search from time import time -from google.oauth2.credentials import Credentials +from urllib.parse import unquote from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload +from httplib2 import Http +from oauth2client.client import FlowExchangeError, OAuth2WebServerFlow from pyrogram.types import Message +from pySmartDL import SmartDL from requests import get -from sedenbot import GDRIVE_FOLDER_ID, HELP, LOGS +from sedenbot import GDRIVE_FOLDER_ID, DRIVE_CLIENT, DRIVE_SECRET, HELP from sedenecem.core import ( download_media_wc, edit, @@ -19,6 +20,55 @@ reply_doc, sedenify, ) +from sedenecem.sql import BASE, SESSION +from sqlalchemy import Column, Integer, LargeBinary + + +class GDriveCreds(BASE): + __tablename__ = 'GDrive' + + user_id = Column(Integer, primary_key=True) + credentials_string = Column(LargeBinary) + + def __init__(self, user_id): + self.user_id = user_id + + +GDriveCreds.__table__.create(checkfirst=True) + + +def set(user_id, credentials): + saved_creds = SESSION.query(GDriveCreds).get(user_id) + if not saved_creds: + saved_creds = GDriveCreds(user_id) + saved_creds.credentials_string = pickle.dumps(credentials) + + SESSION.add(saved_creds) + SESSION.commit() + + +def get(user_id): + saved_creds = SESSION.query(GDriveCreds).get(user_id) + creds = None + if saved_creds is not None: + creds = pickle.loads(saved_creds.credentials_string) + return creds + + +def remove_(user_id): + saved_cred = SESSION.query(GDriveCreds).get(user_id) + if saved_cred: + SESSION.delete(saved_cred) + SESSION.commit() + + +def extract_code(url) -> str: + if 'error' in url: + return '' + if '%2F' in url: + url = unquote(url) + code = search('code=(\d\/.*)&', url)[1] + return code class Progress: @@ -27,7 +77,7 @@ def __init__(self, msg: Message, file_name, start_time): self.file_name = file_name self.start_time = start_time - def pyrogram_download(self, current, total): + def download(self, current, total): percentage = float(current * 100 / total) if (curr_time := time()) - self.start_time > 5: self.start_time = curr_time @@ -38,77 +88,95 @@ def pyrogram_download(self, current, total): ) ) + def upload(self, current, total): + percentage = float(current * 100 / total) + if (curr_time := time()) - self.start_time > 5: + self.start_time = curr_time + self.msg.edit_text( + get_translation( + 'pyrogramUp', + ['**', '`', self.file_name, f'½{round(percentage, 2)}'], + ) + ) -class WebHelper: + +class Gdrive: def __init__(self, message: Message): self.message = message + self.dl_path = '.' + self.service = build('drive', 'v3', credentials=get(self.message.from_user.id)) + + def download_link(self, url): + try: + dl = SmartDL(urls=url, dest=self.dl_path, progress_bar=False) + dl.start(blocking=False) + while not dl.isFinished(): + if dl.isFinished(): + break + edit( + self.message, + get_translation( + 'gdriveEta', + [ + '**', + '`', + dl.get_dest(), + dl.get_speed(human=True), + dl.get_eta(human=True), + round(dl.get_progress(), 2), + ], + ), + ) + return dl.get_dest() + except: + return edit(self.message, f'Bir Hatayla Karşılaşıldı') - def download_link(self, queue: Queue): - while not queue.empty(): - url = queue.get() - re_url = search("^https://drive.google.com", url) - if re_url: - self.download_link_gdrive(url) + def upload_to_telegram(self, url): + file_id = search('d\/(.*)\/v', url).group(1) - queue.task_done() - else: - r = get(url, stream=True) - - total_size = int(r.headers.get('content-length', 0)) - downloaded_size = 0 - - if 'Content-Disposition' in r.headers.keys(): - fname = findall("filename=(.+)", r.headers["Content-Disposition"])[ - 0 - ] - else: - fname = url.split('/')[-1] - - start_time = time() - with open(f'./{fname}', 'wb') as f: - for chunk in r.iter_content(chunk_size=50 * 1024 * 1024): - f.write(chunk) - downloaded_size += int(len(chunk)) - percentage = round(float(downloaded_size * 100 / total_size), 2) - print(f'Hız {downloaded_size / total_size}\n') - if (curr_time := time()) - start_time > 5: - start_time = curr_time - edit( - self.message, - get_translation( - 'reqDown', ['**', '`', fname, f'½{percentage}'] - ), - ) - self.upload_to_gdrive(fname) - del fname - - queue.task_done() - - def download_link_gdrive(self, link): - file_id = search('d\/(.*)\/v', link).group(1) + file_info = ( + self.service.files() + .get(fileId=file_id, fields='size,name', supportsAllDrives=True) + .execute() + ) + size = file_info.get('size') + file_name = file_info.get('name') + + if int(size) > 2147483648: + return edit(self.message, get_translation('tgUpLimit', ['`'])) + else: + file_name = self.download_from_gdrive(url) + start_time = time() + progress = Progress(self.message, file_name, start_time) + + reply_doc( + self.message, + file_name, + progress=progress.upload, + ) + self.message.delete() - scopes = ['https://www.googleapis.com/auth/drive'] - creds = Credentials.from_authorized_user_file('token.json', scopes) - service = build('drive', 'v3', credentials=creds) + def download_from_gdrive(self, link) -> str: + file_id = search('d\/(.*)\/v', link).group(1) file_info = ( - service.files() - .get(fileId=file_id, fields='name,size', supportsTeamDrives=True) + self.service.files() + .get(fileId=file_id, fields='name,size', supportsAllDrives=True) .execute() ) - request = service.files().get_media(fileId=file_id, supportsTeamDrives=True) + request = self.service.files().get_media(fileId=file_id, supportsAllDrives=True) fh = open(f'{file_info.get("name")}', 'wb') downloader = MediaIoBaseDownload( - fd=fh, request=request, chunksize=50 * 1024 * 1024 + fd=fh, request=request, chunksize=100 * 1024 * 1024 ) done = False while done is False: status, done = downloader.next_chunk() progress = edit( + self.message, get_translation( - self.message, 'gdriveDown', [ '**', @@ -116,29 +184,24 @@ def download_link_gdrive(self, link): file_info.get('name'), f'½{int(status.progress() * 100)}', ], - ) + ), ) edit( self.message, get_translation('gdriveDownComplete', ['**', '`', file_info.get('name')]), ) - self.upload_to_gdrive(file_info.get("name")) + return file_info.get("name") def upload_to_gdrive(self, filename): - - scopes = ['https://www.googleapis.com/auth/drive'] - creds = Credentials.from_authorized_user_file('token.json', scopes) - service = build('drive', 'v3', credentials=creds) - file_metadata = { 'name': filename, 'parents': [GDRIVE_FOLDER_ID], } - media = MediaFileUpload(filename, resumable=True, chunksize=50 * 1024 * 1024) + media = MediaFileUpload(filename, resumable=True, chunksize=100 * 1024 * 1024) - file = service.files().create( - body=file_metadata, media_body=media, fields='id', supportsTeamDrives=True + file = self.service.files().create( + body=file_metadata, media_body=media, fields='id', supportsAllDrives=True ) response = None start_time = time() @@ -162,124 +225,105 @@ def upload_to_gdrive(self, filename): remove(filename) -@sedenify(pattern='.gauth') +flow = None + + +@sedenify(pattern='^.gauth') def drive_auth(message): - msg = message.reply_to_message - if msg and msg.document: - download_media_wc(data=msg, file_name='token.json') - copy('./downloads/token.json', '.') - edit(message, get_translation('gauthTokenSucces', ['`'])) + global flow + user_id = message.from_user.id + args = extract_args(message).split() + + if len(args) == 0: + creds = get(user_id) + if creds is not None: + creds.refresh(Http()) + set(user_id, creds) + else: + OAUTH_SCOPE = "https://www.googleapis.com/auth/drive" + REDIRECT_URI = "http://localhost:8080" + + flow = OAuth2WebServerFlow( + DRIVE_CLIENT, DRIVE_SECRET, OAUTH_SCOPE, redirect_uri=REDIRECT_URI + ) + auth_url = flow.step1_get_authorize_url() + edit( + message, + f'{get_translation("gauthURL", ["**","`"])} [Google Drive Auth URL]({auth_url})', + ) + + elif args[0] == 'token': + url = args[1] + code = extract_code(url) + if not code: + return edit(message, f'`{get_translation("gauthTokenErr")}`') + if flow: + try: + creds = flow.step2_exchange(code) + set(user_id, creds) + edit(message, f'`{get_translation("gauthTokenSuccess")}`') + except FlowExchangeError: + edit( + message, + f'`{get_translation("gauthTokenInvalid")}`', + ) + flow = None + else: + edit(message, f'`{get_translation("gauthFirstRun")}`') + elif args[0] == 'revoke': + remove_(user_id) + edit(message, f'`{get_translation("gauthTokenRevoke")}`') else: - edit(message, get_translation('gdriveTokenErr', ['`'])) + edit(message, get_translation('gdriveUsage')) -@sedenify(pattern='.gupload') +@sedenify(pattern='^.gupload') def drive_upload(message): - if path.exists('token.json'): - pass - else: - return edit(message, get_translation('gauthTokenErr', ['`'])) + if get(message.from_user.id) is None: + return edit(message, f'`{get_translation("gauthFirstRun")}`') + drive = Gdrive(message) reply = message.reply_to_message - if reply: + if reply and reply.document: file_name = reply.document.file_name start_time = time() progress = Progress(message, file_name, start_time) - down_file = download_media_wc( - reply, file_name, progress=progress.pyrogram_download - ) - - scopes = ['https://www.googleapis.com/auth/drive'] - creds = Credentials.from_authorized_user_file('token.json', scopes) - service = build('drive', 'v3', credentials=creds) - - file_metadata = { - 'name': file_name, - 'parents': [GDRIVE_FOLDER_ID], - } + down_file = download_media_wc(reply, file_name, progress=progress.download) + replace(path.join('downloads', file_name), file_name) - media = MediaFileUpload(down_file, resumable=True, chunksize=50 * 1024 * 1024) + drive.upload_to_gdrive(file_name) - file = service.files().create( - body=file_metadata, media_body=media, fields='id', supportsTeamDrives=True - ) - start_time = time() - response = None - while response is None: - status, response = file.next_chunk() - if status: - if (curr_time := time()) - start_time > 5: - start_time = curr_time - edit( - message, - get_translation( - 'gdriveUp', - ['**', '`', file_name, int(status.progress() * 100)], - ), - ) - edit(message, get_translation('gdriveUpComplete', ['**', '`', file_name])) - remove(down_file) else: - args = extract_args(message).split(' ') - queue = Queue() - for i in args: - queue.put(i) + args = extract_args(message) + if not args.startswith('https://' or 'http://'): + return edit(message, f'`Geçerli bir url girin.`') - web = WebHelper(message) - web.download_link(queue) + is_drive = match("^https://drive.google.com", args) + if is_drive: + dl = drive.download_from_gdrive(args) + else: + dl = drive.download_link(args) + + drive.upload_to_gdrive(dl) @sedenify(pattern='.gdownload') def gdownload(message): - if path.exists('token.json'): - pass + if get(message.from_user.id) is None: + return edit(message, f'`{get_translation("gauthFirstRun")}`') else: - return edit(message, get_translation('gauthTokenErr', ['`'])) + pass args = extract_args(message) - file_id = search('d\/(.*)\/v', args).group(1) - - scopes = ['https://www.googleapis.com/auth/drive'] - creds = Credentials.from_authorized_user_file('token.json', scopes) - service = build('drive', 'v3', credentials=creds) - - file_info = ( - service.files() - .get(fileId=file_id, fields='name,size', supportsTeamDrives=True) - .execute() - ) - - if int(file_info.get('size')) >= 2147483648: - return edit(message, get_translation('tgUpLimit', ['`'])) - request = service.files().get_media(fileId=file_id, supportsTeamDrives=True) - - fh = FileIO(f'./downloads/{file_info.get("name")}', 'wb') - downloader = MediaIoBaseDownload(fd=fh, request=request, chunksize=50 * 1024 * 1024) - - done = False - while done is False: - status, done = downloader.next_chunk() - progress = edit( - message, - get_translation( - 'gdriveDown', - ['**', '`', file_info.get('name'), int(status.progress() * 100)], - ), - ) - start_time = time() - progress = Progress(message, file_info.get('name'), start_time) + if not match('^https://drive\.google\.com', args): + return edit(message, f'`{get_translation("onlySupportGdrive")}`') - reply_doc( - message, - f'./downloads/{file_info.get("name")}', - progress=progress.pyrogram_download, - delete_orig=False, - ) - message.delete() + drive = Gdrive(message) + drive.upload_to_telegram(args) HELP.update({'gdrive': get_translation('gdriveUsage')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 8a0366e..e4f85a9 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -191,20 +191,25 @@ "filterUpdated": "%2Filter%2 %1%3%1 %2updated%2", "filtersSqlLog": "Unable to run filters module, no SQL connection found", "founderResult": "%1=======================================\n\nThis bot;\nDeveloped by%1 %2[NaytSeyd](https://t.me/NightShade)%2 %1and%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2\n%1In addition\nLovingly edited by%1 %2[Sedenogen](https://t.me/CiyanogenOneTeams)%2\n\n%1=======================================%1", - "gauthTokenErr": "%1Token file is missing.%1", - "gauthTokenSucces": "%1Token file created.%1", + "gauthFirstRun": "First try .gauth command for this operation.", + "gauthTokenErr": "There is a error in link you sending.Try .gauth command", + "gauthTokenInvalid": "Invalid code.Reuse .gauth command for generating new code.", + "gauthTokenRevoke": "Saved token removed succesfully.", + "gauthTokenSuccess": "Token saved successfully.", + "gauthURL": "%1Log in via the link below and use the received link with example command%1\n%1Example:%1 %2.gauth token <URL>%2\n\n", "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 is %2\u00bd gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 This person is %1\u00bd gay!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 I am %1\u00bd gay!", "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)\n%4", "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4\n%5", "gbannedUsers": "GBanned Users:", - "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", + "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", + "gdriveEta": "%1Filename:%1 %2%3%2\n%1Speed:%1 %2%4%2\n%1ETA:%1 %2%5%2\n%1Downloaded:%1 %2%6%2", "gdriveTokenErr": "%1Reply a token file%1", - "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%%4%2", + "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%4%2", "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", - "gdriveUsage": ".gauth <token_file>\nReply with token file.It needed for authentication.\n\n.gupload <reply_message> or <link>\nReply files or paste link for uploading to google drive.\n\n.gdownload <link>\nPaste link for uploading files to telegram.", + "gdriveUsage": ".gauth\nIt needed for authentication.It give you auth url.\n\n.gauth token <URL>\nGive the url you received from .gauth command for saving token\n\n.gauth revoke\nDelete token from db\n\n.gupload <reply_message> or <link>\nReply files or paste link for uploading to google drive.\n\n.gdownload <link>\nPaste link for uploading files to telegram.Limit 2GB", "geniusToken": "Please set the Genius token. Thank you!", "gitAccount": "Account Type", "gitBio": "Bio", @@ -346,6 +351,7 @@ "ofrpNotFound": "%1%2 codename probably doesn't belong to an official device. You can check it at%1 %3", "ofrpUrl": "https://orangefox.download/en", "ofrpUsage": "Usage: .orangefox <codename> eg: .orangefox raphael", + "onlySupportGdrive": "Only support Google drive link.", "outputTooLarge": "Output is too large, sending as a file.", "owoUsage": "UwU no text given!", "packFull": "%1Switching to Pack%1 %2%3%2 %1due to insufficient space..%1", @@ -396,7 +402,8 @@ "purgeUsage": "I need a message to start purging from.", "purgemeInfo": ".purgeme <X>\nUsage: Deletes x amount of your latest messages.", "purgemeUsage": "Purgeme failed, please specify number.", - "pyrogramDown": "%Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", + "pyrogramDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", + "pyrogramUp": "%1Filename:%1 %2%3%2\n%1Uploaded%1 %2%4%2", "pythonVersionError": "You must have at least a Python version 3.8\nMultiple features depend on this. Bot quitting.", "quotlyInfo": ".q\nUsage: Enhance ur text to sticker.", "randomResult": "%1Query:%1\n%2%3%2\n%1Output:%1\n%2%4%2", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 0e1c702..a6e8768 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -192,8 +192,12 @@ "filterUpdated": "%2Filtre%2 %1%3%1 %2g\u00fcncellendi%2", "filtersSqlLog": "Filters mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", "founderResult": "%1=======================================\n\nBu bot;%1\n%2[NaytSeyd](https://t.me/NightShade)%2 %1ve%1 %2[frknkrc44](https://t.me/KaldirimMuhendisi)%2 %1taraf\u0131ndan geli\u015ftirilmektedir.\nEk olarak%1\n%2[Sedenogen](https://t.me/CiyanogenOneTeams)%2 %1taraf\u0131ndan sevgi ile d\u00fczenlenmi\u015ftir.\n\n=======================================%1", - "gauthTokenErr": "%1Token dosyas\u0131 eksik.%1", - "gauthTokenSucces": "%1Token dosyas\u0131 olu\u015fturuldu.%1", + "gauthFirstRun": "\u0130lk \u00f6nce .gauth komutunu \u00e7al\u0131\u015ft\u0131r\u0131n.", + "gauthTokenErr": "G\u00f6nderdi\u011finiz linkte hata mevcut.Yeniden .gauth komutu \u00e7al\u0131\u015ft\u0131r\u0131n.", + "gauthTokenInvalid": "G\u00f6nderilen Kod Ge\u00e7ersiz.Tekrar .gauth komutunu deneyin.", + "gauthTokenRevoke": "Kay\u0131tl\u0131 token ba\u015far\u0131yla silindi.", + "gauthTokenSuccess": "Token ba\u015far\u0131yla kaydedildi.", + "gauthURL": "%1A\u015fa\u011f\u0131daki ba\u011flant\u0131 \u00fczerinden giri\u015f yap ve ald\u0131\u011f\u0131n yeni ba\u011flant\u0131 ile komutlar\u0131 kullan%1\n%1\u00d6rnek:%1 %2.gauth token <URL>%2\n\n", "gayString": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 %1 \u00bd%2 gay!", "gayString2": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 Sen \u00bd%1 gaysin!", "gayString3": "\ud83c\udff3\ufe0f\u200d\ud83c\udf08 \u00bd%1 gayim!", @@ -202,10 +206,11 @@ "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", + "gdriveEta": "%1Dosya Ad\u0131:%1 %2%3%2\n%1H\u0131z:%1 %2%4%2\n%1Kalan S\u00fcre:%1 %2%5%2\n%1\u0130ndirildi:%1 %2%6%2", "gdriveTokenErr": "%1Sadece token Dosyas\u0131n\u0131 yan\u0131tlay\u0131n.%1", "gdriveUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi:%1 %2%4%2", "gdriveUpComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya Y\u00fcklendi.%1", - "gdriveUsage": ".gauth <token_file>\nToken dosyas\u0131n\u0131 .gauth ile yan\u0131tlay\u0131n.Yetkilendirme i\u00e7in gereklidir.\n\n.gupload <reply_message> yada <link>\nTelegrama at\u0131lm\u0131\u015f bir dosyay\u0131 .gupload ile yan\u0131tlay\u0131n veya link belirtin.Yant\u0131lanan dosyay\u0131 veya linki google drive'a y\u00fckler.\n\n.gdownload <link>\nKi\u015fisel drive'\u0131n\u0131zdan veya google drive linkinden dosyay\u0131 indirir ve telegrama y\u00fckler.", + "gdriveUsage": ".gauth \nYetkilendirme i\u00e7in gereklidir.\n\n.gauth token <URL>\n.gauth kullanarak ald\u0131\u011f\u0131n\u0131z url yi girin.Tokeni kaydeder.\n\n.gauth revoke\nGoogle drive tokenini siler.\n\n.gupload <reply_message> yada <link>\nTelegrama at\u0131lm\u0131\u015f bir dosyay\u0131 .gupload ile yan\u0131tlay\u0131n veya link belirtin.Yant\u0131lanan dosyay\u0131 veya linki google drive'a y\u00fckler.\n\n.gdownload <link>\nKi\u015fisel drive'\u0131n\u0131zdan veya google drive linkinden dosyay\u0131 indirir ve telegrama y\u00fckler.S\u0131n\u0131r 2GB", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", "gitAccount": "Kullan\u0131c\u0131 tipi", "gitBio": "Biyografi", @@ -348,6 +353,7 @@ "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.%1 %3 %1adresinden kontrol edebilirsiniz.%1", "ofrpUrl": "https://orangefox.download/tr-TR", "ofrpUsage": "Kullan\u0131m: .orangefox <kod ad\u0131> \u00d6rnek: .orangefox raphael", + "onlySupportGdrive": "Yanl\u0131zca Google Drive linkleri destekleniyor.", "outputTooLarge": "\u00c7\u0131kt\u0131 \u00e7ok b\u00fcy\u00fck, dosya olarak g\u00f6nderiliyor.", "owoUsage": "UwU bana bir metin ver!", "packFull": "%1Yetersiz alandan dolay\u0131%1 %2%3%2 %1numaral\u0131 pakete ge\u00e7iliyor..%1", @@ -398,6 +404,7 @@ "purgemeInfo": ".purgeme <X>\nKullan\u0131m: Hedeflenen yan\u0131ttan ba\u015flayarak t\u00fcm mesajlar\u0131 temizler..", "purgemeUsage": "Temizlik yap\u0131lamad\u0131, say\u0131 ge\u00e7ersiz.", "pyrogramDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", + "pyrogramUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi%1 %2%4%2", "pythonVersionError": "En az Python 3.8 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", "quotlyInfo": ".q\nKullan\u0131m: Metninizi \u00e7\u0131kartmaya d\u00f6n\u00fc\u015ft\u00fcr\u00fcn.", "randomResult": "%1Sorgu:%1\n%2%3%2\n%1\u00c7\u0131kt\u0131:%1\n%2%4%2", From cc4af73613d8c0d9336e665e65d28fcdde6efad0 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Fri, 20 May 2022 19:11:40 +0300 Subject: [PATCH 159/242] gdrive: Add license --- sedenbot/modules/gdrive.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 122b9b4..929f725 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -1,3 +1,12 @@ +# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + import pickle from os import path, remove, replace from re import match, search From ac5504ca8f0ba5aeb7ba04a047535dfe953a7091 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sun, 22 May 2022 18:19:53 +0300 Subject: [PATCH 160/242] gdrive:Move sql function to sedenecem/sql directory Added google drive auth refresh token information text Changed single quotes to double quotes --- sedenbot/modules/gdrive.py | 139 +++++++++++++---------------------- sedenecem/sql/gdrive_sql.py | 42 +++++++++++ sedenecem/translator/en.json | 1 + sedenecem/translator/tr.json | 1 + 4 files changed, 94 insertions(+), 89 deletions(-) create mode 100644 sedenecem/sql/gdrive_sql.py diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 929f725..47ae64d 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -7,12 +7,11 @@ # All rights reserved. See COPYING, AUTHORS. # -import pickle from os import path, remove, replace from re import match, search from time import time - from urllib.parse import unquote + from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload from httplib2 import Http @@ -20,7 +19,7 @@ from pyrogram.types import Message from pySmartDL import SmartDL from requests import get -from sedenbot import GDRIVE_FOLDER_ID, DRIVE_CLIENT, DRIVE_SECRET, HELP +from sedenbot import DRIVE_CLIENT, DRIVE_SECRET, GDRIVE_FOLDER_ID, HELP from sedenecem.core import ( download_media_wc, edit, @@ -29,54 +28,15 @@ reply_doc, sedenify, ) -from sedenecem.sql import BASE, SESSION -from sqlalchemy import Column, Integer, LargeBinary - - -class GDriveCreds(BASE): - __tablename__ = 'GDrive' - - user_id = Column(Integer, primary_key=True) - credentials_string = Column(LargeBinary) - - def __init__(self, user_id): - self.user_id = user_id - - -GDriveCreds.__table__.create(checkfirst=True) - - -def set(user_id, credentials): - saved_creds = SESSION.query(GDriveCreds).get(user_id) - if not saved_creds: - saved_creds = GDriveCreds(user_id) - saved_creds.credentials_string = pickle.dumps(credentials) - - SESSION.add(saved_creds) - SESSION.commit() - - -def get(user_id): - saved_creds = SESSION.query(GDriveCreds).get(user_id) - creds = None - if saved_creds is not None: - creds = pickle.loads(saved_creds.credentials_string) - return creds - - -def remove_(user_id): - saved_cred = SESSION.query(GDriveCreds).get(user_id) - if saved_cred: - SESSION.delete(saved_cred) - SESSION.commit() +from sedenecem.sql.gdrive_sql import get, remove_, set def extract_code(url) -> str: - if 'error' in url: - return '' - if '%2F' in url: + if "error" in url: + return "" + if "%2F" in url: url = unquote(url) - code = search('code=(\d\/.*)&', url)[1] + code = search("code=(\d\/.*)&", url)[1] return code @@ -92,8 +52,8 @@ def download(self, current, total): self.start_time = curr_time self.msg.edit_text( get_translation( - 'pyrogramDown', - ['**', '`', self.file_name, f'½{round(percentage, 2)}'], + "pyrogramDown", + ["**", "`", self.file_name, f"½{round(percentage, 2)}"], ) ) @@ -103,8 +63,8 @@ def upload(self, current, total): self.start_time = curr_time self.msg.edit_text( get_translation( - 'pyrogramUp', - ['**', '`', self.file_name, f'½{round(percentage, 2)}'], + "pyrogramUp", + ["**", "`", self.file_name, f"½{round(percentage, 2)}"], ) ) @@ -112,8 +72,8 @@ def upload(self, current, total): class Gdrive: def __init__(self, message: Message): self.message = message - self.dl_path = '.' - self.service = build('drive', 'v3', credentials=get(self.message.from_user.id)) + self.dl_path = "." + self.service = build("drive", "v3", credentials=get(self.message.from_user.id)) def download_link(self, url): try: @@ -125,10 +85,10 @@ def download_link(self, url): edit( self.message, get_translation( - 'gdriveEta', + "gdriveEta", [ - '**', - '`', + "**", + "`", dl.get_dest(), dl.get_speed(human=True), dl.get_eta(human=True), @@ -138,21 +98,21 @@ def download_link(self, url): ) return dl.get_dest() except: - return edit(self.message, f'Bir Hatayla Karşılaşıldı') + return edit(self.message, f"Bir Hatayla Karşılaşıldı") def upload_to_telegram(self, url): - file_id = search('d\/(.*)\/v', url).group(1) + file_id = search("d\/(.*)\/v", url).group(1) file_info = ( self.service.files() - .get(fileId=file_id, fields='size,name', supportsAllDrives=True) + .get(fileId=file_id, fields="size,name", supportsAllDrives=True) .execute() ) - size = file_info.get('size') - file_name = file_info.get('name') + size = file_info.get("size") + file_name = file_info.get("name") if int(size) > 2147483648: - return edit(self.message, get_translation('tgUpLimit', ['`'])) + return edit(self.message, get_translation("tgUpLimit", ["`"])) else: file_name = self.download_from_gdrive(url) start_time = time() @@ -166,16 +126,16 @@ def upload_to_telegram(self, url): self.message.delete() def download_from_gdrive(self, link) -> str: - file_id = search('d\/(.*)\/v', link).group(1) + file_id = search("d\/(.*)\/v", link).group(1) file_info = ( self.service.files() - .get(fileId=file_id, fields='name,size', supportsAllDrives=True) + .get(fileId=file_id, fields="name,size", supportsAllDrives=True) .execute() ) request = self.service.files().get_media(fileId=file_id, supportsAllDrives=True) - fh = open(f'{file_info.get("name")}', 'wb') + fh = open(f'{file_info.get("name")}', "wb") downloader = MediaIoBaseDownload( fd=fh, request=request, chunksize=100 * 1024 * 1024 ) @@ -186,31 +146,31 @@ def download_from_gdrive(self, link) -> str: progress = edit( self.message, get_translation( - 'gdriveDown', + "gdriveDown", [ - '**', - '`', - file_info.get('name'), - f'½{int(status.progress() * 100)}', + "**", + "`", + file_info.get("name"), + f"½{int(status.progress() * 100)}", ], ), ) edit( self.message, - get_translation('gdriveDownComplete', ['**', '`', file_info.get('name')]), + get_translation("gdriveDownComplete", ["**", "`", file_info.get("name")]), ) return file_info.get("name") def upload_to_gdrive(self, filename): file_metadata = { - 'name': filename, - 'parents': [GDRIVE_FOLDER_ID], + "name": filename, + "parents": [GDRIVE_FOLDER_ID], } media = MediaFileUpload(filename, resumable=True, chunksize=100 * 1024 * 1024) file = self.service.files().create( - body=file_metadata, media_body=media, fields='id', supportsAllDrives=True + body=file_metadata, media_body=media, fields="id", supportsAllDrives=True ) response = None start_time = time() @@ -222,14 +182,14 @@ def upload_to_gdrive(self, filename): edit( self.message, get_translation( - 'gdriveUp', - ['**', '`', filename, f'½{int(status.progress() * 100)}'], + "gdriveUp", + ["**", "`", filename, f"½{int(status.progress() * 100)}"], ), ) else: edit( self.message, - get_translation('gdriveUpComplete', ['**', '`', filename]), + get_translation("gdriveUpComplete", ["**", "`", filename]), ) remove(filename) @@ -237,7 +197,7 @@ def upload_to_gdrive(self, filename): flow = None -@sedenify(pattern='^.gauth') +@sedenify(pattern="^.gauth") def drive_auth(message): global flow user_id = message.from_user.id @@ -247,10 +207,11 @@ def drive_auth(message): creds = get(user_id) if creds is not None: creds.refresh(Http()) + edit(message, f'`{get_translation("gdriveAuth")}`') set(user_id, creds) else: OAUTH_SCOPE = "https://www.googleapis.com/auth/drive" - REDIRECT_URI = "http://localhost:8080" + REDIRECT_URI = "http://localhost" flow = OAuth2WebServerFlow( DRIVE_CLIENT, DRIVE_SECRET, OAUTH_SCOPE, redirect_uri=REDIRECT_URI @@ -261,7 +222,7 @@ def drive_auth(message): f'{get_translation("gauthURL", ["**","`"])} [Google Drive Auth URL]({auth_url})', ) - elif args[0] == 'token': + elif args[0] == "token": url = args[1] code = extract_code(url) if not code: @@ -279,14 +240,14 @@ def drive_auth(message): flow = None else: edit(message, f'`{get_translation("gauthFirstRun")}`') - elif args[0] == 'revoke': + elif args[0] == "revoke": remove_(user_id) edit(message, f'`{get_translation("gauthTokenRevoke")}`') else: - edit(message, get_translation('gdriveUsage')) + edit(message, get_translation("gdriveUsage")) -@sedenify(pattern='^.gupload') +@sedenify(pattern="^.gupload") def drive_upload(message): if get(message.from_user.id) is None: @@ -301,15 +262,15 @@ def drive_upload(message): progress = Progress(message, file_name, start_time) down_file = download_media_wc(reply, file_name, progress=progress.download) - replace(path.join('downloads', file_name), file_name) + replace(path.join("downloads", file_name), file_name) drive.upload_to_gdrive(file_name) else: args = extract_args(message) - if not args.startswith('https://' or 'http://'): - return edit(message, f'`Geçerli bir url girin.`') + if not args.startswith("https://" or "http://"): + return edit(message, f"`Geçerli bir url girin.`") is_drive = match("^https://drive.google.com", args) if is_drive: @@ -320,7 +281,7 @@ def drive_upload(message): drive.upload_to_gdrive(dl) -@sedenify(pattern='.gdownload') +@sedenify(pattern=".gdownload") def gdownload(message): if get(message.from_user.id) is None: return edit(message, f'`{get_translation("gauthFirstRun")}`') @@ -328,11 +289,11 @@ def gdownload(message): pass args = extract_args(message) - if not match('^https://drive\.google\.com', args): + if not match("^https://drive\.google\.com", args): return edit(message, f'`{get_translation("onlySupportGdrive")}`') drive = Gdrive(message) drive.upload_to_telegram(args) -HELP.update({'gdrive': get_translation('gdriveUsage')}) +HELP.update({"gdrive": get_translation("gdriveUsage")}) diff --git a/sedenecem/sql/gdrive_sql.py b/sedenecem/sql/gdrive_sql.py new file mode 100644 index 0000000..055fef2 --- /dev/null +++ b/sedenecem/sql/gdrive_sql.py @@ -0,0 +1,42 @@ +from pickle import dumps, loads + +from sedenecem.sql import BASE, SESSION +from sqlalchemy import Column, Integer, LargeBinary + + +class GDriveCreds(BASE): + __tablename__ = 'GDrive' + + user_id = Column(Integer, primary_key=True) + credentials_string = Column(LargeBinary) + + def __init__(self, user_id): + self.user_id = user_id + + +GDriveCreds.__table__.create(checkfirst=True) + + +def set(user_id, credentials): + saved_creds = SESSION.query(GDriveCreds).get(user_id) + if not saved_creds: + saved_creds = GDriveCreds(user_id) + saved_creds.credentials_string = dumps(credentials) + + SESSION.add(saved_creds) + SESSION.commit() + + +def get(user_id): + saved_creds = SESSION.query(GDriveCreds).get(user_id) + creds = None + if saved_creds is not None: + creds = loads(saved_creds.credentials_string) + return creds + + +def remove_(user_id): + saved_cred = SESSION.query(GDriveCreds).get(user_id) + if saved_cred: + SESSION.delete(saved_cred) + SESSION.commit() diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e4f85a9..4173ba5 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -203,6 +203,7 @@ "gbanLog": "#GBAN\nUSER: [%1](tg://user?id=%2) (%3%2%3)\n%4", "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4\n%5", "gbannedUsers": "GBanned Users:", + "gdriveAuth": "Gdrive token is already in database.Token is refreshing...", "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", "gdriveEta": "%1Filename:%1 %2%3%2\n%1Speed:%1 %2%4%2\n%1ETA:%1 %2%5%2\n%1Downloaded:%1 %2%6%2", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index a6e8768..bbcb564 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -204,6 +204,7 @@ "gbanLog": "#GBAN\nKullan\u0131c\u0131: [%1](tg://user?id=%2) (%3%2%3)\n%4", "gbanResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel yasakland\u0131!%4\n%5", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", + "gdriveAuth": "Gdrive token kay\u0131tl\u0131.Token yenileniyor...", "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", "gdriveEta": "%1Dosya Ad\u0131:%1 %2%3%2\n%1H\u0131z:%1 %2%4%2\n%1Kalan S\u00fcre:%1 %2%5%2\n%1\u0130ndirildi:%1 %2%6%2", From fd2d83b2f23b31ff3bc960a106017281aacc9efe Mon Sep 17 00:00:00 2001 From: frknkrc44 <krc440002@gmail.com> Date: Sun, 22 May 2022 20:06:36 +0300 Subject: [PATCH 161/242] Some fixes --- sedenbot/__init__.py | 2 +- sedenecem/core/sedenify.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 71f5537..c582d3f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -170,7 +170,7 @@ def set_logger(): # SQL Database URL DATABASE_URL = environ.get('DATABASE_URL', None) -if DATABASE_URL.startswith('postgres://'): +if DATABASE_URL and DATABASE_URL.startswith('postgres://'): DATABASE_URL = DATABASE_URL.replace('postgres://', 'postgresql://', 1) # SedenBot Session diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index ab9a498..1898295 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -15,6 +15,7 @@ from pyrogram import ContinuePropagation, StopPropagation, enums, filters from pyrogram.handlers import MessageHandler, EditedMessageHandler +from pyrogram.raw.types import MessageActionContactSignUp from sedenbot import BLACKLIST, BOT_VERSION, BRAIN, TEMP_SETTINGS, app, get_translation from .misc import _parsed_prefix, edit, get_cmd, is_admin @@ -57,6 +58,9 @@ def wrap(client, message): if message.service and not service: return + if message.service and isinstance(message.action, MessageActionContactSignUp): + return + if message.chat.type == enums.ChatType.CHANNEL: return From 6d58da6a36aac431b3a487fbf412ddfb5642b72d Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Wed, 25 May 2022 11:24:10 +0300 Subject: [PATCH 162/242] purge:Fix delete all users message --- sedenbot/modules/purge.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index 33354c9..3f051c5 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -10,7 +10,7 @@ from time import sleep from pyrogram.errors import FloodWait -from sedenbot import HELP +from sedenbot import HELP, TEMP_SETTINGS from sedenecem.core import ( edit, extract_args, @@ -51,6 +51,7 @@ def purge(client, message): @sedenify(pattern='^.purgeme', compat=False) def purgeme(client, message): + me = TEMP_SETTINGS['ME'] count = extract_args(message) if not count.isdigit(): return edit(message, f'`{get_translation("purgemeUsage")}`') @@ -60,8 +61,9 @@ def purgeme(client, message): for message in itermsg: if i > int(count) + 1: break - i = i + 1 - message.delete() + if message.from_user.id == me.id: + i = i + 1 + message.delete() smsg = reply(message, get_translation('purgeResult', ['**', '`', str(count)])) send_log(get_translation('purgeLog', ['**', '`', str(count)])) From 3d227e187fb23cf1c6bf9217f6e63b828b8242e8 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sun, 29 May 2022 13:04:46 +0300 Subject: [PATCH 163/242] requirements: Update dependencies --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4096c7f..083ba8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,8 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.19 +pyrogram==2.0.26 +pysmartDL python-barcode python-dotenv qrcode @@ -32,4 +33,3 @@ tgcrypto urbandic wikipedia yt-dlp -pysmartDL From 173bdd12c210d55a955ef812801f98139395927c Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Mon, 13 Jun 2022 15:13:25 +0300 Subject: [PATCH 164/242] spotify_api: Use ThreadPoolExecutor gdrive: Copy google drive file instead download and upload translator: Remove gdriveEta --- sedenbot/modules/gdrive.py | 45 ++++++-- sedenbot/modules/spotify_api.py | 175 ++++++++++++++------------------ sedenecem/translator/en.json | 3 +- sedenecem/translator/tr.json | 3 +- 4 files changed, 119 insertions(+), 107 deletions(-) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 47ae64d..e487f23 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -74,6 +74,11 @@ def __init__(self, message: Message): self.message = message self.dl_path = "." self.service = build("drive", "v3", credentials=get(self.message.from_user.id)) + self.folder_mimetype = "application/vnd.google-apps.folder" + + def get_file_id(self, link): + file_id = search("d\/(.+)\/v", link).group(1) + return file_id def download_link(self, url): try: @@ -85,23 +90,21 @@ def download_link(self, url): edit( self.message, get_translation( - "gdriveEta", + "gdriveDown", [ "**", "`", - dl.get_dest(), - dl.get_speed(human=True), - dl.get_eta(human=True), + path.basename(dl.get_dest()), round(dl.get_progress(), 2), ], ), ) - return dl.get_dest() + return path.basename(dl.get_dest()) except: return edit(self.message, f"Bir Hatayla Karşılaşıldı") def upload_to_telegram(self, url): - file_id = search("d\/(.*)\/v", url).group(1) + file_id = self.get_file_id(url) file_info = ( self.service.files() @@ -126,7 +129,7 @@ def upload_to_telegram(self, url): self.message.delete() def download_from_gdrive(self, link) -> str: - file_id = search("d\/(.*)\/v", link).group(1) + file_id = self.get_file_id(link) file_info = ( self.service.files() @@ -161,6 +164,32 @@ def download_from_gdrive(self, link) -> str: ) return file_info.get("name") + def copy_file_gdrive(self, link): + + edit(self.message, get_translation('processing')) + file_id = self.get_file_id(link) + + meta = ( + self.service.files() + .get( + supportsAllDrives=True, + fileId=file_id, + fields="name,id,mimeType,size", + ) + .execute() + ) + + if meta.get('mimeType') == self.folder_mimetype: + return edit(self.message, f"`{get_translation('gdriveCopyErr')}`") + + body = {'parents': [GDRIVE_FOLDER_ID]} + + self.service.files().copy( + fileId=file_id, body=body, supportsAllDrives=True + ).execute() + + edit(self.message, get_translation('gdriveCopyFile')) + def upload_to_gdrive(self, filename): file_metadata = { "name": filename, @@ -274,7 +303,7 @@ def drive_upload(message): is_drive = match("^https://drive.google.com", args) if is_drive: - dl = drive.download_from_gdrive(args) + return drive.copy_file_gdrive(args) else: dl = drive.download_link(args) diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index ad64b1c..7764727 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -7,10 +7,10 @@ # All rights reserved. See COPYING, AUTHORS. # +from concurrent.futures import ThreadPoolExecutor from glob import glob -from os import mkdir, path, remove -from queue import Queue -from threading import Thread +from os import cpu_count, mkdir, path, remove, rmdir +from threading import Lock from urllib.request import urlretrieve from zipfile import ZipFile @@ -33,21 +33,18 @@ class Spotipy: def __init__(self): self.spotify_dir() + self.sp = Spotify(auth_manager=SpotifyClientCredentials()) def spotify_dir(self): - if path.exists('./Spotify'): - pass + if path.exists('Spotify'): + rmdir('Spotify') + mkdir('Spotify') else: - mkdir('./Spotify') + mkdir('Spotify') - def fetch_token(self): - self.spotify_dir() - sp = Spotify(auth_manager=SpotifyClientCredentials()) - return sp - - def download_track(self, query): + def search_track(self, name: str): ydl_opts = { - 'outtmpl': f'./Spotify/%(title)s.%(ext)s', + 'outtmpl': f'Spotify/{name}.%(ext)s', 'format': 'bestaudio/best', 'addmetadata': True, 'writethumbnail': True, @@ -72,94 +69,73 @@ def download_track(self, query): } with YoutubeDL(ydl_opts) as ydl: - ydl.download([query]) - - def worker(self, q): - while not q.empty(): - item = q.get() - self.download_track(item) - q.task_done() - - def search_track(self, message): - sp = self.fetch_token() - args = extract_args(message) - if 'zip' in args: - args = extract_args(message).split(' ', 3) - playlist_url = args[2] - edit(message, f'`{get_translation("downloadMedia")}`') - zip_file = ZipFile('./Spotify/playlist.zip', 'w') - fields = "items.track.track_number,items.track.name,items.track.artists.name,items.track.album.name,items.track.album.release_date,total,items.track.album.images" - playlist = sp.playlist_items( + ydl.download([name]) + + def fetch_track(self, message, playlist_url: str, is_zip=False): + fullname = [] + offset = 0 + + edit(message, f'`{get_translation("downloadMedia")}`') + + while True: + fields = "items.track.name,items.track.artists.name,total" + playlist = self.sp.playlist_items( playlist_url, fields=fields, + offset=offset, additional_types=['track'], - )['items'] - threads = [] - video_urls = [] - q = Queue() - for item in playlist: - track = f'{item["track"]["name"]} ' + ) + total = playlist['total'] + + for item in playlist['items']: + if item['track'] is None: + offset += 1 + continue + + track = item['track']['name'] artist = item['track']['artists'][0]['name'] - track += artist - video_urls.append(track) - for url in video_urls: - q.put(url) - for i in range(15): - t1 = Thread(target=self.worker, args=(q,), daemon=True) - t1.start() - threads.append(t1) - for t in threads: - t.join() - for i in glob('./Spotify/*.mp3'): - zip_file.write(i) - zip_file.close() - edit(message, f'`{get_translation("uploadingZip")}`') - reply_doc(message, './Spotify/playlist.zip', delete_after_send=True) - message.delete() - for song in glob('./Spotify/*.mp3'): + name = f'{artist} - {track}' + fullname.append(name) + + offset += 1 + + if total == offset: + break + self.download_track(message, fullname, is_zip=is_zip) + + def download_track(self, message, song_list: list, is_zip): + lock = Lock() + with lock: + with ThreadPoolExecutor(min(32, cpu_count() or 0) + 4) as executor: + executor.map(self.search_track, song_list) + + if is_zip: + zipfile = ZipFile('Spotify/playlist.zip', 'w') + for song in glob('Spotify/*.mp3'): + zipfile.write(song) remove(song) + zipfile.close() + edit(message, f'`{get_translation("uploadingZip")}`') + reply_doc( + message, + 'Spotify/playlist.zip', + delete_orig=True, + delete_after_send=True, + ) else: - args = extract_args(message).split(' ', 2) - playlist_url = args[1] - edit(message, f'`{get_translation("downloadMedia")}`') - fields = "items.track.track_number,items.track.name,items.track.artists.name,items.track.album.name,items.track.album.release_date,total,items.track.album.images" - playlist = sp.playlist_items( - playlist_url, - fields=fields, - additional_types=['track'], - )['items'] - threads = [] - video_urls = [] - q = Queue() - for item in playlist: - track = f'{item["track"]["name"]} ' - artist = item['track']['artists'][0]['name'] - track += artist - video_urls.append(track) - for url in video_urls: - q.put(url) - for i in range(15): - t1 = Thread(target=self.worker, args=(q,), daemon=True) - t1.start() - threads.append(t1) - for t in threads: - t.join() - for song in glob('./Spotify/*.mp3'): - reply_audio(message, song, delete_file=True) - for song in glob('./Spotify/*.mp3'): - remove(song) + for song in glob('Spotify/*.mp3'): + reply_audio(message, song, delete_orig=True, delete_file=True) def show_playlist(self, username=None): - sp = self.fetch_token() global counter counter = 0 users_playlists = '\n' if username: - result = sp.user_playlists(username, limit=50) + result = self.sp.user_playlists(username, limit=50) else: - result = sp.user_playlists(username=self.username) + result = self.sp.user_playlists(username=self.username) for item in result['items']: pl_name = item['name'] @@ -173,7 +149,6 @@ def show_users_detail( username, message, ): - sp = self.fetch_token() if username == None: return edit(message, (get_translation("invalidUsername"))) else: @@ -182,10 +157,10 @@ def show_users_detail( edit(message, get_translation("userNotFound", ['**', '`', username])) else: - user = sp.user(username) + user = self.sp.user(username) profile_photo = [i['url'] for i in user['images']] if profile_photo: - r = urlretrieve("".join(profile_photo), './Spotify/pfp.png') + r = urlretrieve("".join(profile_photo), 'Spotify/pfp.png') else: profile_photo = None pass @@ -213,11 +188,11 @@ def show_users_detail( if profile_photo and media_perm: reply_img( message, - photo='./Spotify/pfp.png', + photo='Spotify/pfp.png', caption=out, delete_file=True, + delete_orig=True, ) - message.delete() else: edit(message, out, preview=False) @@ -225,15 +200,21 @@ def show_users_detail( @sedenify(pattern='^.spoti(|fy)') def spotify_download(message): spotify = Spotipy() - args = extract_args(message).split(' ', 2) - if args[0] == 'dl' or args[0] == 'dl zip': - spotify.search_track(message) + args = extract_args(message).split() + + if len(args) == 2 and args[0] == 'dl' and not args[1] == 'zip': + url = args[1] + spotify.fetch_track(message, url) + + elif len(args) == 3 and args[0] == 'dl' and args[1] == 'zip': + url = args[2] + spotify.fetch_track(message, url, is_zip=True) - elif 'show' == args[0]: - if len(args) == 1: + elif len(args) == 2 and args[0] == 'show': + username = args[1] + if len(username) == 1: edit(message, f'`{get_translation("invalidUsername")}`') else: - username = args[1] spotify.show_users_detail(username=username, message=message) else: edit(message, f'`{get_translation("invalidProcess")}`') diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 4173ba5..74da511 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -204,9 +204,10 @@ "gbanResult": "%1[%2](tg://user?id=%3)%1 %4globally banned!%4\n%5", "gbannedUsers": "GBanned Users:", "gdriveAuth": "Gdrive token is already in database.Token is refreshing...", + "gdriveCopyErr": "Gdrive folder not supported,only support files.", + "gdriveCopyFile": "File copied successfully.", "gdriveDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", "gdriveDownComplete": "%1Filename:%1 %2%3%2\n%1File Downloaded.%1", - "gdriveEta": "%1Filename:%1 %2%3%2\n%1Speed:%1 %2%4%2\n%1ETA:%1 %2%5%2\n%1Downloaded:%1 %2%6%2", "gdriveTokenErr": "%1Reply a token file%1", "gdriveUp": "%1Filename:%1 %2%3%2\n%1Uploading:%1 %2%4%2", "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index bbcb564..2697730 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -205,9 +205,10 @@ "gbanResult": "%1[%2](tg://user?id=%3)%1 %4k\u00fcresel yasakland\u0131!%4\n%5", "gbannedUsers": "K\u00fcresel yasaklanan kullan\u0131c\u0131lar:", "gdriveAuth": "Gdrive token kay\u0131tl\u0131.Token yenileniyor...", + "gdriveCopyErr": "Gdrive klas\u00f6rleri desteklenmiyor,yanl\u0131zca dosyalar destekleniyor.", + "gdriveCopyFile": "Dosya ba\u015far\u0131yla kopyaland\u0131.", "gdriveDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "gdriveDownComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya \u0130ndirildi.%1", - "gdriveEta": "%1Dosya Ad\u0131:%1 %2%3%2\n%1H\u0131z:%1 %2%4%2\n%1Kalan S\u00fcre:%1 %2%5%2\n%1\u0130ndirildi:%1 %2%6%2", "gdriveTokenErr": "%1Sadece token Dosyas\u0131n\u0131 yan\u0131tlay\u0131n.%1", "gdriveUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi:%1 %2%4%2", "gdriveUpComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya Y\u00fcklendi.%1", From 89c8d29609b9b0dd7060843bb20a6b4f636ce3d4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 23 Jun 2022 21:42:32 +0300 Subject: [PATCH 165/242] Release Seden v1.6.7 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index c582d3f..d468a07 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.6' +BOT_VERSION = '1.6.7' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 80dfee957cb9286e20d794cc97d4259d72c13789 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 23 Jun 2022 21:49:34 +0300 Subject: [PATCH 166/242] requirements: Update dependencies Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 083ba8e..0f993de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.26 +pyrogram==2.0.30 pysmartDL python-barcode python-dotenv From be6261e20debe1c094abf9bd3627660046ddd3ba Mon Sep 17 00:00:00 2001 From: squirrelpython <dphenix4570@gmail.com> Date: Thu, 23 Jun 2022 21:50:06 +0300 Subject: [PATCH 167/242] Bug fixes + improvements Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- sedenbot/modules/kargotakip.py | 64 +++++++++++++++------------------- sedenbot/modules/scrapers.py | 10 +++--- sedenecem/translator/tr.json | 4 +-- 4 files changed, 37 insertions(+), 43 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index d468a07..af76ded 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -59,7 +59,7 @@ def get_translation(transKey, params: list = None): return ret -if version_info[0] < 3 or version_info[1] < 8: +if version_info[0] < 3 or version_info[1] < 10: LOGS.warn(get_translation('pythonVersionError')) quit(1) diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index f84fa54..204c405 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -12,11 +12,10 @@ from pyrogram import enums from requests import get from sedenbot import HELP -from sedenecem.core import edit, get_translation, sedenify +from sedenecem.core import edit, extract_args, get_translation, parse_cmd, sedenify def parseShipEntity(jsonEntity: dict) -> str: - text = get_translation( 'shippingResult', [ @@ -33,14 +32,16 @@ def parseShipEntity(jsonEntity: dict) -> str: jsonEntity['data']['receiver_unit'], jsonEntity['data']['sended_date'], jsonEntity['data']['delivered_date'] or get_translation('notFound'), - '<u>', - '</u>', ], ) if jsonEntity['data']['movements']: movements = get_translation( 'shippingMovements', [ + '<b>', + '</b>', + '<u>', + '</u>', '<code>', '</code>', jsonEntity['data']['movements'][0]['unit'], @@ -50,8 +51,8 @@ def parseShipEntity(jsonEntity: dict) -> str: jsonEntity['data']['movements'][0]['action'], ], ) - text += movements + text += movements return text @@ -62,45 +63,38 @@ def getShipEntity(company: str, trackId: int or str) -> dict or None: 'secret': 'sMPMnQuc51nmcBbaeOK1', 'unique': 'afb612018716663e', } - response = get(f"https://tapi.kolibu.com/{company}/{trackId}", headers=headers) + response = get(f'https://tapi.kolibu.com/{company}/{trackId}', headers=headers) try: return response.json() if response.json()['success'] else None except JSONDecodeError: return None -@sedenify(pattern='^.(yurtici|aras|ptt|mng|ups|surat|trendyol|hepsiburada)') +@sedenify(pattern='^.(yurti[çc]i|aras|ptt|mng|ups|s[üu]rat|trendyol|hepsijet)') def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") - argv = message.text.split(' ') - if len(argv) > 2: - edit(message, f"`{get_translation('wrongCommand')}`") - return - - try: - comp, trackId = argv - except ValueError: - return edit(message, f"`{get_translation('wrongCommand')}`") - - if not trackId: + trackId = extract_args(message) + comp = parse_cmd(message.text) + if not trackId or len(trackId.split(' ')) > 1: edit(message, f"`{get_translation('wrongCommand')}`") return - if comp == '.yurtici': - kargo_data = getShipEntity(company="yurtici", trackId=trackId) - if comp == '.aras': - kargo_data = getShipEntity(company="aras", trackId=trackId) - if comp == '.ptt': - kargo_data = getShipEntity(company="ptt", trackId=trackId) - if comp == '.mng': - kargo_data = getShipEntity(company="mng", trackId=trackId) - if comp == '.ups': - kargo_data = getShipEntity(company="ups", trackId=trackId) - if comp == '.surat': - kargo_data = getShipEntity(company="surat", trackId=trackId) - if comp == '.trendyol': - kargo_data = getShipEntity(company="trendyolexpress", trackId=trackId) - if comp == '.hepsiburada': - kargo_data = getShipEntity(company="hepsijet", trackId=trackId) + match comp.replace('ç', 'c').replace('ü', 'u'): + case 'yurtici': + kargo_data = getShipEntity(company='yurtici', trackId=trackId) + case 'aras': + kargo_data = getShipEntity(company='aras', trackId=trackId) + case 'ptt': + kargo_data = getShipEntity(company='ptt', trackId=trackId) + case 'mng': + kargo_data = getShipEntity(company='mng', trackId=trackId) + case 'ups': + kargo_data = getShipEntity(company='ups', trackId=trackId) + case 'surat': + kargo_data = getShipEntity(company='surat', trackId=trackId) + case 'trendyol': + kargo_data = getShipEntity(company='trendyolexpress', trackId=trackId) + case 'hepsijet': + kargo_data = getShipEntity(company='hepsijet', trackId=trackId) if kargo_data: text = parseShipEntity(kargo_data) edit(message, text, parse=enums.ParseMode.HTML) @@ -108,4 +102,4 @@ def shippingTrack(message): edit(message, f"`{get_translation('shippingNoResult')}`") -HELP.update({'shippingtrack': get_translation("shippingTrack")}) +HELP.update({'shippingtrack': get_translation('shippingTrack')}) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 8448e7d..db87b25 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -547,11 +547,11 @@ def imeichecker(message): continue result = response['data'] break - reply_text = f"<strong>Sorgu Tarihi</strong> <code>{result['sorguTarihi']}</code>\n\ -<strong>IMEI:</strong> <code>{result['imei'][:-5] + 5*'*'}</code>\n\ -<strong>Durum:</strong> <code>{result['durum']}</code>\n\ -<strong>Cihaz:</strong> <code>{result['markaModel']}</code>\n\n\ -<strong>Powered by </strong><a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden</a>♥" + reply_text = f"<b>Sorgu Tarihi</b> <code>{result['sorguTarihi']}</code>\n\ +<b>IMEI:</b> <code>{result['imei'][:-5] + 5*'*'}</code>\n\ +<b>Durum:</b> <code>{result['durum']}</code>\n\ +<b>Cihaz:</b> <code>{result['markaModel']}</code>\n\n\ +<b>Powered by </b><a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden</a>♥" edit(message, reply_text, parse='HTML', preview=False) except Exception as e: raise e diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2697730..93bf717 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -508,9 +508,9 @@ "sedenUsage": "L\u00fctfen bir Seden mod\u00fcl\u00fc ad\u0131 belirtin.", "sedenUsage2": "%1L\u00fctfen hangi Seden mod\u00fcl\u00fc i\u00e7in yard\u0131m istedi\u011finizi belirtin!\nKullan\u0131m:%1 %2.seden <mod\u00fcl ad\u0131>%2", "sedenVersion": "Bot s\u00fcr\u00fcm\u00fc; Seden v%1", - "shippingMovements": "\n\n%1Yer: %3\nDurum: %4\nTarih: %5\nZaman: %6\n\u0130\u015flem: %7%2", + "shippingMovements": "\n\n%1%3Son hareket%4%2\n\n%5Yer: %7\nDurum: %8\nTarih: %9\nZaman: %10\n\u0130\u015flem: %11%6", "shippingNoResult": "Takip bilgisi bulunamad\u0131!", - "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4\n\n%1%14Son hareket%15%2", + "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4", "shippingTrack": ".<firma> <takipNo>\n\nKullan\u0131m: Kargo bilgilerini g\u00f6sterir.\n\n\u00d6rnek: `.ups 1234567890`\n\nDesteklenen firmalar:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", From 140e72f9a9cc651761c89f62520293bd80b9eb1e Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 23 Jun 2022 21:50:49 +0300 Subject: [PATCH 168/242] Release Seden v1.6.8 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index af76ded..d418af9 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.7' +BOT_VERSION = '1.6.8' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 8ec953ef49360ce69ca3797d62930ad90bc4a887 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Fri, 24 Jun 2022 16:46:00 +0300 Subject: [PATCH 169/242] gdrive:Add premium check & Add 5 second delay to edit message Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/gdrive.py | 29 ++++++++++++++++------------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index e487f23..0fd2c4c 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -84,21 +84,24 @@ def download_link(self, url): try: dl = SmartDL(urls=url, dest=self.dl_path, progress_bar=False) dl.start(blocking=False) + start_time = time() while not dl.isFinished(): if dl.isFinished(): break - edit( - self.message, - get_translation( - "gdriveDown", - [ - "**", - "`", - path.basename(dl.get_dest()), - round(dl.get_progress(), 2), - ], - ), - ) + if (curr_time := time()) - start_time > 5: + start_time = curr_time + edit( + self.message, + get_translation( + "gdriveDown", + [ + "**", + "`", + path.basename(dl.get_dest()), + round(dl.get_progress(), 2), + ], + ), + ) return path.basename(dl.get_dest()) except: return edit(self.message, f"Bir Hatayla Karşılaşıldı") @@ -114,7 +117,7 @@ def upload_to_telegram(self, url): size = file_info.get("size") file_name = file_info.get("name") - if int(size) > 2147483648: + if int(size) > 4294967296 if self.message.from_user.is_premium else 2147483648: return edit(self.message, get_translation("tgUpLimit", ["`"])) else: file_name = self.download_from_gdrive(url) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 74da511..0658527 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -568,7 +568,7 @@ "termUsage": "Please write a command.", "testException": "This is a test, don't submit a bug log.", "testLogId": "Testing LOG_ID value\u2026", - "tgUpLimit": "%1File size bigger than 2Gb.I cant do that.%1", + "tgUpLimit": "%1File size too big.I cant do that.%1", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", "trtError": "Invalid destination language.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 93bf717..b701653 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -568,7 +568,7 @@ "termUsage": "L\u00fctfen bir komut yaz\u0131n.", "testException": "Bu bir test, hata kayd\u0131 g\u00f6ndermeyin.", "testLogId": "LOG_ID de\u011feri test ediliyor\u2026", - "tgUpLimit": "%1Dosya boyutu 2gb dan fazla oldu\u011fu i\u00e7in bunu yapamam.%1", + "tgUpLimit": "%1Dosya boyutu fazla oldu\u011fu i\u00e7in bunu yapamam.%1", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", From 6b67aeba98424fcde01b3461852016ef45e205df Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 24 Jun 2022 22:52:25 +0300 Subject: [PATCH 170/242] =?UTF-8?q?Is=20Premium=20=C2=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/info.py | 2 ++ sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 17abd83..87de197 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -61,6 +61,7 @@ def who_is(client, message): dc_id = reply_user.dc_id or get_translation('notSet') bot = reply_user.is_bot chats = len(client.get_common_chats(user_id)) + premium = reply_user.is_premium bio = reply_chat.bio or get_translation('notSet') status = reply_user.status last_seen = LastSeen(bot, status) @@ -79,6 +80,7 @@ def who_is(client, message): photos, dc_id, chats, + premium, bio, last_seen, sudo if sudo else '', diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 0658527..313d0aa 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -636,7 +636,7 @@ "weatherErrorServer": "Weather information couldn't be retrieved.", "whoisError": "Failed to fetching user..", "whoisProcess": "Fetching user information..", - "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Common chats:%1 %2%9%2\n\n%1Bio:%1 %2%10%2\n%1Last Seen:%1 %2%11%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%12\n%13%1", + "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Common chats:%1 %2%9%2\n%1Is Premium:%1 %2%10%2\n\n%1Bio:%1 %2%11%2\n%1Last Seen:%1 %2%12%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%13\n%14%1", "wikiError": "Disambiguated page found.\n\n%1", "wikiError2": "Page not found.\n\n%1", "wikiInfo": ".wiki <query>\nUsage: Does a search on Wikipedia.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index b701653..edc1fde 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -636,7 +636,7 @@ "weatherErrorServer": "Hava durumu bilgisi al\u0131namad\u0131.", "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", "whoisProcess": "Kullan\u0131c\u0131 bilgisi getiriliyor..", - "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Ortak Sohbetler:%1 %2%9%2\n\n%1Biyografi:%1 %2%10%2\n%1Son G\u00f6r\u00fclme:%1 %2%11%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%12\n%13%1", + "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Ortak Sohbetler:%1 %2%9%2\n%1Premium Kullan\u0131c\u0131:%1 %2%10%2\n\n%1Biyografi:%1 %2%11%2\n%1Son G\u00f6r\u00fclme:%1 %2%12%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%13\n%14%1", "wikiError": "Belirsiz bir sayfa bulundu.\n\n%1", "wikiError2": "Arad\u0131\u011f\u0131n\u0131z sayfa bulunamad\u0131.\n\n%1", "wikiInfo": ".wiki <terim>\nKullan\u0131m: Bir Vikipedi aramas\u0131 ger\u00e7ekle\u015ftirir.", From 5d341827bb6b309f3cdbfc1beeba0b558a5fe5c2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 12 Jul 2022 09:16:20 +0300 Subject: [PATCH 171/242] README: Nuke Heroku * see our site for installation instructions // https://teamderuntergang.github.io // Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 4aff8b9..03d9c8c 100755 --- a/README.md +++ b/README.md @@ -39,9 +39,6 @@ python3 seden.py ### Nix/NixOS Just type `nix-shell` command in bot folder. -## Heroku -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TeamDerUntergang/Telegram-SedenUserBot/tree/seden) - If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.html) page for installation instructions! Questions asked without reading the instruction will not be answered. From 855fb24b397a7160c0f6a154c96195490e543fd2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 20 Jul 2022 13:13:29 +0300 Subject: [PATCH 172/242] Update packages, translations Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 4 ++-- sedenecem/translator/tr.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0f993de..b053487 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ gitpython google-api-python-client google-auth-httplib2 google-auth-oauthlib -googletrans==3.1.0a0 +googletrans==4.0.0rc1 gtts heroku3 humanize @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.30 +pyrogram==2.0.33 pysmartDL python-barcode python-dotenv diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index edc1fde..ab497e4 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -145,7 +145,7 @@ "demoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici de\u011fil!%4", "deviceError": "%1%2cihaz\u0131 i\u00e7in bilgi bulunamad\u0131!%1", "deviceSearch": "%1%2 i\u00e7in arama sonu\u00e7lar\u0131:%1", - "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4", + "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", @@ -352,7 +352,7 @@ "ofrpConnect": "OrangeFox sunucular\u0131na ba\u011flan\u0131l\u0131yor\u2026", "ofrpError": "Muhtemelen bu liste bo\u015f.", "ofrpErrorDate": "Tarih bilgisi \u00e7ekilirken bir hata olu\u015ftu", - "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.%1 %3 %1adresinden kontrol edebilirsiniz.%1", + "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.\n%1 %3 %1adresinden kontrol edebilirsiniz.%1", "ofrpUrl": "https://orangefox.download/tr-TR", "ofrpUsage": "Kullan\u0131m: .orangefox <kod ad\u0131> \u00d6rnek: .orangefox raphael", "onlySupportGdrive": "Yanl\u0131zca Google Drive linkleri destekleniyor.", From 6d5fcda7ec5e459f17f842340abd77376ffbe76d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 20 Jul 2022 13:14:27 +0300 Subject: [PATCH 173/242] Release Seden v1.6.9 Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index d418af9..839bd47 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -120,7 +120,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.8' +BOT_VERSION = '1.6.9' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 5b41a37b5a14a50658ecc51ec75f56a4dcf73cc6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 20 Jul 2022 14:14:12 +0300 Subject: [PATCH 174/242] session: Improve method Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- session.py | 117 +++++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/session.py b/session.py index f9f0a4b..274b6b9 100644 --- a/session.py +++ b/session.py @@ -9,14 +9,18 @@ from pyrogram import Client -lang = input('Select lang (tr, en): ').lower() -if lang == 'en': - print('''\nPlease go to my.telegram.org -Login using your Telegram account -Click on API Development Tools -Create a new application, by entering the required details\n''') +def select_lang(): + lang = '' + while not lang: + lang = input('Select lang (tr, en): ') + + while lang.lower() not in ('tr', 'en'): + lang = input('Select lang (tr, en): ') + return lang + +def get_api_hash(): API_ID = '' API_HASH = '' @@ -27,67 +31,64 @@ while len(API_HASH) != 32: API_HASH = input('API HASH: ') + return API_ID, API_HASH - app = Client( - 'sedenify', - api_id=API_ID, - api_hash=API_HASH) - with app: - self = app.get_me() - session = app.export_session_string() - out = f'''**Hi [{self.first_name}](tg://user?id={self.id}) +def get_session(): + if lang == 'en': + print( + '''\nPlease go to my.telegram.org +Login using your Telegram account +Click on API Development Tools +Create a new application, by entering the required details\n''' + ) + API_ID, API_HASH = get_api_hash() + app = Client('sedenify', api_id=API_ID, api_hash=API_HASH) + with app: + self = app.get_me() + session = app.export_session_string() + out = f'''**Hi [{self.first_name}](tg://user?id={self.id}) \nAPI_ID:** `{API_ID}` \n**API_HASH:** `{API_HASH}` \n**SESSION:** `{session}` \n**NOTE: Don't give your account information to others!**''' - out2 = 'Session successfully generated!' - if self.is_bot: - print(f'{session}\n{out2}') - else: - app.send_message('me', out) - print('''Session successfully generated! -Please check your Telegram Saved Messages''') - - -elif lang == 'tr': - print('''Lütfen my.telegram.org adresine gidin + out2 = 'Session successfully generated!' + if self.is_bot: + print(f'{session}\n{out2}') + else: + app.send_message('me', out) + print( + '''Session successfully generated! +Please check your Telegram Saved Messages''' + ) + + elif lang == 'tr': + print( + '''Lütfen my.telegram.org adresine gidin Telegram hesabınızı kullanarak giriş yapın API Development Tools kısmına tıklayın -Gerekli ayrıntıları girerek yeni bir uygulama oluşturun\n''') - - API_ID = '' - API_HASH = '' - - while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 8: - API_ID = input('API ID: ') - - API_ID = int(API_ID) - - while len(API_HASH) != 32: - API_HASH = input('API HASH: ') - - app = Client( - 'sedenify', - api_id=API_ID, - api_hash=API_HASH) - - with app: - self = app.get_me() - session = app.export_session_string() - out = f'''**Merhaba [{self.first_name}](tg://user?id={self.id}) +Gerekli ayrıntıları girerek yeni bir uygulama oluşturun\n''' + ) + API_ID, API_HASH = get_api_hash() + app = Client('sedenify', api_id=API_ID, api_hash=API_HASH) + with app: + self = app.get_me() + session = app.export_session_string() + out = f'''**Merhaba [{self.first_name}](tg://user?id={self.id}) \nAPI_ID:** `{API_ID}` \n**API_HASH:** `{API_HASH}` \n**SESSION:** `{session}` \n**NOT: Hesap bilgileriniz başkalarına vermeyin!**''' - out2 = 'Session başarıyla oluşturuldu!' - if self.is_bot: - print(f'{session}\n{out2}') - else: - app.send_message('me', out) - print('''Session başarıyla oluşturuldu! -Lütfen Telegram Kayıtlı Mesajlarınızı kontrol edin.''') - - -else: - print('\nWhat? Please select en or tr') + out2 = 'Session başarıyla oluşturuldu!' + if self.is_bot: + print(f'{session}\n{out2}') + else: + app.send_message('me', out) + print( + '''Session başarıyla oluşturuldu! +Lütfen Telegram Kayıtlı Mesajlarınızı kontrol edin.''' + ) + + +lang = select_lang() +get_session() From 97becddbae7899d36b22200a923504e116721b5b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 20 Jul 2022 14:25:38 +0300 Subject: [PATCH 175/242] session: Add dotenv Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- session.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/session.py b/session.py index 274b6b9..99ca723 100644 --- a/session.py +++ b/session.py @@ -7,8 +7,15 @@ # All rights reserved. See COPYING, AUTHORS. # +from os import environ +from os.path import isfile + +from dotenv import load_dotenv from pyrogram import Client +if isfile('config.env'): + load_dotenv('config.env') + def select_lang(): lang = '' @@ -21,8 +28,8 @@ def select_lang(): def get_api_hash(): - API_ID = '' - API_HASH = '' + API_ID = environ.get('API_ID', '') + API_HASH = environ.get('API_HASH', '') while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 8: API_ID = input('API ID: ') From bc96463850ff322be4028ee7fffc6a583059571d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 20 Jul 2022 16:18:13 +0300 Subject: [PATCH 176/242] Update autopp module Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/autopp.py | 11 +++++------ sedenbot/modules/git.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index 66175d6..5400699 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -60,13 +60,12 @@ def autopic(client, message): load.write(get(AUTO_PP).content) else: try: - profile_photo = client.get_profile_photos('me', limit=1) - downloaded_file_name = download_media_wc( - profile_photo[0], downloaded_file_name - ) + for i in client.get_chat_photos('me', limit=1): + downloaded_file_name = download_media_wc( + i.file_id, downloaded_file_name + ) except BaseException: - edit(message, f'`{get_translation("autoppConfig")}`') - return + return edit(message, f'`{get_translation("autoppConfig")}`') edit(message, f'`{get_translation("autoppResult")}`') diff --git a/sedenbot/modules/git.py b/sedenbot/modules/git.py index 26284a9..c662d00 100644 --- a/sedenbot/modules/git.py +++ b/sedenbot/modules/git.py @@ -42,7 +42,7 @@ def return_defval_onnull(jsonkey, defval): user_id = json.get('id', -1) - NULL_TEXT = f'{get_translation("gitNull")}' + NULL_TEXT = get_translation('gitNull') name = return_defval_onnull('name', NULL_TEXT) acc_type = return_defval_onnull('type', 'User') From e72cb7a239b50ca6a46aa7380ad352345d52cc5e Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 21 Jul 2022 10:51:31 +0300 Subject: [PATCH 177/242] scrapers: Fix import error Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/scrapers.py | 7 ++----- sedenecem/translator/tr.json | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index db87b25..111c9f7 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -17,7 +17,7 @@ from urllib.parse import quote_plus from bs4 import BeautifulSoup -from emoji import get_emoji_regexp +from emoji import demojize from googletrans import LANGUAGES, Translator from gtts import gTTS from gtts.lang import tts_langs @@ -461,7 +461,7 @@ def translate(message): return try: - reply_text = translator.translate(deEmojify(args), dest=TRT_LANG) + reply_text = translator.translate(demojize(args), dest=TRT_LANG) except ValueError: edit(message, f'`{get_translation("trtError")}`') return @@ -480,9 +480,6 @@ def translate(message): send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) -def deEmojify(inputString): - return get_emoji_regexp().sub(u'', inputString) - @sedenify(pattern='^.lang') def lang(message): diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index ab497e4..54fd83f 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -143,7 +143,7 @@ "deletedAcc": "Silinen Hesap", "demoteProcess": "Yetki d\u00fc\u015f\u00fcr\u00fcl\u00fcyor\u2026", "demoteResult": "%1[%2](tg://user?id=%3)%1 %4art\u0131k y\u00f6netici de\u011fil!%4", - "deviceError": "%1%2cihaz\u0131 i\u00e7in bilgi bulunamad\u0131!%1", + "deviceError": "%1%2 cihaz\u0131 i\u00e7in bilgi bulunamad\u0131!%1", "deviceSearch": "%1%2 i\u00e7in arama sonu\u00e7lar\u0131:%1", "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", @@ -352,7 +352,7 @@ "ofrpConnect": "OrangeFox sunucular\u0131na ba\u011flan\u0131l\u0131yor\u2026", "ofrpError": "Muhtemelen bu liste bo\u015f.", "ofrpErrorDate": "Tarih bilgisi \u00e7ekilirken bir hata olu\u015ftu", - "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.\n%1 %3 %1adresinden kontrol edebilirsiniz.%1", + "ofrpNotFound": "%1%2 kod ad\u0131 muhtemelen resmi bir cihaza ait de\u011fil.%1\n%3%1adresinden kontrol edebilirsiniz.%1", "ofrpUrl": "https://orangefox.download/tr-TR", "ofrpUsage": "Kullan\u0131m: .orangefox <kod ad\u0131> \u00d6rnek: .orangefox raphael", "onlySupportGdrive": "Yanl\u0131zca Google Drive linkleri destekleniyor.", From 9d78a4b30e8b0f04266675c723d5070c45609565 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 21 Jul 2022 22:16:48 +0300 Subject: [PATCH 178/242] scrapers: Add missing enums Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/scrapers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 111c9f7..8380441 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -21,6 +21,7 @@ from googletrans import LANGUAGES, Translator from gtts import gTTS from gtts.lang import tts_langs +from pyrogram import enums from pyrogram.types import InputMediaPhoto from requests import get, post from sedenbot import HELP, SEDEN_LANG @@ -480,7 +481,6 @@ def translate(message): send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) - @sedenify(pattern='^.lang') def lang(message): arr = extract_args(message).split(' ', 1) @@ -549,7 +549,7 @@ def imeichecker(message): <b>Durum:</b> <code>{result['durum']}</code>\n\ <b>Cihaz:</b> <code>{result['markaModel']}</code>\n\n\ <b>Powered by </b><a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden</a>♥" - edit(message, reply_text, parse='HTML', preview=False) + edit(message, reply_text, parse=enums.ParseMode.HTML, preview=False) except Exception as e: raise e From 96d55c6f77a2ba0314443cfb987e6796a3d8bbad Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 22 Jul 2022 23:05:48 +0300 Subject: [PATCH 179/242] afk: Add last seen Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/afk.py | 30 ++++++++++++++++++++++++++++-- sedenecem/translator/en.json | 4 ++-- sedenecem/translator/tr.json | 4 ++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 9d1d384..b97f107 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -7,11 +7,13 @@ # All rights reserved. See COPYING, AUTHORS. # +from datetime import datetime from random import choice, randint from time import sleep +from humanize import i18n, naturaltime from pyrogram import ContinuePropagation, StopPropagation -from sedenbot import HELP, PM_AUTO_BAN, TEMP_SETTINGS, app +from sedenbot import HELP, PM_AUTO_BAN, SEDEN_LANG, TEMP_SETTINGS, app from sedenecem.core import ( edit, extract_args, @@ -26,6 +28,15 @@ TEMP_SETTINGS['AFK_USERS'] = {} TEMP_SETTINGS['IS_AFK'] = False TEMP_SETTINGS['COUNT_MSG'] = 0 +TEMP_SETTINGS['AFK_TIME'] = {} + + +def get_time(now, then) -> str: + i18n.activate('tr_TR') if SEDEN_LANG == 'tr' else i18n.deactivate() + time = naturaltime(now - then) + return time + + # ================================================================= @@ -43,6 +54,7 @@ def mention_afk(msg): rep_m = msg.reply_to_message if mentioned or rep_m and rep_m.from_user and rep_m.from_user.id == me.id: if TEMP_SETTINGS['IS_AFK']: + afk_duration = get_time(datetime.now(), TEMP_SETTINGS['AFK_TIME']) if msg.from_user.id not in TEMP_SETTINGS['AFK_USERS']: if 'AFK_REASON' in TEMP_SETTINGS: reply( @@ -55,6 +67,7 @@ def mention_afk(msg): me.id, '`', TEMP_SETTINGS['AFK_REASON'], + afk_duration, ], ), ) @@ -75,6 +88,7 @@ def mention_afk(msg): me.id, '`', TEMP_SETTINGS['AFK_REASON'], + afk_duration, ], ), ) @@ -110,15 +124,24 @@ def afk_on_pm(message): apprv = True else: apprv = True + if apprv and TEMP_SETTINGS['IS_AFK']: me = TEMP_SETTINGS['ME'] + afk_duration = get_time(datetime.now(), TEMP_SETTINGS['AFK_TIME']) if message.from_user.id not in TEMP_SETTINGS['AFK_USERS']: if 'AFK_REASON' in TEMP_SETTINGS: reply( message, get_translation( "afkMessage2", - ['**', me.first_name, me.id, '`', TEMP_SETTINGS['AFK_REASON']], + [ + '**', + me.first_name, + me.id, + '`', + TEMP_SETTINGS['AFK_REASON'], + afk_duration, + ], ), ) else: @@ -138,6 +161,7 @@ def afk_on_pm(message): me.id, '`', TEMP_SETTINGS['AFK_REASON'], + afk_duration, ], ), ) @@ -168,6 +192,7 @@ def set_afk(message): edit(message, f'**{get_translation("afkStart")}**') send_log(get_translation('afkLog')) TEMP_SETTINGS['IS_AFK'] = True + TEMP_SETTINGS['AFK_TIME'] = datetime.now() raise StopPropagation @@ -199,6 +224,7 @@ def type_afk_is_not_true(message): ) TEMP_SETTINGS['COUNT_MSG'] = 0 TEMP_SETTINGS['AFK_USERS'] = {} + TEMP_SETTINGS['AFK_TIME'] = {} if 'AFK_REASON' in TEMP_SETTINGS: del TEMP_SETTINGS['AFK_REASON'] raise ContinuePropagation diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 313d0aa..4a425c8 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -7,10 +7,10 @@ "afkInfo": ".afk [Optional Reason]\nUsage: Sets you as afk.\nReplies to anyone who tags/PM's you telling them that you are AFK(reason).\n\nSwitches off AFK when you type back anything, anywhere.", "afkLog": "#AFK\nYou went AFK!", "afkMentionUsers": "%1[%2](tg://user?id=%3) sent you%1 %4%5%4 %1messages%1", - "afkMessage2": "%1[%2](tg://user?id=%3) still AFK.\nReason:%1 %4%5%4", + "afkMessage2": "%1[%2](tg://user?id=%3) still AFK.\nReason: %4%5%4\nLast Seen: %4%6%4 %1", "afkMessages": "%1%3%1 %2person sent%2 %1%4%1 %2messages to you while you were AFK.%2", "afkStart": "AFK AF!", - "afkStartReason": "%1AFK AF!\nReason%1: %2%3%2", + "afkStartReason": "%1AFK AF!\nReason: %2%3%2%1", "afkstr1": "I'm busy right now. Please talk in a bag and when I come back you can just give me the bag!", "afkstr10": "I'm away over 7 seas and 7 countries,\n7 waters and 7 continents,\n7 mountains and 7 hills,\n7 plains and 7 mounds,\n7 pools and 7 lakes,\n7 springs and 7 meadows,\n7 cities and 7 neighborhoods,\n7 blocks and 7 houses\u2026\n\nWhere not even your messages can reach me!", "afkstr11": "I'm away from the keyboard at the moment, but if you'll scream loud enough at your screen, I might just hear you.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 54fd83f..286fbc8 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -7,10 +7,10 @@ "afkInfo": ".afk [\u0130ste\u011fe ba\u011fl\u0131 sebep]\nKullan\u0131m: AFK oldu\u011funuzu belirtir.\nKim size pm atarsa ya da sizi etiketlerse sizin AFK oldu\u011funuzu ve belirledi\u011finiz sebebi g\u00f6sterir.\n\nHerhangi bir yere mesaj yazd\u0131\u011f\u0131n\u0131zda AFK modu kapan\u0131r.", "afkLog": "#AFK\nAFK oldunuz.", "afkMentionUsers": "%1[%2](tg://user?id=%3) size%1 %4%5%4 %1mesaj g\u00f6nderdi%1", - "afkMessage2": "%1[%2](tg://user?id=%3) h\u00e2l\u00e2 AFK.\nSebep:%1 %4%5%4", + "afkMessage2": "%1[%2](tg://user?id=%3) h\u00e2l\u00e2 AFK.\nSebep: %4%5%4\nSon G\u00f6r\u00fclme: %4%6%4%1", "afkMessages": "%2Siz AFK iken%2 %1%3%1 %2ki\u015fi size%2 %1%4%1 %2mesaj g\u00f6nderdi.%2", "afkStart": "Art\u0131k AFK'y\u0131m.", - "afkStartReason": "%1Art\u0131k AFK'y\u0131m.\nSebep%1: %2%3%2", + "afkStartReason": "%1Art\u0131k AFK'y\u0131m.\nSebep: %2%3%2%1", "afkstr1": "\u015eu an acele i\u015fim var, daha sonra mesaj atsan olmaz m\u0131? Zaten yine gelece\u011fim.", "afkstr10": "7 deniz ve 7 \u00fclkeden uzaktay\u0131m,\n7 su ve 7 k\u0131ta,\n7 da\u011f ve 7 tepe,\n7 ovala ve 7 h\u00f6y\u00fck,\n7 havuz ve 7 g\u00f6l,\n7 bahar ve 7 \u00e7ay\u0131r,\n7 \u015fehir ve 7 mahalle,\n7 blok ve 7 ev\u2026\n\nMesajlar\u0131n bile bana ula\u015famayaca\u011f\u0131 bir yer!", "afkstr11": "\u015eu anda klavyeden uzaktay\u0131m, ama ekran\u0131n\u0131zda yeterince y\u00fcksek sesle \u00e7\u0131\u011fl\u0131k atarsan\u0131z, sizi duyabilirim.", From f280ba98eff8dab9c6ea79b45eb5fdaf178b4fee Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 24 Jul 2022 20:23:29 +0300 Subject: [PATCH 180/242] beautify true false Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- sedenbot/modules/info.py | 2 +- sedenbot/modules/stickers.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index b053487..7c106d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.33 +pyrogram==2.0.35 pysmartDL python-barcode python-dotenv diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 87de197..df91252 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -80,7 +80,7 @@ def who_is(client, message): photos, dc_id, chats, - premium, + '✅' if premium else '❌', bio, last_seen, sudo if sudo else '', diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 59651b0..8ed01fa 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -281,9 +281,9 @@ def packinfo(client, message): '`', get_stickerset.set.title, get_stickerset.set.short_name, - get_stickerset.set.official, - get_stickerset.set.archived, - get_stickerset.set.animated, + '✅' if get_stickerset.set.official else '❌', + '✅' if get_stickerset.set.archived else '❌', + '✅' if get_stickerset.set.animated else '❌', get_stickerset.set.count, ' '.join(pack_emojis), ], From b484c63538a3cdf711fafda1f9133246a3f704e4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 3 Aug 2022 13:34:23 +0300 Subject: [PATCH 181/242] Updated paste.py, scrapers.py * .paste command improved, added .getpaste command * .doviz command improved * translations updated Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/paste.py | 63 ++++++++++++++++++++++++++++++------ sedenbot/modules/scrapers.py | 11 +++++-- sedenecem/translator/en.json | 6 ++-- sedenecem/translator/tr.json | 2 ++ 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/sedenbot/modules/paste.py b/sedenbot/modules/paste.py index 6842025..c25b83d 100644 --- a/sedenbot/modules/paste.py +++ b/sedenbot/modules/paste.py @@ -6,25 +6,33 @@ # # All rights reserved. See COPYING, AUTHORS. # -from requests import post -from sedenecem.core import edit, get_translation, sedenify +from requests import get, post +from requests.exceptions import HTTPError, Timeout, TooManyRedirects +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify -@sedenify(pattern="^.paste") -def paste(message): +@sedenify(pattern="^.paste") +def paste_hastebin(message): text = message.text.strip() - if len(text) <= 6: + reply = message.reply_to_message + edit(message, f'`{get_translation("processing")}`') + if not reply and len(text) <= 6: return edit(message, f'`{get_translation("pasteErr")}`') - paste = text.replace('.paste ', '').encode('utf-8') + paste = text.replace('.paste ', '') + url = "https://hastebin.com/documents" - url = "https://www.toptal.com/developers/hastebin/documents" + if reply: + if not reply.text: + return edit(message, f'`{get_translation("pasteErr")}`') + paste = reply.text try: r = post( url=url, - data=paste, + data=paste.encode('utf-8'), ) except BaseException as e: edit(message, f'`{get_translation("pasteConErr")}`') @@ -32,7 +40,42 @@ def paste(message): try: resp = r.json() key = resp['key'] - new_url = f"https://www.toptal.com/developers/hastebin/{key}" - return edit(message, new_url, False) + new_url = f"https://hastebin.com/{key}" + return edit(message, new_url, preview=False) except BaseException as e: raise e + + +@sedenify(pattern='^.getpaste') +def get_hastebin_text(message): + args = extract_args(message) + reply = message.reply_to_message + edit(message, f'`{get_translation("processing")}`') + + if reply: + args = reply.text + + if args.startswith('https://hastebin.com/'): + args = args[len('https://hastebin.com/') :] + elif args.startswith("hastebin.com/"): + args = args[len("hastebin.com/") :] + else: + return edit(message, f'`{get_translation("wrongURL")}`') + + resp = get(f'https://hastebin.com/raw/{args}') + + try: + resp.raise_for_status() + except HTTPError as err: + return edit(message, get_translation('banError', ['`', '**', err])) + except Timeout as err: + return edit(message, get_translation('banError', ['`', '**', err])) + except TooManyRedirects as err: + return edit(message, get_translation('banError', ['`', '**', err])) + + out = f'`Hastebin içeriği başarıyla getirildi!`\n\n`İçerik:` {resp.text}' + + edit(message, out) + + +HELP.update({'hastebin': get_translation('pasteInfo')}) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 8380441..80264aa 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -516,13 +516,20 @@ def lang(message): @sedenify(pattern='^.d[oö]viz') def doviz(message): - page = BeautifulSoup(get('https://www.doviz.com/').content, 'html.parser') + req = get( + 'https://www.doviz.com/', + headers={ + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.9999.0 Safari/537.36' + }, + ) + page = BeautifulSoup(req.content, 'html.parser') res = page.find_all('div', {'class', 'item'}) out = '**Güncel döviz kurları:**\n\n' for i in res: name = i.find('span', {'class': 'name'}).text value = i.find('span', {'class': 'value'}).text - out += f'`•` **{name}:** `{value}`\n' + changes = i.find('span', {'data-socket-attr': 'a'}).text + out += f'`•` **{name}:** `{value}` // `{changes}`\n' edit(message, out) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 4a425c8..e6a4f0e 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -363,6 +363,7 @@ "packinfoResult": "%1Sticker Title:%1 %2%3%2\n%1Sticker Short Name:%1 %2%4%2\n%1Official:%1 %2%5%2\n%1Archived:%1 %2%6%2\n%1Animated:%1 %2%7%2\n%1Stickers In Pack:%1 %2%8%2\n%1Emojis In Pack:%1\n%9", "pasteConnErr": "Could not connect to server.", "pasteErr": "Enter text for paste.", + "pasteInfo": ".paste <text/reply>\nUsage: Pastes the given text on the Hastebin\n\n.getpaste <text/reply>\nUsage: Gets the content of a paste url from Hastebin\n\n(https://hastebin.com/)", "phhError": "%1Couldn't find anything for%1 %2%3%2", "picspamLog": "#PICSPAM\nPicSpam was executed successfully", "pinLog": "#PIN\nGROUP: %1 (%2%3%2)", @@ -508,9 +509,9 @@ "sedenUsage": "Please specify a Seden module.", "sedenUsage2": "%1Please indicate which Seden module you want help with!\nUsage:%1 %2.seden <module name>%2", "sedenVersion": "Bot version; Seden v%1", - "shippingMovements": "\n\n%1Unit: %3\nStatus: %4\nDate: %5\nTime: %6\nAction: %7%2", + "shippingMovements": "\n\n%1%3Last movement%4%2\n\n%5Unit: %7\nStatus: %8\nDate: %9\nTime: %10\nAction: %11%6", "shippingNoResult": "Tracking information not found!", - "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4\n\n%1%14Last movement%15%2", + "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4", "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", @@ -644,6 +645,7 @@ "wrongCommand": "Command usage is wrong.", "wrongFilter": "Filter is wrong!", "wrongMedia": "Wrong media type!", + "wrongUrl": "Wrong URL!", "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl <mp3 or mp4> <url>\nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 286fbc8..cd01fa9 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -364,6 +364,7 @@ "packinfoResult": "%1\u00c7\u0131kartma paketi ba\u015fl\u0131\u011f\u0131:%1 %2%3%2\n%1\u00c7\u0131kartma paketi k\u0131sa ad\u0131:%1 %2%4%2\n%1Resmi paket mi:%1 %2%5%2\n%1Ar\u015fivlenmi\u015f mi:%1 %2%6%2\n%1Animasyonlu mu:%1 %2%7%2\n%1Pakettki \u00e7\u0131kartma say\u0131s\u0131:%1 %2%8%2\n%1Paketteki emoji say\u0131s\u0131:%1\n%9", "pasteConnErr": "Sunucuya ba\u011flan\u0131lamad\u0131", "pasteErr": "Yap\u0131\u015ft\u0131rmak i\u00e7in bir \u015fey yaz\u0131n.", + "pasteInfo": ".paste <metin/yan\u0131tlama>\nKullan\u0131m: Verilen metni Hastebin'e yap\u0131\u015ft\u0131r\u0131r\n\n.getpaste <metin/yan\u0131tlama>\nKullan\u0131m: Hastebin link i\u00e7eri\u011fini metne aktar\u0131r\n\n(https://hastebin.com/)", "phhError": "%2%3%2 %1aramas\u0131 i\u00e7in bir ROM bulunamad\u0131%1", "picspamLog": "#PICSPAM\nPicSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "pinLog": "#PIN\nGRUP: %1 (%2%3%2)", @@ -644,6 +645,7 @@ "wrongCommand": "Komut kullan\u0131m\u0131 hatal\u0131.", "wrongFilter": "Filtre hatal\u0131!", "wrongMedia": "Ge\u00e7ersiz medya t\u00fcr\u00fc!", + "wrongUrl": "Hatal\u0131 link!", "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl <mp3 ya da mp4> <link>\nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334", From 62e2de3b2cf4e640d966331bf5963f4c2c424e29 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 3 Aug 2022 14:30:50 +0300 Subject: [PATCH 182/242] Update whois command Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/info.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index df91252..897bfb4 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -36,6 +36,9 @@ def who_is(client, message): perm = message.chat.permissions media_perm = perm.can_send_media_messages + if message.chat.type == enums.ChatType.PRIVATE: + media_perm = True + for reply_user in find_user: try: reply_chat = client.get_chat(reply_user.id) From 5520bbfd982c1db916ca1dd217a719b19b68aab0 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Mon, 22 Aug 2022 21:59:51 +0300 Subject: [PATCH 183/242] Check value contain '/' Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/exif.py | 46 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index 0b22778..d3082e4 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -7,12 +7,10 @@ # All rights reserved. See COPYING, AUTHORS. # -import math +from math import floor, sqrt from os import remove, path from exifread import process_file -from fractions import Fraction -from PIL import Image from sedenbot import HELP from sedenecem.core import ( download_media_wc, @@ -22,7 +20,7 @@ ) -@sedenify(pattern="^.exif") +@sedenify(pattern="^.exif$") def exif_data(message): reply = message.reply_to_message google_coordinate = ["https://www.google.com/maps?q="] @@ -96,10 +94,11 @@ def exif_data(message): def calculate_aperture(string): - division = string.split("/") - return "f/%.1f" % ( - math.sqrt(2.0) ** (int(division[0]) / int(division[1])) - ) + if '/' in string: + division = string.split("/") + return "f/%.1f" % (sqrt(2.0) ** (int(division[0]) / int(division[1]))) + else: + return f'f/{float(string):.1f}' def calculate_brightness(string): @@ -108,22 +107,27 @@ def calculate_brightness(string): def calculate_fnumber(string): - division = string.split("/") - return "f/%.1f" % (int(division[0]) / int(division[1])) + if '/' in string: + division = string.split("/") + return "f/%.1f" % (int(division[0]) / int(division[1])) + return f'f/{float(string):.1f}' def calculate_focal(string): - division = string.split("/") - return "{0} mm".format( - str(math.floor(int(division[0]) / int(division[1]))) - ) + if '/' in string: + division = string.split("/") + return "{0} mm".format(str(floor(int(division[0]) / int(division[1])))) + return f'{string} mm' def calculate_shutter(string): - division = string.split("/") - return "1/{0} sec".format( - str(math.floor(2 ** (int(division[0]) / int(division[1])))) - ) + print(string) + if '/' in string: + division = string.split("/") + return "1/{0} sec".format( + str(floor(2 ** (int(division[0]) / int(division[1])))) + ) + return f"1/{int(string)} sec" def calculate_altitude(string, google_coordinate): @@ -146,10 +150,8 @@ def calculate_gps(coord, google_coordinate): minutes = int(minutes[0]) seconds = int(seconds[0]) / int(seconds[1]) - google_coordinate.append( - f"{hour}%C2%B0{math.floor(minutes)}'{seconds:.2f}%22" - ) - return f"{hour}°{math.floor(minutes)}'{seconds:.2f}\"" + google_coordinate.append(f"{hour}%C2%B0{floor(minutes)}'{seconds:.2f}%22") + return f"{hour}°{floor(minutes)}'{seconds:.2f}\"" def calculate_latitude_ref(coord, google_coordinate): From 667c5ea5a3ea873a0cdb9c8de2b06a45affab947 Mon Sep 17 00:00:00 2001 From: fatihesergg <47848940+fatihesergg@users.noreply.github.com> Date: Mon, 22 Aug 2022 19:06:24 +0000 Subject: [PATCH 184/242] requirements: Update pyrogram version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7c106d1..68303c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.35 +pyrogram==2.0.41 pysmartDL python-barcode python-dotenv From 1e64aa6d1b746174f4a13b0720ae58369698689b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= <46200293+trkzmn@users.noreply.github.com> Date: Sun, 28 Aug 2022 13:28:45 +0300 Subject: [PATCH 185/242] Bring back gdrive into direct_link generator (#17) * modules: direct_link: Bring back gdrive Signed-off-by: trkzmn <trkzmn89@gmail.com> * direct_link: gdrive: Add alternative download link Signed-off-by: trkzmn <trkzmn89@gmail.com> Signed-off-by: trkzmn <trkzmn89@gmail.com> --- sedenbot/modules/direct_link.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index e4a9b16..1ee8de9 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -52,7 +52,9 @@ def check(url, items, starts=False): reply += f'`{get_translation("directUrlNotFound")}`\n' continue try: - if check(link, 'zippyshare.com'): + if check(link, 'drive.google.com'): + reply += gdrive(link) + elif check(link, 'zippyshare.com'): reply += zippy_share(link) elif check(link, 'yadi.sk'): reply += yandex_disk(link) @@ -73,6 +75,20 @@ def check(url, items, starts=False): edit(message, reply, preview=False) +def gdrive(link: str) -> str: + reply = '' + url_id = link.split('/')[5] + dl_url = f'https://drive.google.com/u/0/uc?id={url_id}&export=download&confirm=t' + headers = {'user-agent': useragent()} + response = get(url=dl_url, headers=headers, stream=True) + name = response.headers.get('Content-Disposition').split(';')[1].split('"')[1] + size = f'{int(response.headers.get("Content-Length")) / (1024 * 1024):0.2f}' + alternative_url = response.url + reply += f'[1 - {name} ({size}MB)]({dl_url})\n' + reply += f'[2 - {name} ({size}MB)]({alternative_url})\n' + return reply + + def zippy_share(link: str) -> str: reply = '' driver = get_webdriver() From a05860747b09d99fdddc0a5e8b527906cc364fd4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Sun, 28 Aug 2022 13:34:04 +0300 Subject: [PATCH 186/242] direct: Update useragent, update translations Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/direct_link.py | 10 ++++------ sedenecem/translator/en.json | 4 ++-- sedenecem/translator/tr.json | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index 1ee8de9..7a5257d 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -217,13 +217,11 @@ def androidfilehost(link: str) -> str: def useragent(): - req = get('https://user-agents.net/random') + req = get('https://useragents.io/random') soup = BeautifulSoup(req.text, 'html.parser') - agent = soup.find('article') - if agent: - agent = agent.find('li') - if agent: - return agent.find('a').text.replace('"', '') + agent = soup.find_all('td') + for i in agent: + return i.find('a').text return 'Googlebot/2.1 (+http://www.google.com/bot.html)' diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index e6a4f0e..19ae7ef 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -147,7 +147,7 @@ "deviceSearchResultChild": "%1Brand:%1 %2\n%1Name:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Usage: .device <codename> eg: .device raphael", "directError": "Error occurred while processing %1", - "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nMediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 not supported", "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", @@ -407,7 +407,7 @@ "purgemeUsage": "Purgeme failed, please specify number.", "pyrogramDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", "pyrogramUp": "%1Filename:%1 %2%3%2\n%1Uploaded%1 %2%4%2", - "pythonVersionError": "You must have at least a Python version 3.8\nMultiple features depend on this. Bot quitting.", + "pythonVersionError": "You must have at least a Python version 3.10\nMultiple features depend on this. Bot quitting.", "quotlyInfo": ".q\nUsage: Enhance ur text to sticker.", "randomResult": "%1Query:%1\n%2%3%2\n%1Output:%1\n%2%4%2", "randomUsage": "2 or more argument required.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index cd01fa9..5e2a3a5 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -148,7 +148,7 @@ "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", - "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nMediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 desteklenmiyor", "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", @@ -408,7 +408,7 @@ "purgemeUsage": "Temizlik yap\u0131lamad\u0131, say\u0131 ge\u00e7ersiz.", "pyrogramDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "pyrogramUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi%1 %2%4%2", - "pythonVersionError": "En az Python 3.8 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", + "pythonVersionError": "En az Python 3.10 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", "quotlyInfo": ".q\nKullan\u0131m: Metninizi \u00e7\u0131kartmaya d\u00f6n\u00fc\u015ft\u00fcr\u00fcn.", "randomResult": "%1Sorgu:%1\n%2%3%2\n%1\u00c7\u0131kt\u0131:%1\n%2%4%2", "randomUsage": "2 veya daha fazla se\u00e7enek gerekli.", From d041cc1eaeaa5178b59656e6738a020d3778d1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= <46200293+trkzmn@users.noreply.github.com> Date: Mon, 29 Aug 2022 00:14:03 +0300 Subject: [PATCH 187/242] direct: gdrive: Support GDocs (#18) Signed-off-by: trkzmn <trkzmn89@gmail.com> --- sedenbot/modules/direct_link.py | 2 +- sedenecem/translator/en.json | 4 ++-- sedenecem/translator/tr.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index 7a5257d..0a47a35 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -52,7 +52,7 @@ def check(url, items, starts=False): reply += f'`{get_translation("directUrlNotFound")}`\n' continue try: - if check(link, 'drive.google.com'): + if check(link, ['drive.google.com', 'docs.google.com']): reply += gdrive(link) elif check(link, 'zippyshare.com'): reply += zippy_share(link) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 19ae7ef..1b79908 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -147,7 +147,7 @@ "deviceSearchResultChild": "%1Brand:%1 %2\n%1Name:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Usage: .device <codename> eg: .device raphael", "directError": "Error occurred while processing %1", - "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nMediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 not supported", "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", @@ -657,4 +657,4 @@ "zombiesRemove": "Removing deleted account(s)\u2026", "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" -} \ No newline at end of file +} diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 5e2a3a5..b1a5ae1 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -148,7 +148,7 @@ "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", - "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nMediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 desteklenmiyor", "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", @@ -657,4 +657,4 @@ "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor\u2026", "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" -} \ No newline at end of file +} From a0cbc73265109b277c0e3b8b5e8ad4da7ee73913 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 20 Sep 2022 10:25:58 +0300 Subject: [PATCH 188/242] youtubedl: Fix audio file decode Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- sedenbot/modules/youtubedl.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 0cff372..3ceb8e1 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -16,7 +16,7 @@ from sedenbot import HELP from sedenecem.core import ( edit, - extract_args, + extract_args_arr, get_download_dir, get_translation, reply_audio, @@ -26,9 +26,9 @@ from yt_dlp import YoutubeDL -@sedenify(pattern='^.(youtube|yt)dl') +@sedenify(pattern='^.y(outube|tdl)') def youtubedl(message): - args = extract_args(message).split(' ', 2) + args = extract_args_arr(message) if len(args) != 2: edit(message, f'`{get_translation("wrongCommand")}`') @@ -39,7 +39,7 @@ def youtubedl(message): if util == 'mp4': ydl_opts = { - 'outtmpl': f'%(id)s.%(ext)s', + 'outtmpl': '%(id)s.%(ext)s', 'format': 'bestvideo[ext=mp4][height<=?1080]+bestaudio[ext=m4a]/best', 'addmetadata': True, 'prefer_ffmpeg': True, @@ -98,7 +98,7 @@ def youtubedl(message): elif util == 'mp3': ydl_opts = { - 'outtmpl': f'%(title)s.%(ext)s', + 'outtmpl': '%(id)s.%(ext)s', 'format': 'bestaudio/best', 'addmetadata': True, 'writethumbnail': True, @@ -136,7 +136,7 @@ def youtubedl(message): edit(message, f'`{get_translation("uploadMedia")}`') reply_audio( message, - f'{title}.mp3', + f'{video_info["id"]}.mp3', caption=f"**{get_translation('videoUploader')}** `{uploader}`", duration=duration, delete_orig=True, From 66aa7a23ed4621690fd47a9f2833811bd7103248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= <46200293+trkzmn@users.noreply.github.com> Date: Tue, 20 Sep 2022 10:28:14 +0300 Subject: [PATCH 189/242] direct: gdrive: Bypass login requirement to Google account (#19) Signed-off-by: trkzmn <trkzmn89@gmail.com> --- cookies.txt | 5 +++++ sedenbot/modules/direct_link.py | 11 ++++++++--- sedenecem/translator/en.json | 2 ++ sedenecem/translator/tr.json | 2 ++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 cookies.txt diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 0000000..b3cd0ca --- /dev/null +++ b/cookies.txt @@ -0,0 +1,5 @@ +# HTTP Cookie File +# Generated by Wget on 2022-09-18 16:35:21. +# Edit at your own risk. + +.google.com TRUE / TRUE 1726590921 __Secure-3PSID OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XydPm0-knkwsNrazAuGCA7Q. diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index 0a47a35..da9c257 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -14,7 +14,7 @@ from bs4 import BeautifulSoup from requests import Session, get from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, get_webdriver, sedenify +from sedenecem.core import edit, extract_args, get_translation, get_webdriver, reply_doc, sedenify @sedenify(pattern=r'^.direct') @@ -53,7 +53,7 @@ def check(url, items, starts=False): continue try: if check(link, ['drive.google.com', 'docs.google.com']): - reply += gdrive(link) + reply += gdrive(link, message) elif check(link, 'zippyshare.com'): reply += zippy_share(link) elif check(link, 'yadi.sk'): @@ -75,12 +75,17 @@ def check(url, items, starts=False): edit(message, reply, preview=False) -def gdrive(link: str) -> str: +def gdrive(link: str, message) -> str: reply = '' url_id = link.split('/')[5] dl_url = f'https://drive.google.com/u/0/uc?id={url_id}&export=download&confirm=t' headers = {'user-agent': useragent()} response = get(url=dl_url, headers=headers, stream=True) + if response.status_code == 429: + reply_doc(message, 'cookies.txt', caption=get_translation("directGdriveCookieUsage")) + reply += get_translation("directGdriveCookie") + cookies = {'__Secure-1PSID': 'OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XqwPg5PZZKN6KNnoBsXJrQw.'} + response = get(url=dl_url, headers=headers, cookies=cookies, stream=True) name = response.headers.get('Content-Disposition').split(';')[1].split('"')[1] size = f'{int(response.headers.get("Content-Length")) / (1024 * 1024):0.2f}' alternative_url = response.url diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 1b79908..f7dd0ee 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -147,6 +147,8 @@ "deviceSearchResultChild": "%1Brand:%1 %2\n%1Name:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Usage: .device <codename> eg: .device raphael", "directError": "Error occurred while processing %1", + "directGdriveCookie": "This file requires login Google account to access & download.\nUse cookies.txt to download file.\n\n", + "directGdriveCookieUsage": "Usage: `wget --load-cookies cookies.txt` directLink", "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 not supported", "directUrlNotFound": "URL not found", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index b1a5ae1..9cdd898 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -148,6 +148,8 @@ "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", + "directGdriveCookie": "Bu dosyay\u0131 indirebilmek i\u00E7in Google hesab\u0131n\u0131za giri\u015F yapmal\u0131s\u0131n\u0131z.\nDosyay\u0131 indirmek i\u00E7in cookies.txt\u0027i kullan\u0131n.\n\n", + "directGdriveCookieUsage": "Kullan\u0131m: `wget --load-cookies cookies.txt` directLink", "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 desteklenmiyor", "directUrlNotFound": "Link bulunamad\u0131", From 89b6964038a7a43b54d2343bc04df2f1bfdb6d58 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 20 Sep 2022 10:29:02 +0300 Subject: [PATCH 190/242] update pyrogram Signed-off-by: NaytSeyd <naytseyd@yandex.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68303c7..b57f396 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.41 +pyrogram==2.0.54 pysmartDL python-barcode python-dotenv From e4073eb0c16acf29580e4468c5a94dc2a43c23e1 Mon Sep 17 00:00:00 2001 From: trkzmn <trkzmn89@gmail.com> Date: Thu, 22 Sep 2022 02:20:16 +0000 Subject: [PATCH 191/242] exif: Fix division by zero error Signed-off-by: trkzmn <trkzmn89@gmail.com> --- sedenbot/modules/exif.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index d3082e4..e479c28 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -70,17 +70,19 @@ def exif_data(message): if i in unit_dict.keys(): converted = "" - # GPS Data - if "GPS" == i.split(" ")[0]: - converted = unit_dict[i](data[i].printable, google_coordinate) + if not str(data[i]) in ['0', '0/0', '[0/0, 0/0, 0/0]']: - # Thumbnail Image Data in Byte Array - elif type(data[i]) == bytes: - converted = unit_dict[i](data[i]) + # GPS Data + if "GPS" == i.split(" ")[0]: + converted = unit_dict[i](data[i].printable, google_coordinate) - # Others - else: - converted = unit_dict[i](data[i].printable) + # Thumbnail Image Data in Byte Array + elif type(data[i]) == bytes: + converted = unit_dict[i](data[i]) + + # Others + else: + converted = unit_dict[i](data[i].printable) data_str += "{0} : {1}\n".format(i, converted) else: From 9abc14f8d713495a9b44491174c30ed691ebb7e6 Mon Sep 17 00:00:00 2001 From: Squirrel Python <squirrelpython@gmail.com> Date: Sun, 25 Sep 2022 01:47:51 +0300 Subject: [PATCH 192/242] scrapers: improved imei checker command --- sedenbot/modules/scrapers.py | 23 +++++++++++++++-------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 80264aa..4059820 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -533,7 +533,7 @@ def doviz(message): edit(message, out) -@sedenify(pattern='^.imeicheck') +@sedenify(pattern='^.imei(checker)?') def imeichecker(message): argv = extract_args(message) imei = argv.split(' ', 1)[0] @@ -551,18 +551,25 @@ def imeichecker(message): continue result = response['data'] break - reply_text = f"<b>Sorgu Tarihi</b> <code>{result['sorguTarihi']}</code>\n\ -<b>IMEI:</b> <code>{result['imei'][:-5] + 5*'*'}</code>\n\ -<b>Durum:</b> <code>{result['durum']}</code>\n\ -<b>Cihaz:</b> <code>{result['markaModel']}</code>\n\n\ -<b>Powered by </b><a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden</a>♥" - edit(message, reply_text, parse=enums.ParseMode.HTML, preview=False) + _marka = findall(r'Marka:(.+) Model', result['markaModel']) + _model = findall(r'Model Bilgileri:(.+)', result['markaModel']) + marka = _marka[0].replace(',','').strip() if _marka else "Marka Bilgisi Bulunamadi" + model = _model[0].replace(',','').strip() if _model else "Model Bilgisi Bulunamadi" + reply_text = ( + f"<b>Sorgu Tarihi</b> ⇾ <code>{result['sorguTarihi']}</code>\n" + f"<b>IMEI</b> ⇾ <code>{result['imei'][:-5]+5*'*'}</code>\n" + f"<b>Durum</b> ⇾ <code>{result['durum']}</code>\n" + f"<b>Marka</b> ⇾ <code>{marka}</code>\n" + f"<b>Model</b> ⇾ <code>{model}</code>\n\n" + f"<b>Powered by</b> ⇾ <a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden ♥</a>\n" + ) + edit(message, reply_text, parse=enums.parse_mode.ParseMode.HTML, preview=False) except Exception as e: raise e HELP.update({'img': get_translation('imgInfo')}) -HELP.update({'imeicheck': get_translation('imeiInfo')}) +HELP.update({'imei': get_translation('imeiInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) HELP.update({'goolag': get_translation('googleInfo')}) HELP.update({'duckduckgo': get_translation('ddgoInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index f7dd0ee..180d0d5 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -256,7 +256,7 @@ "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", - "imeiInfo": ".imeicheck <imeinumber>\nUsage: Check if IMEI is registered and get device information about model, vendor, etc.", + "imeiInfo": ".imei <imeinumber>\nUsage: Check if IMEI is registered and get device information about model, vendor, etc.", "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 9cdd898..4ed4557 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -258,7 +258,7 @@ "herokuInfo": ".kota\nKullan\u0131m: Heroku kaynak kullan\u0131m\u0131n\u0131z\u0131 g\u00f6sterir.\n\n.drestart\nKullan\u0131m: Heroku dynosunu yeniden ba\u015flat\u0131r.\n\n.logs\nKullan\u0131m: Heroku uygulama g\u00fcnl\u00fc\u011f\u00fc g\u00f6nderir.", "herokuQuotaInHM": "%1s %2d", "herokuQuotaInfo": "%2Heroku Kalan Kota%2\n\n%2Toplam:%2 %1%3%1\n%2Kullan\u0131lan:%2 %1%4 (\u00bd%5)%1\n%2Kalan:%2 %1%6 (\u00bd%7)%1\n%2Uygulama kullan\u0131m\u0131:%2 %1%8 (\u00bd%9)%1", - "imeiInfo": ".imeicheck <imeinumber>\nKullan\u0131m: IMEI numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda bilgi verir", + "imeiInfo": ".imei <imeinumber>\nKullan\u0131m: IMEI numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda bilgi verir", "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", From f02363cd6c7e3df4f2f326d2a463249b6d8a5230 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Sat, 15 Oct 2022 00:58:42 +0300 Subject: [PATCH 193/242] gdrive: Fix typo & Telegram premium user limit check Signed-off-by: fatihesergg <fatihesergg@gmail.com> --- sedenbot/modules/gdrive.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 0fd2c4c..722b573 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -117,7 +117,11 @@ def upload_to_telegram(self, url): size = file_info.get("size") file_name = file_info.get("name") - if int(size) > 4294967296 if self.message.from_user.is_premium else 2147483648: + if ( + int(size) > 4294967296 + if self.message.from_user.is_premium + else int(size) > 2147483648 + ): return edit(self.message, get_translation("tgUpLimit", ["`"])) else: file_name = self.download_from_gdrive(url) @@ -149,7 +153,7 @@ def download_from_gdrive(self, link) -> str: done = False while done is False: status, done = downloader.next_chunk() - progress = edit( + edit( self.message, get_translation( "gdriveDown", From 47ea929278fecb10fc6e3c3e4ed5000c7d4cfa93 Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Thu, 23 Mar 2023 00:30:36 +0300 Subject: [PATCH 194/242] major changes for stable update Co-authored-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: frknkrc44 <krc440002@gmail.com> Signed-off-by: naytseyd <naytseyd@yandex.com> --- README.md | 21 ++- cookies.txt | 2 +- requirements.txt | 6 +- sample_config.env | 5 +- seden.py | 2 +- seden_translate_sorter.py | 2 +- sedenbot/__init__.py | 15 +- sedenbot/modules/afk.py | 16 ++- sedenbot/modules/android.py | 61 ++++++-- sedenbot/modules/autopp.py | 10 +- sedenbot/modules/ban.py | 32 ++--- sedenbot/modules/blacklist.py | 2 +- sedenbot/modules/chat.py | 10 +- sedenbot/modules/colors.py | 2 +- sedenbot/modules/covid19.py | 82 ----------- sedenbot/modules/deepfry.py | 11 +- sedenbot/modules/direct_link.py | 51 +++---- sedenbot/modules/ecem.py | 2 +- sedenbot/modules/effects.py | 125 +++++++++-------- sedenbot/modules/env.py | 16 +-- sedenbot/modules/exif.py | 13 +- sedenbot/modules/ezanvakti.py | 101 ++++++-------- sedenbot/modules/filters.py | 2 +- sedenbot/modules/gdrive.py | 13 +- sedenbot/modules/git.py | 6 +- sedenbot/modules/globals.py | 18 +-- sedenbot/modules/horeke.py | 26 ++-- sedenbot/modules/info.py | 86 ++++++------ sedenbot/modules/kargotakip.py | 32 +++-- sedenbot/modules/lastfm.py | 2 +- sedenbot/modules/locks.py | 10 +- sedenbot/modules/lyrics.py | 2 +- sedenbot/modules/memes.py | 125 +++++++++-------- sedenbot/modules/misc.py | 73 +++++----- sedenbot/modules/notes.py | 2 +- sedenbot/modules/ocr.py | 2 +- sedenbot/modules/paste.py | 45 +++--- sedenbot/modules/pmpermit.py | 121 ++++++++-------- sedenbot/modules/profile.py | 82 +++++------ sedenbot/modules/purge.py | 24 ++-- sedenbot/modules/qrcode.py | 2 +- sedenbot/modules/quotly.py | 8 +- sedenbot/modules/remove_bg.py | 2 +- sedenbot/modules/reverse.py | 8 +- sedenbot/modules/rgb.py | 2 +- sedenbot/modules/sangmata.py | 45 ------ sedenbot/modules/scrapers.py | 178 ++++++++++++++--------- sedenbot/modules/screencapture.py | 2 +- sedenbot/modules/sed.py | 2 +- sedenbot/modules/seden.py | 2 +- sedenbot/modules/snips.py | 2 +- sedenbot/modules/spammer.py | 6 +- sedenbot/modules/spamwatch.py | 7 +- sedenbot/modules/speedtest.py | 2 +- sedenbot/modules/spotify_api.py | 6 +- sedenbot/modules/stickers.py | 18 +-- sedenbot/modules/system.py | 13 +- sedenbot/modules/updater.py | 2 +- sedenbot/modules/updown.py | 2 +- sedenbot/modules/weather.py | 16 ++- sedenbot/modules/youtubedl.py | 6 +- sedenecem/core/__init__.py | 3 +- sedenecem/core/conv.py | 38 ++++- sedenecem/core/filters.py | 176 +++++++++++++++++++++++ sedenecem/core/image.py | 21 ++- sedenecem/core/misc.py | 225 ++++++++++++++++++++++++++++-- sedenecem/core/proxy.py | 151 ++++++++++++-------- sedenecem/core/replier.py | 90 +++++++++++- sedenecem/core/sedenify.py | 75 +++++----- sedenecem/core/sedenlog.py | 18 ++- sedenecem/core/send.py | 36 ++++- sedenecem/core/webdriver.py | 11 +- sedenecem/sql/blacklist_sql.py | 2 +- sedenecem/sql/filters_sql.py | 2 +- sedenecem/sql/gban_sql.py | 2 +- sedenecem/sql/gdrive_sql.py | 2 +- sedenecem/sql/gmute_sql.py | 2 +- sedenecem/sql/keep_read_sql.py | 2 +- sedenecem/sql/mute_sql.py | 2 +- sedenecem/sql/notes_sql.py | 2 +- sedenecem/sql/pm_permit_sql.py | 4 +- sedenecem/sql/snips_sql.py | 2 +- sedenecem/translator/__init__.py | 2 +- sedenecem/translator/en.json | 46 +++--- sedenecem/translator/tr.json | 46 +++--- session.py | 2 +- 86 files changed, 1557 insertions(+), 994 deletions(-) delete mode 100644 sedenbot/modules/covid19.py delete mode 100644 sedenbot/modules/sangmata.py create mode 100644 sedenecem/core/filters.py diff --git a/README.md b/README.md index 03d9c8c..3161e4b 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -Seden UserBot -== +# Seden UserBot <sub><sup><sub>_Feel my hands in your hair_</sup></sub></sub> +### Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It is an modular and simple to use bot. -![GitHub repo size](https://img.shields.io/github/repo-size/TeamDerUntergang/Telegram-SedenUserBot?color=brightgreen) ![GitHub](https://img.shields.io/github/license/TeamDerUntergang/Telegram-SedenUserBot?color=red) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It is an modular and simple to use bot. +## Disclaimer +<details> + <summary>Click to expand!</summary> ```c #include <std/disclaimer.h> @@ -18,7 +19,12 @@ Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It I'll just laugh at you. /** ``` +</details> + ## Run Bot +<details> + <summary>Click to expand!</summary> + ```bash # Clone repo git clone https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git @@ -38,12 +44,17 @@ python3 seden.py ``` ### Nix/NixOS Just type `nix-shell` command in bot folder. +</details> +## Q&A If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.html) page for installation instructions! Questions asked without reading the instruction will not be answered. ## Credits +<details> + <summary>Click to expand!</summary> + * [@NaytSeyd](https://github.com/NaytSeyd) - Founder * [@frknkrc44](https://github.com/frknkrc44) - Operator * [@Sedenogen](https://github.com/ciyanogen) - Co-Founder @@ -51,7 +62,7 @@ Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.htm * [@Skittles9823](https://github.com/skittles9823) - Memes * [@RaphielGang](https://github.com/raphielgang) - Other Modules * [All Contributors](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/graphs/contributors) +</details> ## License - This project is licensed under the [AGPL-3](https://www.gnu.org/licenses/agpl-3.0.html). diff --git a/cookies.txt b/cookies.txt index b3cd0ca..029e722 100644 --- a/cookies.txt +++ b/cookies.txt @@ -2,4 +2,4 @@ # Generated by Wget on 2022-09-18 16:35:21. # Edit at your own risk. -.google.com TRUE / TRUE 1726590921 __Secure-3PSID OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XydPm0-knkwsNrazAuGCA7Q. +.google.com TRUE / TRUE 1726590921 __Secure-3PSID OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XydPm0-knkwsNrazAuGCA7Q. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b57f396..e007581 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.54 +pyrogram==2.0.102 pysmartDL python-barcode python-dotenv @@ -30,6 +30,4 @@ speedtest-cli spotipy sqlalchemy tgcrypto -urbandic -wikipedia -yt-dlp +yt-dlp \ No newline at end of file diff --git a/sample_config.env b/sample_config.env index 26df3bb..6087461 100644 --- a/sample_config.env +++ b/sample_config.env @@ -8,14 +8,15 @@ API_HASH='' SESSION='' # Your Database URL -# Example: 'postgresql://sedenbot:sedenbot@localhost:5432/sedenbot' +# Example: 'postgresql://username:passwd@localhost:5432/db_name' DATABASE_URL='' # Chat ID for Log Group LOG_ID='' # Location of ChromeDriver for .carbon module -# Example for Linux Machines : '/usr/bin/chromedriver' +# Example for Linux : '/usr/bin/chromedriver' +# Windows: 'C:\Users\<user>\scoop\apps\chromedriver\current\chromedriver' CHROME_DRIVER='' # To change Alive message diff --git a/seden.py b/seden.py index 68ed7dc..469fd68 100644 --- a/seden.py +++ b/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/seden_translate_sorter.py b/seden_translate_sorter.py index f4ec6f4..b7beb4b 100644 --- a/seden_translate_sorter.py +++ b/seden_translate_sorter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 839bd47..261a138 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -7,11 +7,6 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import name - -if name == 'nt': - print('Uninstall Windows to use this bot') - quit(1) from distutils.util import strtobool as sb from importlib import import_module @@ -25,12 +20,13 @@ from traceback import format_exc from typing import Any, Dict -import sedenecem.translator as _tr from dotenv import load_dotenv, set_key, unset_key from pyrogram import Client, filters from pyrogram.handlers import MessageHandler from requests import get +import sedenecem.translator as _tr + def reload_env(): return load_dotenv('config.env', override=True) @@ -302,9 +298,10 @@ def __get_modules(): def __import_modules(): - modules = sorted(__get_modules()) + get_modules = sorted(__get_modules()) + modules = ', '.join(get_modules) LOGS.info(get_translation('loadedModules', [modules])) - for module in modules: + for module in get_modules: try: import_module(f'sedenbot.modules.{module}') except Exception: diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index b97f107..675f0c5 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -13,7 +13,7 @@ from humanize import i18n, naturaltime from pyrogram import ContinuePropagation, StopPropagation -from sedenbot import HELP, PM_AUTO_BAN, SEDEN_LANG, TEMP_SETTINGS, app +from sedenbot import HELP, PM_AUTO_BAN, SEDEN_LANG, TEMP_SETTINGS from sedenecem.core import ( edit, extract_args, @@ -49,7 +49,7 @@ def get_time(now, then) -> str: disable_notify=True, ) def mention_afk(msg): - me = TEMP_SETTINGS['ME'] + me = msg._client.me mentioned = msg.mentioned rep_m = msg.reply_to_message if mentioned or rep_m and rep_m.from_user and rep_m.from_user.id == me.id: @@ -109,7 +109,7 @@ def mention_afk(msg): @sedenify( incoming=True, outgoing=False, - disable_errors=True, + disable_edited=True, group=False, bot=False, disable_notify=True, @@ -126,7 +126,7 @@ def afk_on_pm(message): apprv = True if apprv and TEMP_SETTINGS['IS_AFK']: - me = TEMP_SETTINGS['ME'] + me = message._client.me afk_duration = get_time(datetime.now(), TEMP_SETTINGS['AFK_TIME']) if message.from_user.id not in TEMP_SETTINGS['AFK_USERS']: if 'AFK_REASON' in TEMP_SETTINGS: @@ -214,12 +214,14 @@ def type_afk_is_not_true(message): ) ) for i in TEMP_SETTINGS['AFK_USERS']: - name = app.get_chat(i) + name = message._client.get_chat(int(i)) name0 = str(name.first_name) + userid = int(name.id) + reply(message, f'[{name0}](tg://user?id={userid})') send_log( get_translation( 'afkMentionUsers', - ['**', name0, str(i), '`', str(TEMP_SETTINGS['AFK_USERS'][i])], + ['**', name0, int(i), '`', str(TEMP_SETTINGS['AFK_USERS'][i])], ) ) TEMP_SETTINGS['COUNT_MSG'] = 0 diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index f54491b..64dfb6e 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -14,8 +14,16 @@ from bs4 import BeautifulSoup from requests import get + from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, sedenify, use_proxy +from sedenecem.core import ( + ProxyHandler, + edit, + extract_args, + get_translation, + sedenify, + useragent, +) @sedenify(pattern='^.magisk$') @@ -68,6 +76,40 @@ def phh(message): edit(message, releases, preview=False) +@sedenify(pattern='^.l(ineage(os)?|os)') +def get_lineageos(message): + args = extract_args(message).lower() + + if len(args) < 1: + return edit(message, f'`{get_translation("wrongCommand")}`') + else: + edit(message, f'`{get_translation("processing")}`') + + req = get( + f'https://download.lineageos.org/api/v1/{args}/nightly/*', + headers={'User-Agent': useragent()}, + ).json() + + response = req['response'] + + if response: + build = response[0] + time = datetime.utcfromtimestamp(int(build['datetime'])).strftime('%Y-%m-%d') + filename = build['filename'] + size = '{:,.2f} MB'.format(int(build['size']) / float(1 << 20)) + build_url = build['url'] + version = build['version'] + else: + return edit(message, get_translation('losNoBuild', ['**', '`', args])) + + edit( + message, + get_translation( + 'losBuild', ['**', '`', args, filename, build_url, size, version, time] + ), + ) + + @sedenify(pattern='^.device') def device(message): textx = message.reply_to_message @@ -213,7 +255,8 @@ def specs(message): edit(message, f'`{get_translation("specsUsage")}`') return - proxy = use_proxy(message) + handler = ProxyHandler() + proxy = handler.use_proxy(message) link = find_device(args, proxy) if not link: @@ -222,10 +265,7 @@ def specs(message): req = get( link, - headers={ - 'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' - '+http://www.google.com/bot.html)' - }, + headers={'User-Agent': useragent()}, proxies=proxy, ) soup = BeautifulSoup(req.text, features='html.parser') @@ -310,10 +350,7 @@ def replace_query(query): query = replace_query(raw_query) req = get( f'https://www.gsmarena.com/res.php3?{query}', - headers={ - 'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; ' - '+http://www.google.com/bot.html)' - }, + headers={'User-Agent': useragent()}, proxies=proxy, ) soup = BeautifulSoup(req.text, features='html.parser') @@ -391,7 +428,7 @@ def ofrp_get(url): try: head = { 'Accept-Language': 'en-US,en;q=0.8', - 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', + 'User-Agent': useragent(), 'Referer': 'https://orangefox.download/en', } req = get(url, headers=head) diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index 5400699..3130491 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -27,8 +27,8 @@ # ================================================ -@sedenify(pattern='^.autopp', compat=False) -def autopic(client, message): +@sedenify(pattern='^.autopp') +def autopic(message): args = extract_args(message) autopic = KEY_AUTOPP in TEMP_SETTINGS if args == 'disable': @@ -60,7 +60,7 @@ def autopic(client, message): load.write(get(AUTO_PP).content) else: try: - for i in client.get_chat_photos('me', limit=1): + for i in message._client.get_chat_photos('me', limit=1): downloaded_file_name = download_media_wc( i.file_id, downloaded_file_name ) @@ -83,7 +83,7 @@ def autopic(client, message): fill=(255, 255, 255), ) img.save(photo) - client.set_profile_photo(photo=photo) + message._client.set_profile_photo(photo=photo) remove(photo) sleep(60) except BaseException: diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 183c4ef..52f5b2c 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -24,7 +24,7 @@ download_media_wc, edit, extract_args, - extract_args_arr, + extract_args_split, extract_user, get_download_dir, get_translation, @@ -110,7 +110,7 @@ def ban_user(message): @sedenify(pattern='^.unban', private=False, admin=True) def unban_user(message): - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message @@ -263,7 +263,7 @@ def unmute_user(message): edit(message, f'`{get_translation("nonSqlMode")}`') return - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message @@ -294,8 +294,8 @@ def unmute_user(message): return -@sedenify(pattern='^.promote', admin=True, private=False, compat=False) -def promote_user(client, message): +@sedenify(pattern='^.promote', admin=True, private=False) +def promote_user(message): args = extract_args(message) reply = message.reply_to_message rank = None @@ -336,7 +336,7 @@ def promote_user(client, message): if rank is not None: if len(rank) > 16: rank = rank[:16] - client.set_administrator_title(chat.id, user.id, rank) + message._client.set_administrator_title(chat.id, user.id, rank) edit( message, get_translation('promoteResult', ['**', user.first_name, user.id, '`']), @@ -355,7 +355,7 @@ def promote_user(client, message): @sedenify(pattern='^.demote', private=False, admin=True) def demote_user(message): - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message @@ -441,8 +441,8 @@ def unpin_message(message): edit(message, f'`{get_translation("wrongCommand")}`') -@sedenify(pattern='^.(admins|bots|user(s|sdel))$', compat=False, private=False) -def get_users(client, message): +@sedenify(pattern='^.(admins|bots|user(s|sdel))$', private=False) +def get_users(message): args = message.text.split(' ', 1) users = args[0][1:5] == 'user' showdel = users and args[0][-3:] == 'del' @@ -470,7 +470,7 @@ def get_users(client, message): try: chat_id = message.chat.id - find = client.get_chat_members(chat_id, filter=filtr) + find = message._client.get_chat_members(chat_id, filter=filtr) for i in find: if not i.user.is_deleted and showdel: continue @@ -503,8 +503,8 @@ def get_users(client, message): ) -@sedenify(pattern='^.zombies', private=False, compat=False) -def zombie_accounts(client, message): +@sedenify(pattern='^.zombies', private=False) +def zombie_accounts(message): args = extract_args(message).lower() chat = message.chat count = 0 @@ -512,7 +512,7 @@ def zombie_accounts(client, message): if args != 'clean': edit(message, f'`{get_translation("zombiesFind")}`') - for i in client.get_chat_members(chat.id): + for i in message._client.get_chat_members(chat.id): if i.user.is_deleted: count += 1 sleep(1) @@ -528,7 +528,7 @@ def zombie_accounts(client, message): count = 0 users = 0 - for i in client.get_chat_members(chat.id): + for i in message._client.get_chat_members(chat.id): if i.user.is_deleted: try: chat.ban_member(i.user.id) @@ -601,7 +601,7 @@ def mute_check(message): try: from sedenecem.sql import mute_sql as sql except BaseException: - return + message.continue_propagation() chat = message.chat user = message.from_user diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/blacklist.py index e7baac3..b17b233 100644 --- a/sedenbot/modules/blacklist.py +++ b/sedenbot/modules/blacklist.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat.py index 24a1f4f..492a031 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -62,18 +62,18 @@ def mutechat(message): send_log(get_translation('chatLog', [message.chat.id])) -@sedenify(incoming=True, compat=False) -def keep_read(client, message): +@sedenify(incoming=True, outgoing=False) +def keep_read(message): if message.from_user and message.from_user.is_self: message.continue_propagation() try: from sedenecem.sql.keep_read_sql import is_kread except BaseException: - return + message.continue_propagation() if is_muted(message.chat.id): - client.read_chat_history(message.chat.id) + message._client.read_chat_history(message.chat.id) message.continue_propagation() diff --git a/sedenbot/modules/colors.py b/sedenbot/modules/colors.py index 3aa5b99..985616e 100644 --- a/sedenbot/modules/colors.py +++ b/sedenbot/modules/colors.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/covid19.py b/sedenbot/modules/covid19.py deleted file mode 100644 index 514629b..0000000 --- a/sedenbot/modules/covid19.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from json import loads -from re import sub - -from bs4 import BeautifulSoup -from requests import get -from sedenbot import HELP -from sedenecem.core import edit, get_translation, sedenify - - -@sedenify(pattern='^.covid(|19)$') -def covid(message): - try: - req = get( - 'https://covid19.saglik.gov.tr/', - headers={ - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3', - 'Cache-Control': 'max-age=0', - 'Connection': 'keep-alive', - 'Referer': 'https://covid19.saglik.gov.tr/', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36', - }, - ) - - soup = BeautifulSoup(req.text, 'html.parser') - scripts = soup.find_all('script') - for script in scripts: - turejq = str(script) - if 'var sondurumjson' in turejq: - result = loads( - sub( - '(<(/|)script(.*)>|\/\/|<!\[CDATA\[|\]\]>|;|var sondurumjson =|\n|\s|var haftalikdurumjson(.*))', - '', - turejq, - ) - ) - break - except BaseException: - edit(message, f'`{get_translation("covidError")}`') - return - - if len(result) > 0: - result = result[0] - - def del_dots(res): - return empty_check(res.replace('.', '')) - - def empty_check(res): - return res if len(res.strip()) else 'N/A' - - sonuclar = ( - f'**{get_translation("covidData")}**\n' - + f'\n**{get_translation("covidDate")}** {result["tarih"]}\n' - + f'\n**{get_translation("covidTotal")}**\n' - + f'**{get_translation("covidTests")}** `{del_dots(result["toplam_test"])}`\n' - + f'**{get_translation("covidCases")}** `{del_dots(result["toplam_hasta"])}`\n' - + f'**{get_translation("covidDeaths")}** `{del_dots(result["toplam_vefat"])}`\n' - + f'**{get_translation("covidSeriouslyill")}** `{del_dots(result["agir_hasta_sayisi"])}`\n' - + f'**{get_translation("covidPneumonia")}** `%{empty_check(result["hastalarda_zaturre_oran"])}`\n' - + f'**{get_translation("covidHealed")}** `{del_dots(result["toplam_iyilesen"])}`\n' - + f'\n**{get_translation("covidToday")}**\n' - + f'**{get_translation("covidTests")}** `{del_dots(result["gunluk_test"])}`\n' - + f'**{get_translation("covidCases")}** `{del_dots(result["gunluk_vaka"])}`\n' - + f'**{get_translation("covidPatients")}** `{del_dots(result["gunluk_hasta"])}`\n' - + f'**{get_translation("covidDeaths")}** `{del_dots(result["gunluk_vefat"])}`\n' - + f'**{get_translation("covidHealed")}** `{del_dots(result["gunluk_iyilesen"])}`' - ) - - edit(message, sonuclar) - - -HELP.update({'covid19': get_translation('covidInfo')}) diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index a0cd02d..8459126 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -24,7 +24,6 @@ @sedenify(pattern='^.(deepf|f)ry') def deepfry(message): - text = (message.text or message.caption).split(' ', 1) fry = parse_cmd(text[0]) == 'fry' @@ -61,7 +60,6 @@ def deepfry(message): edit(message, f'`{get_translation("deepfryDownload")}`') image_file = download_media_wc(reply, 'image.png') image = Image.open(image_file) - remove(image_file) # Apply effect to media edit(message, get_translation('deepfryApply', ['`', f'{"" if fry else "deep"}'])) @@ -73,6 +71,7 @@ def deepfry(message): fried_io.close() reply_img(reply or message, 'image.jpeg', delete_file=True) + remove(image_file) message.delete() @@ -90,17 +89,17 @@ def deepfry_media(img: Image, fry: bool) -> Image: temp_num = uniform(0.8, 0.9) if fry else 0.75 img = img.resize( - (int(width ** temp_num), int(height ** temp_num)), resample=Image.LANCZOS + (int(width**temp_num), int(height**temp_num)), resample=Image.LANCZOS ) temp_num = uniform(0.85, 0.95) if fry else 0.88 img = img.resize( - (int(width ** temp_num), int(height ** temp_num)), resample=Image.BILINEAR + (int(width**temp_num), int(height**temp_num)), resample=Image.BILINEAR ) temp_num = uniform(0.89, 0.98) if fry else 0.9 img = img.resize( - (int(width ** temp_num), int(height ** temp_num)), resample=Image.BICUBIC + (int(width**temp_num), int(height**temp_num)), resample=Image.BICUBIC ) img = img.resize((width, height), resample=Image.BICUBIC) diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index da9c257..dbe8174 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -13,11 +13,21 @@ from bs4 import BeautifulSoup from requests import Session, get -from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, get_webdriver, reply_doc, sedenify - +from selenium.webdriver.common.by import By -@sedenify(pattern=r'^.direct') +from sedenbot import HELP +from sedenecem.core import ( + edit, + extract_args, + get_translation, + get_webdriver, + reply_doc, + sedenify, + useragent, +) + + +@sedenify(pattern='^.direct') def direct(message): edit(message, f'`{get_translation("processing")}`') textx = message.reply_to_message @@ -56,7 +66,7 @@ def check(url, items, starts=False): reply += gdrive(link, message) elif check(link, 'zippyshare.com'): reply += zippy_share(link) - elif check(link, 'yadi.sk'): + elif check(link, ['yadi.sk', 'disk.yandex.com']): reply += yandex_disk(link) elif check(link, 'mediafire.com'): reply += mediafire(link) @@ -82,9 +92,13 @@ def gdrive(link: str, message) -> str: headers = {'user-agent': useragent()} response = get(url=dl_url, headers=headers, stream=True) if response.status_code == 429: - reply_doc(message, 'cookies.txt', caption=get_translation("directGdriveCookieUsage")) + reply_doc( + message, 'cookies.txt', caption=get_translation("directGdriveCookieUsage") + ) reply += get_translation("directGdriveCookie") - cookies = {'__Secure-1PSID': 'OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XqwPg5PZZKN6KNnoBsXJrQw.'} + cookies = { + '__Secure-1PSID': 'OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XqwPg5PZZKN6KNnoBsXJrQw.' + } response = get(url=dl_url, headers=headers, cookies=cookies, stream=True) name = response.headers.get('Content-Disposition').split(';')[1].split('"')[1] size = f'{int(response.headers.get("Content-Length")) / (1024 * 1024):0.2f}' @@ -98,11 +112,11 @@ def zippy_share(link: str) -> str: reply = '' driver = get_webdriver() driver.get(link) - left = driver.find_element_by_xpath('//div[contains(@class, "left")]') - font = left.find_elements_by_xpath('.//font') + left = driver.find_element(By.XPATH, '//div[contains(@class, "left")]') + font = left.find_elements(By.XPATH, './/font') name = font[2].text size = font[4].text - button = driver.find_element_by_xpath('//a[contains(@id, "dlbutton")]') + button = driver.find_element(By.XPATH, '//a[contains(@id, "dlbutton")]') link = button.get_attribute('href') reply += '{}\n'.format(get_translation('directZippyLink', [name, size, link])) driver.quit() @@ -183,13 +197,12 @@ def github(link: str) -> str: def androidfilehost(link: str) -> str: fid = findall(r'\?fid=[0-9]+', link)[0] session = Session() - user_agent = useragent() - headers = {'user-agent': user_agent} + headers = {'user-agent': useragent()} res = session.get(link, headers=headers, allow_redirects=True) headers = { 'origin': 'https://androidfilehost.com', 'accept-language': 'en-US,en;q=0.9', - 'user-agent': user_agent, + 'user-agent': useragent(), 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'x-mod-sbb-ctype': 'xhr', 'accept': '*/*', @@ -221,14 +234,4 @@ def androidfilehost(link: str) -> str: return reply -def useragent(): - req = get('https://useragents.io/random') - soup = BeautifulSoup(req.text, 'html.parser') - agent = soup.find_all('td') - for i in agent: - return i.find('a').text - - return 'Googlebot/2.1 (+http://www.google.com/bot.html)' - - HELP.update({'direct': get_translation('directInfo')}) diff --git a/sedenbot/modules/ecem.py b/sedenbot/modules/ecem.py index 74a34a2..a3e543a 100644 --- a/sedenbot/modules/ecem.py +++ b/sedenbot/modules/ecem.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 34e0e07..af9d30f 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -14,7 +14,7 @@ from sedenecem.core import ( download_media_wc, edit, - extract_args, + extract_args_split, get_translation, reply_audio, reply_video, @@ -24,70 +24,71 @@ @sedenify(pattern='^.earrape') def earrape(message): - args = extract_args(message).split(' ', 1) + args = extract_args_split(message) reply = message.reply_to_message - util = args[0].lower() - if util == 'mp4': - if not ( - reply - and ( - reply.video - or reply.video_note - or (reply.document and 'video' in reply.document.mime_type) - ) - ): - edit(message, f'`{get_translation("wrongMedia")}`') - else: - edit(message, f'`{get_translation("applyEarrape")}`') - media = download_media_wc(reply) - process = Popen( - [ - 'ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp4', - ] - ) - process.communicate() - edit(message, f'`{get_translation("uploadMedia")}`') - reply_video(reply or message, f'{media}.mp4', delete_file=True) - remove(media) - message.delete() - elif util == 'mp3': - if not ( - reply - and ( - reply.video - or reply.video_note - or ( - reply.audio - or reply.voice + if args: + util = args[0].lower() + if util == 'mp4': + if not ( + reply + and ( + reply.video + or reply.video_note or (reply.document and 'video' in reply.document.mime_type) ) - ) - ): - edit(message, f'`{get_translation("wrongMedia")}`') - else: - edit(message, f'`{get_translation("applyEarrape")}`') - media = download_media_wc(reply) - process = Popen( - [ - 'ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp3', - ] - ) - process.communicate() - edit(message, f'`{get_translation("uploadMedia")}`') - reply_audio(reply or message, f'{media}.mp3', delete_file=True) - remove(media) - message.delete() + ): + edit(message, f'`{get_translation("wrongMedia")}`') + else: + edit(message, f'`{get_translation("applyEarrape")}`') + media = download_media_wc(reply) + process = Popen( + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'acrusher=.1:1:64:0:log', + f'{media}.mp4', + ] + ) + process.communicate() + edit(message, f'`{get_translation("uploadMedia")}`') + reply_video(reply or message, f'{media}.mp4', delete_file=True) + remove(media) + message.delete() + elif util == 'mp3': + if not ( + reply + and ( + reply.video + or reply.video_note + or ( + reply.audio + or reply.voice + or (reply.document and 'video' in reply.document.mime_type) + ) + ) + ): + edit(message, f'`{get_translation("wrongMedia")}`') + else: + edit(message, f'`{get_translation("applyEarrape")}`') + media = download_media_wc(reply) + process = Popen( + [ + 'ffmpeg', + '-i', + f'{media}', + '-af', + 'acrusher=.1:1:64:0:log', + f'{media}.mp3', + ] + ) + process.communicate() + edit(message, f'`{get_translation("uploadMedia")}`') + reply_audio(reply or message, f'{media}.mp3', delete_file=True) + remove(media) + message.delete() else: edit(message, f'`{get_translation("wrongCommand")}`') return diff --git a/sedenbot/modules/env.py b/sedenbot/modules/env.py index dc4b642..0418b1e 100644 --- a/sedenbot/modules/env.py +++ b/sedenbot/modules/env.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -25,11 +25,11 @@ from sedenecem.core import edit, extract_args, get_translation, sedenify -@sedenify(pattern='^.env', compat=False) -def manage_env(client, message): +@sedenify(pattern='^.env') +def manage_env(message): action = extract_args(message).split(' ', 1) - if action[0] == 'list': + if action and action[0] == 'list': pass elif len(action) < 2 or action[0] not in ['get', 'set', 'rem', 'copy', 'move']: edit(message, f"`{get_translation('wrongCommand')}`") @@ -83,7 +83,7 @@ def manage_env(client, message): edit(message, get_translation('envSetSuccess', ['`', '**', key])) sleep(2) - restart(client, message) + restart(message) elif action[0] == 'get': items = action[1].split(' ', 1) @@ -121,7 +121,7 @@ def manage_env(client, message): edit(message, get_translation('envRemSuccess', ['`', '**', items[0]])) sleep(2) - restart(client, message) + restart(message) elif action[0] in ['copy', 'move']: items = action[1].split(' ', 1) @@ -161,14 +161,14 @@ def manage_env(client, message): get_translation('envMoveSuccess', ['`', '**', items[0], items[1]]), ) sleep(2) - restart(client, message) + restart(message) return edit( message, get_translation('envCopySuccess', ['`', '**', items[0], items[1]]) ) sleep(2) - restart(client, message) + restart(message) elif action[0] == 'list': out = '' if heroku_mode: diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index e479c28..a4a4419 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -8,16 +8,12 @@ # from math import floor, sqrt -from os import remove, path +from os import path, remove from exifread import process_file + from sedenbot import HELP -from sedenecem.core import ( - download_media_wc, - edit, - get_translation, - sedenify, -) +from sedenecem.core import download_media_wc, edit, get_translation, sedenify @sedenify(pattern="^.exif$") @@ -71,7 +67,6 @@ def exif_data(message): converted = "" if not str(data[i]) in ['0', '0/0', '[0/0, 0/0, 0/0]']: - # GPS Data if "GPS" == i.split(" ")[0]: converted = unit_dict[i](data[i].printable, google_coordinate) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index e6bb832..84fba7b 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -7,12 +7,13 @@ # All rights reserved. See COPYING, AUTHORS. # -from datetime import datetime from functools import reduce from re import DOTALL, sub +from time import localtime from bs4 import BeautifulSoup from requests import get + from sedenbot import HELP from sedenecem.core import edit, extract_args, sedenify @@ -54,7 +55,6 @@ def get_val(st): edit(message, vakitler) -""" @sedenify(pattern='^.ramazan') def ramazan(message): konum = extract_args(message).lower() @@ -65,51 +65,56 @@ def ramazan(message): result = get_result(konum) except BaseException: return edit(message, f'`{konum} için bir bilgi bulunamadı.`') - saat_imsak = ( - result.find('div', {'data-vakit-name': 'imsak'}) - .find('div', {'class': 'tpt-time'}) - .get_text() - ) - - saat_aksam = ( - result.find('div', {'data-vakit-name': 'aksam'}) - .find('div', {'class': 'tpt-time'}) - .get_text() + res1 = result.body.findAll('div', {'class': ['body-content']}) + res1 = res1[0].findAll('script') + res1 = sub( + r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1[0]), flags=DOTALL ) + res1 = sub('\n\n', '\n', res1)[:-1].split('\n') - saat_yatsi = ( - result.find('div', {'data-vakit-name': 'yatsi'}) - .find('div', {'class': 'tpt-time'}) - .get_text() - ) + def get_val(st): + return [i.split('=')[1].replace('"', '').strip() for i in st[:-1].split(';')] - sehir = ( - result.find('table', {'class': 'table vakit-table'}) - .find('caption') - .get_text() - .split(' ')[0] - ) + res2 = get_val(res1[1]) + res3 = get_val(res1[2]) - saatler_yarin = result.find_all('tr')[2] - aksam_yarin = saatler_yarin.get_text().split('\n')[6] - yatsi_yarin = saatler_yarin.get_text().split('\n')[7] - imsak_yarin = saatler_yarin.get_text().split('\n')[2] + current_time = localtime() + current_hour = current_time.tm_hour + current_minute = current_time.tm_min + + sahur_vakti, iftar_vakti, teravih_vakti = res3[0], res3[4], res3[5] + + def get_remaining_time(vakt, current_hour, current_minute): + vakt_time = vakt.split(':') + vakt_hour = int(vakt_time[0]) + vakt_minute = int(vakt_time[1]) + + if current_hour < vakt_hour or ( + current_hour == vakt_hour and current_minute < vakt_minute + ): + minutes_left = (vakt_hour - current_hour) * 60 + ( + vakt_minute - current_minute + ) + hours_left = minutes_left // 60 + minutes_left = minutes_left % 60 + return f'{vakt} ({hours_left}s {minutes_left}dk kaldı)' + else: + return f'{vakt}' - iftar_saat = calculate_time(saat_aksam, aksam_yarin) - yatsi_saat = calculate_time(saat_yatsi, yatsi_yarin) - imsak_saat = calculate_time(saat_imsak, imsak_yarin) + sahur = get_remaining_time(sahur_vakti, current_hour, current_minute) + iftar = get_remaining_time(iftar_vakti, current_hour, current_minute) + teravih = get_remaining_time(teravih_vakti, current_hour, current_minute) vakitler = ( '**Diyanet Ramazan Vakitleri**\n\n' - + f'📍 **Yer:** `{sehir}`\n\n' - + f'🏙 **Sahur:** `{saat_imsak} ({imsak_saat[0]}s {imsak_saat[1]}dk kaldı)`\n' - + f'🌃 **İftar:** `{saat_aksam} ({iftar_saat[0]}s {iftar_saat[1]}dk kaldı)`\n' - + f'🌌 **Teravih:** `{saat_yatsi} ({yatsi_saat[0]}s {yatsi_saat[1]}dk kaldı)`\n\n' + + f'📍 **Yer:** `{res2[1]}`\n\n' + + (f'🏙 **Sahur:** `{sahur}`\n') + + (f'🌃 **İftar:** `{iftar}`\n') + + (f'🌌 **Teravih:** `{teravih}`\n\n') + '**Hayırlı Ramazanlar**' ) edit(message, vakitler) -""" def find_loc(konum): @@ -137,30 +142,6 @@ def get_result(konum): request = get(f'https://namazvakitleri.diyanet.gov.tr/tr-TR/{knum}') return BeautifulSoup(request.content, 'html.parser') -""" -def calculate_time(saat, yarin_saat): - now = datetime.now().timestamp() - now_t = datetime.fromtimestamp(now).strftime('%d.%m.%Y') - iftar_vakit = datetime.strptime(f'{saat} {now_t}', '%H:%M %d.%m.%Y').timestamp() - if iftar_vakit < now: - temp = now + 24 * 60 * 60 - tomorrow = datetime.fromtimestamp(temp).strftime('%d.%m.%Y') - yarin_iftar = datetime.strptime( - f'{yarin_saat} {tomorrow}', '%H:%M %d.%m.%Y' - ).timestamp() - sonuc = yarin_iftar - now - kalan_saat, kalan_dk = timedelta(sonuc) - else: - sonuc = iftar_vakit - now - kalan_saat, kalan_dk = timedelta(sonuc) - return kalan_saat, kalan_dk - - -def timedelta(time): - saat = int(time / 3600) - dakika = int((time % 3600) / 60) - return saat, dakika -""" sehirler = [ '01 Adana 9146', diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index 04d6e43..9c1cc17 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index 722b573..a49ed8b 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -24,6 +24,7 @@ download_media_wc, edit, extract_args, + extract_args_split, get_translation, reply_doc, sedenify, @@ -117,11 +118,7 @@ def upload_to_telegram(self, url): size = file_info.get("size") file_name = file_info.get("name") - if ( - int(size) > 4294967296 - if self.message.from_user.is_premium - else int(size) > 2147483648 - ): + if int(size) > 4294967296 if self.message.from_user.is_premium else 2147483648: return edit(self.message, get_translation("tgUpLimit", ["`"])) else: file_name = self.download_from_gdrive(url) @@ -153,7 +150,7 @@ def download_from_gdrive(self, link) -> str: done = False while done is False: status, done = downloader.next_chunk() - edit( + progress = edit( self.message, get_translation( "gdriveDown", @@ -237,7 +234,7 @@ def upload_to_gdrive(self, filename): def drive_auth(message): global flow user_id = message.from_user.id - args = extract_args(message).split() + args = extract_args_split(message) if len(args) == 0: creds = get(user_id) diff --git a/sedenbot/modules/git.py b/sedenbot/modules/git.py index c662d00..f4b72df 100644 --- a/sedenbot/modules/git.py +++ b/sedenbot/modules/git.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -14,7 +14,7 @@ from sedenecem.core import edit, extract_args, get_translation, sedenify -@sedenify(pattern='^.github') +@sedenify(pattern='^.git(|hub)') def github(message): args = extract_args(message) @@ -105,7 +105,7 @@ def get_repos(): ) + format_info(get_translation("gitCreationDate"), created) + format_info(get_translation("gitDateOfUpdate"), updated) - + f'\n{get_translation("gitRepoList")}\n{get_repos()}', + + f'**\n{get_translation("gitRepoList")}\n{get_repos()}**', preview=False, ) diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 98e5779..357a0b2 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -11,11 +11,11 @@ from pyrogram import enums from pyrogram.types import ChatPermissions -from sedenbot import BRAIN, HELP, LOGS, TEMP_SETTINGS +from sedenbot import BRAIN, HELP, LOGS from sedenbot.modules.ban import get_reason from sedenecem.core import ( edit, - extract_args_arr, + extract_args_split, extract_user, get_translation, sedenify, @@ -40,8 +40,8 @@ def globals_init(): globals_init() -@sedenify(pattern='^.gban', compat=False) -def gban_user(client, message): +@sedenify(pattern='^.gban') +def gban_user(message): reply = message.reply_to_message edit(message, f'`{get_translation("banProcess")}`') @@ -76,7 +76,7 @@ def gban_user(client, message): ), ) try: - common_chats = client.get_common_chats(user.id) + common_chats = message._client.get_common_chats(user.id) for i in common_chats: i.ban_member(user.id) except BaseException: @@ -94,7 +94,7 @@ def gban_user(client, message): @sedenify(pattern='^.(ung|gun)ban', compat=False) def ungban_user(client, message): - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message @@ -140,7 +140,7 @@ def find_member(dialog): try: dialogs = client.get_dialogs() - me_id = TEMP_SETTINGS['ME'].id + me_id = message._client.me.id chats = [ dialog.chat for dialog in dialogs @@ -245,7 +245,7 @@ def gmute_user(client, message): @sedenify(pattern='^.(ung|gun)mute', compat=False) def ungmute_user(client, message): - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') reply = message.reply_to_message diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index f85533a..0853db5 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -8,16 +8,25 @@ # from math import floor -from os import execl, getpid +from os import execl, getpid, kill, name +from signal import CTRL_C_EVENT, SIGINT from sys import argv, executable from heroku3 import from_key from requests import get + from sedenbot import HELP, HEROKU_APPNAME, HEROKU_KEY -from sedenecem.core import edit, get_translation, reply_doc, sedenify, send_log +from sedenecem.core import ( + edit, + get_translation, + reply_doc, + sedenify, + send_log, + useragent, +) -@sedenify(pattern='^.(quo|ko)ta$') +@sedenify(pattern='^.(qu|k)ota$') def dyno(message): if not HEROKU_KEY: edit(message, f"`{get_translation('notHeroku')}`") @@ -49,7 +58,7 @@ def dyno(message): acc_id = heroku.account().id headers = { - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', + 'User-Agent': useragent(), 'Authorization': f'Bearer {HEROKU_KEY}', 'Accept': 'application/vnd.heroku+json; version=3.account-quotas', } @@ -115,12 +124,12 @@ def get_app_quota(): ) -@sedenify(pattern='^.(restart|yb)$') +@sedenify(pattern='^.(re(star|boo)t|yb)$') def _restart(message): return restart(message) -@sedenify(pattern='^.d(restart|yb)$') +@sedenify(pattern='^.d(re(star|boo)t|yb)$') def _drestart(message): return restart(message, dyno=True) @@ -180,8 +189,7 @@ def shutdown(message): def std_off(): try: - from sedenecem.core.misc import __status_out__ - __status_out__(f'kill -7 {getpid()}') + kill(getpid(), CTRL_C_EVENT if name == 'nt' else SIGINT) except Exception: pass diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 897bfb4..ad8eeff 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -22,8 +22,8 @@ ) -@sedenify(pattern='^.whois', compat=False) -def who_is(client, message): +@sedenify(pattern='^.whois') +def who_is(message): find_user = extract_user(message) reply = message.reply_to_message media_perm = None @@ -41,7 +41,7 @@ def who_is(client, message): for reply_user in find_user: try: - reply_chat = client.get_chat(reply_user.id) + reply_chat = message._client.get_chat(reply_user.id) except Exception: return edit(message, f'`{get_translation("whoisError")}`') if reply_user or reply_chat is not None: @@ -60,16 +60,17 @@ def who_is(client, message): else get_translation('notSet') ) user_id = reply_user.id - photos = client.get_chat_photos_count(user_id) + photos = message._client.get_chat_photos_count(user_id) dc_id = reply_user.dc_id or get_translation('notSet') bot = reply_user.is_bot - chats = len(client.get_common_chats(user_id)) + chats = len(message._client.get_common_chats(user_id)) premium = reply_user.is_premium bio = reply_chat.bio or get_translation('notSet') status = reply_user.status - last_seen = LastSeen(bot, status) - sudo = SudoCheck(user_id) - blacklist = BlacklistCheck(user_id) + user_utils = UserUtils(BRAIN, BLACKLIST) + last_seen = user_utils.last_seen(bot, status) + sudo = user_utils.sudo_check(user_id) + blacklist = user_utils.blacklist_check(user_id) caption = get_translation( 'whoisResult', @@ -98,35 +99,38 @@ def who_is(client, message): return edit(message, caption) -def LastSeen(bot, status): - if bot: - return 'BOT' - elif status == enums.UserStatus.ONLINE: - return get_translation('statusOnline') - elif status == enums.UserStatus.OFFLINE: - return get_translation('statusOffline') - elif status == enums.UserStatus.RECENTLY: - return get_translation('statusRecently') - elif status == enums.UserStatus.LAST_WEEK: - return get_translation('statusWeek') - elif status == enums.UserStatus.LAST_MONTH: - return get_translation('statusMonth') - elif status == enums.UserStatus.LONG_AGO: - return get_translation('statusLong') - - -def SudoCheck(user_id): - if user_id in BRAIN: - return get_translation('sudoCheck') - - -def BlacklistCheck(user_id): - if user_id in BLACKLIST: - return get_translation('blacklistCheck') - - -@sedenify(pattern='^.ginfo', compat=False) -def get_chat_info(client, message): +class UserUtils: + def __init__(self, brain, blacklist): + self.brain = brain + self.blacklist = blacklist + + def last_seen(self, bot, status): + if bot: + return 'BOT' + elif status == enums.UserStatus.ONLINE: + return get_translation('statusOnline') + elif status == enums.UserStatus.OFFLINE: + return get_translation('statusOffline') + elif status == enums.UserStatus.RECENTLY: + return get_translation('statusRecently') + elif status == enums.UserStatus.LAST_WEEK: + return get_translation('statusWeek') + elif status == enums.UserStatus.LAST_MONTH: + return get_translation('statusMonth') + elif status == enums.UserStatus.LONG_AGO: + return get_translation('statusLong') + + def sudo_check(self, user_id): + if user_id in self.brain: + return get_translation('sudoCheck') + + def blacklist_check(self, user_id): + if user_id in self.blacklist: + return get_translation('blacklistCheck') + + +@sedenify(pattern='^.ginfo') +def get_chat_info(message): args = extract_args(message) reply = message.reply_to_message chat_id = message.chat.id @@ -134,8 +138,8 @@ def get_chat_info(client, message): edit(message, f'`{get_translation("processing")}`') try: - reply_chat = client.get_chat(args or chat_id) - peer = client.resolve_peer(args or chat_id) + reply_chat = message._client.get_chat(args or chat_id) + peer = message._client.resolve_peer(args or chat_id) except PeerIdInvalid: edit(message, f'`{get_translation("groupNotFound")}`') return @@ -145,7 +149,7 @@ def get_chat_info(client, message): media_perm = perm.can_send_media_messages try: - online_users = client.invoke(GetOnlines(peer=peer)) + online_users = message._client.invoke(GetOnlines(peer=peer)) online = online_users.onlines except PeerIdInvalid: edit(message, f'`{get_translation("groupNotFound")}`') diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index 204c405..63a1a0f 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -12,7 +12,13 @@ from pyrogram import enums from requests import get from sedenbot import HELP -from sedenecem.core import edit, extract_args, get_translation, parse_cmd, sedenify +from sedenecem.core import ( + edit, + extract_args_split, + get_translation, + parse_cmd, + sedenify, +) def parseShipEntity(jsonEntity: dict) -> str: @@ -70,31 +76,31 @@ def getShipEntity(company: str, trackId: int or str) -> dict or None: return None -@sedenify(pattern='^.(yurti[çc]i|aras|ptt|mng|ups|s[üu]rat|trendyol|hepsijet)') +@sedenify(pattern='^.(hepsijet|trendyol|yurti[cç]i|(s[uü]ra|pt)t|aras|mng|ups)') def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") - trackId = extract_args(message) + trackId = extract_args_split(message) comp = parse_cmd(message.text) - if not trackId or len(trackId.split(' ')) > 1: + if not trackId or len(trackId) > 1: edit(message, f"`{get_translation('wrongCommand')}`") return match comp.replace('ç', 'c').replace('ü', 'u'): case 'yurtici': - kargo_data = getShipEntity(company='yurtici', trackId=trackId) + kargo_data = getShipEntity(company='yurtici', trackId=trackId[0]) case 'aras': - kargo_data = getShipEntity(company='aras', trackId=trackId) + kargo_data = getShipEntity(company='aras', trackId=trackId[0]) case 'ptt': - kargo_data = getShipEntity(company='ptt', trackId=trackId) + kargo_data = getShipEntity(company='ptt', trackId=trackId[0]) case 'mng': - kargo_data = getShipEntity(company='mng', trackId=trackId) + kargo_data = getShipEntity(company='mng', trackId=trackId[0]) case 'ups': - kargo_data = getShipEntity(company='ups', trackId=trackId) + kargo_data = getShipEntity(company='ups', trackId=trackId[0]) case 'surat': - kargo_data = getShipEntity(company='surat', trackId=trackId) + kargo_data = getShipEntity(company='surat', trackId=trackId[0]) case 'trendyol': - kargo_data = getShipEntity(company='trendyolexpress', trackId=trackId) + kargo_data = getShipEntity(company='trendyolexpress', trackId=trackId[0]) case 'hepsijet': - kargo_data = getShipEntity(company='hepsijet', trackId=trackId) + kargo_data = getShipEntity(company='hepsijet', trackId=trackId[0]) if kargo_data: text = parseShipEntity(kargo_data) edit(message, text, parse=enums.ParseMode.HTML) diff --git a/sedenbot/modules/lastfm.py b/sedenbot/modules/lastfm.py index cd9d97c..645e19b 100644 --- a/sedenbot/modules/lastfm.py +++ b/sedenbot/modules/lastfm.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/locks.py index eeb5da0..128c819 100644 --- a/sedenbot/modules/locks.py +++ b/sedenbot/modules/locks.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -12,8 +12,8 @@ from sedenecem.core import edit, get_translation, parse_cmd, sedenify -@sedenify(pattern=r'^.(un|)lock', compat=False, private=False, admin=True) -def lock_unlock_chat(client, message): +@sedenify(pattern='^.(un|)lock', private=False, admin=True) +def lock_unlock_chat(message): text = (message.text or message.caption).replace(r'\s+', ' ').split(' ', 1) unlock = parse_cmd(text[0])[:2] == 'un' @@ -76,7 +76,7 @@ def lock_unlock_chat(client, message): edit(message, get_translation('lockError', ['`', args])) return - chat = client.get_chat(message.chat.id) + chat = message._client.get_chat(message.chat.id) msg = get_on_none(msg, chat.permissions.can_send_messages) media = get_on_none(media, chat.permissions.can_send_media_messages) @@ -88,7 +88,7 @@ def lock_unlock_chat(client, message): changeinfo = get_on_none(changeinfo, chat.permissions.can_change_info) try: - client.set_chat_permissions( + message._client.set_chat_permissions( message.chat.id, ChatPermissions( can_send_messages=msg, diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/lyrics.py index 477fd3b..029bbf5 100644 --- a/sedenbot/modules/lyrics.py +++ b/sedenbot/modules/lyrics.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index b57b15d..cb63ba3 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -12,6 +12,7 @@ from re import sub from textwrap import wrap from time import sleep +from pyrogram import enums from cowpy import cow from PIL import Image, ImageDraw, ImageFont @@ -21,6 +22,7 @@ download_media_wc, edit, extract_args, + extract_args_split, get_download_dir, get_translation, parse_cmd, @@ -634,46 +636,66 @@ def lfy(message): ) -@sedenify(pattern=r'.scam', compat=False) -def scam(client, message): - options = [ - 'typing', - 'upload_photo', - 'record_video', - 'upload_video', - 'record_audio', - 'upload_audio', - 'upload_document', - 'find_location', - 'record_video_note', - 'upload_video_note', - 'choose_contact', - 'playing', - ] - input_str = extract_args(message) - args = input_str.split() - if len(args) == 0: - scam_action = choice(options) - scam_time = randint(30, 60) - elif len(args) == 1: +@sedenify(pattern='.action') +def set_action(message): + ACTIONS = { + 'typing': enums.ChatAction.TYPING, + 'photo': enums.ChatAction.UPLOAD_PHOTO, + 'rec_video': enums.ChatAction.RECORD_VIDEO, + 'video': enums.ChatAction.UPLOAD_VIDEO, + 'rec_audio': enums.ChatAction.RECORD_AUDIO, + 'audio': enums.ChatAction.UPLOAD_AUDIO, + 'document': enums.ChatAction.UPLOAD_DOCUMENT, + 'location': enums.ChatAction.FIND_LOCATION, + 'rec_videonote': enums.ChatAction.RECORD_VIDEO_NOTE, + 'videonote': enums.ChatAction.UPLOAD_VIDEO_NOTE, + 'game': enums.ChatAction.PLAYING, + 'contact': enums.ChatAction.CHOOSE_CONTACT, + 'speaking': enums.ChatAction.SPEAKING, + 'import_history': enums.ChatAction.IMPORT_HISTORY, + 'sticker': enums.ChatAction.CHOOSE_STICKER, + 'cancel': enums.ChatAction.CANCEL, + } + args = extract_args_split(message) + scam_action = None + scam_time = None + + if len(args) == 1: try: - scam_action = str(args[0]).lower() - scam_time = randint(30, 60) - except ValueError: - scam_action = choice(options) - scam_time = int(args[0]) + scam_action = ACTIONS[args[0].lower()] + except KeyError: + try: + scam_time = int(args[0]) + except: + pass elif len(args) == 2: - scam_action = str(args[0]).lower() - scam_time = int(args[1]) + try: + scam_action = ACTIONS[args[0].lower()] + scam_time = int(args[1]) + except KeyError: + try: + scam_time = int(args[0]) + except: + pass + except ValueError: + pass else: - edit(message, f'`{get_translation("wrongCommand")}`') + edit(message, f'{get_translation("wrongCommand")}') return + + if scam_action is None: + scam_action = choice(list(ACTIONS.values())) + + if scam_time is None: + scam_time = randint(30, 60) + try: if scam_time > 0: - chat_id = message.chat.id message.delete() - client.send_chat_action(chat_id, scam_action) - sleep(scam_time) + while scam_time > 0: + message.reply_chat_action(scam_action) + sleep(2) + scam_time = scam_time - 2 except BaseException: return @@ -860,27 +882,20 @@ def gay_calculator(message): edit(message, f'**{get_translation("gayString3", [random])}**') -@sedenify(pattern='^.react$') -def react(message): - edit(message, choice(REACTS)) - - -@sedenify(pattern='^.shg$') -def shg(message): - edit(message, choice(SHGS)) - - -@sedenify(pattern='^.run$') -def run(message): - edit(message, choice(RUNS)) - - -@sedenify(pattern='^.xda$') -def xda(message): - """ - Copyright (c) @NaytSeyd, Quotes taken - from friendly-telegram (https://gitlab.com/friendly-telegram) | 2020""" - edit(message, choice(XDA_STRINGS)) +@sedenify(pattern='^.(r(eact|un)|shg|xda)$') +def react_shg_run_xda(message): + options = message.text[1:] + if options == 'react': + options_list = REACTS + elif options == 'shg': + options_list = SHGS + elif options == 'run': + options_list = RUNS + elif options == 'xda': # Quotes taken from friendly-telegram (https://gitlab.com/friendly-telegram) + options_list = XDA_STRINGS + else: + return + edit(message, choice(options_list)) @sedenify(pattern='^.f (.*)') diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index 927a9dd..a397fce 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -11,6 +11,7 @@ from random import choice from subprocess import PIPE from subprocess import run as runapp +from pyrogram import enums from image_to_ascii import ImageToAscii from pybase64 import b64decode, b64encode @@ -19,6 +20,7 @@ download_media_wc, edit, extract_args, + extract_user, get_translation, reply, reply_doc, @@ -38,13 +40,13 @@ def random(message): @sedenify(pattern='^.chatid$', private=False) -def chatid(message): +def get_chat_id(message): edit(message, get_translation('chatidResult', ['`', str(message.chat.id)])) -@sedenify(pattern='^.invitelink$', compat=False, admin=True, private=False) -def get_invite_link(client, message): - chat = client.get_chat(message.chat.id) +@sedenify(pattern='^.invitelink$', admin=True, private=False) +def get_invite_link(message): + chat = message._client.get_chat(message.chat.id) try: url = chat.invite_link edit(message, url, preview=False) @@ -53,7 +55,7 @@ def get_invite_link(client, message): @sedenify(pattern='^.id$') -def userid(message): +def get_user_id(message): reply = message.reply_to_message if reply: if not reply.forward_from: @@ -73,10 +75,10 @@ def userid(message): edit(message, f'`{get_translation("wrongCommand")}`') -@sedenify(pattern='^.kickme$', compat=False, private=False) -def kickme(client, message): +@sedenify(pattern='^.kickme$', private=False) +def kick_me(message): edit(message, f'`{get_translation("kickmeResult")}`') - client.leave_chat(message.chat.id, 'me') + message.chat.leave() @sedenify(pattern='^.support$') @@ -89,25 +91,6 @@ def founder(message): edit(message, get_translation('founderResult', ['`', '**']), preview=False) -@sedenify(pattern='^.readme$') -def readme(message): - edit( - message, - '[Seden README.md](https://github.com/TeamDerUntergang/' - 'Telegram-SedenUserBot/blob/seden/README.md)', - preview=False, - ) - - -@sedenify(pattern='^.repo$') -def repo(message): - edit( - message, - '[Seden Repo](https://github.com/TeamDerUntergang/' 'Telegram-SedenUserBot)', - preview=False, - ) - - @sedenify(pattern='^.repeat') def repeat(message): # Copyright (c) Gegham Zakaryan | 2019 @@ -136,23 +119,25 @@ def crash(message): raise Exception(get_translation('testException')) -@sedenify(pattern='^.tagall$', compat=False, private=False) -def tagall(client, message): +@sedenify(pattern='^.tagall$', private=False) +def tag_all_users(message): msg = '@tag' chat = message.chat.id length = 0 - for member in client.get_chat_members(chat): + for member in message._client.get_chat_members(chat): if length < 4092: msg += f'[\u2063](tg://user?id={member.user.id})' length += 1 reply(message, msg, delete_orig=True) -@sedenify(pattern='^.report$', compat=False, private=False) -def report_admin(client, message): +@sedenify(pattern='^.report$', private=False) +def report_admin(message): msg = '@admin' chat = message.chat.id - for member in client.get_chat_members(chat, filter='administrators'): + for member in message._client.get_chat_members( + chat, filter=enums.ChatMembersFilter.ADMINISTRATORS + ): msg += f'[\u2063](tg://user?id={member.user.id})' re_msg = message.reply_to_message reply(re_msg if re_msg else message, msg) @@ -237,4 +222,24 @@ def img_to_ascii(message): remove(media) +@sedenify(pattern='^.mention') +def mention_user(message): + args = extract_args(message) + arr = args.split(' ', 1) + find_user = extract_user(message) + if len(find_user) < 1: + return edit(message, f'`{get_translation("banFailUser")}`') + for user in find_user: + name = ( + user.first_name.replace('\u2060', '') if user.first_name else user.username + ) + if message.reply_to_message: + edit(message, f'[{args or name}](tg://user?id={user.id})') + elif args: + if len(arr) > 1: + edit(message, f'[{arr[1]}](tg://user?id={user.id})') + else: + edit(message, f'[{args}](tg://user?id={user.id})') + + HELP.update({'misc': get_translation('miscInfo')}) diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index faffaea..d4152d0 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/ocr.py index 87e3e02..eec6163 100644 --- a/sedenbot/modules/ocr.py +++ b/sedenbot/modules/ocr.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/paste.py b/sedenbot/modules/paste.py index c25b83d..6984c35 100644 --- a/sedenbot/modules/paste.py +++ b/sedenbot/modules/paste.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -9,20 +9,18 @@ from requests import get, post from requests.exceptions import HTTPError, Timeout, TooManyRedirects + from sedenbot import HELP from sedenecem.core import edit, extract_args, get_translation, sedenify -@sedenify(pattern="^.paste") -def paste_hastebin(message): - text = message.text.strip() +@sedenify(pattern='^.paste') +def pastebin(message): + paste = extract_args(message, line=False) reply = message.reply_to_message edit(message, f'`{get_translation("processing")}`') - if not reply and len(text) <= 6: - return edit(message, f'`{get_translation("pasteErr")}`') - paste = text.replace('.paste ', '') - url = "https://hastebin.com/documents" + url = "https://dpaste.org/api/" if reply: if not reply.text: @@ -32,16 +30,19 @@ def paste_hastebin(message): try: r = post( url=url, - data=paste.encode('utf-8'), + data={ + 'content': paste.encode('utf-8'), + 'lexer': '_text', + 'expires': '3600', + }, ) except BaseException as e: - edit(message, f'`{get_translation("pasteConErr")}`') + raise e try: - resp = r.json() - key = resp['key'] - new_url = f"https://hastebin.com/{key}" - return edit(message, new_url, preview=False) + resp = r.text + out = resp.replace('"', '') + return edit(message, out, preview=False) except BaseException as e: raise e @@ -55,14 +56,14 @@ def get_hastebin_text(message): if reply: args = reply.text - if args.startswith('https://hastebin.com/'): - args = args[len('https://hastebin.com/') :] - elif args.startswith("hastebin.com/"): - args = args[len("hastebin.com/") :] + if args.startswith('https://dpaste.org/'): + args = args[len('https://dpaste.org/') :] + elif args.startswith('dpaste.org/'): + args = args[len('dpaste.org/') :] else: return edit(message, f'`{get_translation("wrongURL")}`') - resp = get(f'https://hastebin.com/raw/{args}') + resp = get(f'https://dpaste.org/{args}/raw') try: resp.raise_for_status() @@ -73,9 +74,7 @@ def get_hastebin_text(message): except TooManyRedirects as err: return edit(message, get_translation('banError', ['`', '**', err])) - out = f'`Hastebin içeriği başarıyla getirildi!`\n\n`İçerik:` {resp.text}' - - edit(message, out) + edit(message, get_translation('getPasteOut', ['`', resp.text])) -HELP.update({'hastebin': get_translation('pasteInfo')}) +HELP.update({'pastebin': get_translation('pasteInfo')}) diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 283eb79..9c49b39 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -41,75 +41,72 @@ def pmpermit_init(): disable_edited=True, disable_notify=True, group=False, - compat=False, bot=False, ) -def permitpm(client, message): +def permitpm(message): if not PM_AUTO_BAN: message.continue_propagation() - else: - if auto_accept(client, message) or message.from_user.is_self: + + if auto_accept(message) or message.from_user.is_self: + message.continue_propagation() + + if message.chat.id != 777000: + try: + from sedenecem.sql.pm_permit_sql import is_approved + except BaseException: message.continue_propagation() - if message.chat.id != 777000: - try: - from sedenecem.sql.pm_permit_sql import is_approved - except BaseException: - pass - - apprv = is_approved(message.chat.id) - notifsoff = is_muted(-1) - - if not apprv and message.text != UNAPPROVED_MSG: - if message.chat.id in TEMP_SETTINGS['PM_LAST_MSG']: - prevmsg = TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] - if message.text != prevmsg: - for message in _find_unapproved_msg(client, message.chat.id): - message.delete() - if TEMP_SETTINGS['PM_COUNT'][message.chat.id] < ( - PM_MSG_COUNT - 1 - ): - ret = reply(message, UNAPPROVED_MSG) - TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text - else: - ret = reply(message, UNAPPROVED_MSG) - if ret.text: + apprv = is_approved(message.chat.id) + notifsoff = is_muted(-1) + + if not apprv and message.text != UNAPPROVED_MSG: + if message.chat.id in TEMP_SETTINGS['PM_LAST_MSG']: + prevmsg = TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] + if message.text != prevmsg: + for i in _find_unapproved_msg(message, message.chat.id): + i.delete() + if TEMP_SETTINGS['PM_COUNT'][message.chat.id] < (PM_MSG_COUNT - 1): + ret = reply(message, UNAPPROVED_MSG) TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text + else: + ret = reply(message, UNAPPROVED_MSG) + if ret.text: + TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] = ret.text - if notifsoff: - client.read_chat_history(message.chat.id) + if notifsoff: + message._client.read_chat_history(message.chat.id) - if message.chat.id not in TEMP_SETTINGS['PM_COUNT']: - TEMP_SETTINGS['PM_COUNT'][message.chat.id] = 1 - else: - TEMP_SETTINGS['PM_COUNT'][message.chat.id] = ( - TEMP_SETTINGS['PM_COUNT'][message.chat.id] + 1 - ) + if message.chat.id not in TEMP_SETTINGS['PM_COUNT']: + TEMP_SETTINGS['PM_COUNT'][message.chat.id] = 1 + else: + TEMP_SETTINGS['PM_COUNT'][message.chat.id] = ( + TEMP_SETTINGS['PM_COUNT'][message.chat.id] + 1 + ) - if TEMP_SETTINGS['PM_COUNT'][message.chat.id] > (PM_MSG_COUNT - 1): - reply(message, f'`{get_translation("pmpermitBlock")}`') + if TEMP_SETTINGS['PM_COUNT'][message.chat.id] > (PM_MSG_COUNT - 1): + reply(message, f'`{get_translation("pmpermitBlock")}`') - try: - del TEMP_SETTINGS['PM_COUNT'][message.chat.id] - del TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] - except BaseException: - pass + try: + del TEMP_SETTINGS['PM_COUNT'][message.chat.id] + del TEMP_SETTINGS['PM_LAST_MSG'][message.chat.id] + except BaseException: + pass - client.block_user(message.chat.id) - peer: InputPeerUser = client.resolve_peer(message.chat.id) - client.invoke(ReportSpam(peer=peer)) + message._client.block_user(message.chat.id) + peer: InputPeerUser = message._client.resolve_peer(message.chat.id) + message._client.invoke(ReportSpam(peer=peer)) - send_log( - get_translation( - 'pmpermitLog', [message.chat.first_name, message.chat.id] - ) + send_log( + get_translation( + 'pmpermitLog', [message.chat.first_name, message.chat.id] ) + ) - message.continue_propagation() + message.continue_propagation() -def auto_accept(client, message): - self_user = TEMP_SETTINGS['ME'] +def auto_accept(message): + self_user = message._client.me if message.chat.id not in [self_user.id, 777000]: try: from sedenecem.sql.pm_permit_sql import approve, is_approved @@ -120,7 +117,7 @@ def auto_accept(client, message): if is_approved(chat.id): return True - for msg in client.get_chat_history(chat.id, limit=1): + for msg in message._client.get_chat_history(chat.id, limit=1): # chat.id in TEMP_SETTINGS['PM_LAST_MSG'] # and msg.text != UNAPPROVED_MSG # and @@ -134,8 +131,8 @@ def auto_accept(client, message): try: approve(chat.id) - for message in _find_unapproved_msg(client, chat.id): - message.delete() + for i in _find_unapproved_msg(message, chat.id): + i.delete() send_log( get_translation('pmAutoAccept', [chat.first_name, chat.id]) ) @@ -170,8 +167,8 @@ def notifon(message): edit(message, f'`{get_translation("pmNotifOn")}`') -@sedenify(outgoing=True, pattern='^.approve$', compat=False) -def approvepm(client, message): +@sedenify(outgoing=True, pattern='^.approve$') +def approvepm(message): try: from sedenecem.sql.pm_permit_sql import approve except BaseException: @@ -199,8 +196,8 @@ def approvepm(client, message): approve(uid) edit(message, get_translation('pmApproveSuccess', [name0, uid, '`'])) send_log(get_translation('pmApproveLog', [name0, uid])) - for message in _find_unapproved_msg(client, message.chat.id): - message.delete() + for i in _find_unapproved_msg(message, message.chat.id): + i.delete() except IntegrityError: edit(message, f'`{get_translation("pmApproveError2")}`') return @@ -238,9 +235,9 @@ def disapprovepm(message): send_log(get_translation('pmDisapprove', [name0, uid, '`'])) -def _find_unapproved_msg(client, chat_id): +def _find_unapproved_msg(message, chat_id): try: - return client.search_messages( + return message._.search_messages( chat_id, from_user='me', limit=10, query=UNAPPROVED_MSG ) except BaseException: diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index f31c4b3..5cc3622 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -31,17 +31,17 @@ # =============================================================== -@sedenify(pattern='^.reserved$', compat=False) -def reserved(client, message): - sonuc = client.invoke(GetAdminedPublicChannels()) - mesaj = '' - for channel_obj in sonuc.chats: - mesaj += f'{channel_obj.title}\n@{channel_obj.username}\n\n' - edit(message, mesaj) +@sedenify(pattern='^.reserved$') +def reserved_channels(message): + channels = message._client.invoke(GetAdminedPublicChannels()) + out = '' + for chat in channels.chats: + out += f'{chat.title}\n@{chat.username}\n\n' + edit(message, out) -@sedenify(pattern='^.name', compat=False) -def name(client, message): +@sedenify(pattern='^.name') +def set_name(message): newname = extract_args(message) if ' ' not in newname: firstname = newname @@ -51,12 +51,12 @@ def name(client, message): firstname = namesplit[0] lastname = namesplit[1] - client.invoke(UpdateProfile(first_name=firstname, last_name=lastname)) + message._client.invoke(UpdateProfile(first_name=firstname, last_name=lastname)) edit(message, f'`{get_translation("nameOk")}`') -@sedenify(pattern='^.setpfp$', compat=False) -def set_profilepic(client, message): +@sedenify(pattern='^.setpfp$') +def set_profilepic(message): reply = message.reply_to_message photo = None if ( @@ -80,7 +80,7 @@ def set_profilepic(client, message): image = image.resize((int(width * ratio), int(height * ratio))) new_photo = f'{get_download_dir()}/profile_photo_new.png' image.save(new_photo) - client.set_profile_photo(photo=new_photo) + message._client.set_profile_photo(photo=new_photo) remove(photo) remove(new_photo) edit(message, f'`{get_translation("ppChanged")}`') @@ -88,8 +88,8 @@ def set_profilepic(client, message): edit(message, f'`{get_translation("ppError")}`') -@sedenify(pattern='^.delpfp', compat=False) -def remove_profilepic(client, message): +@sedenify(pattern='^.delpfp') +def remove_profilepic(message): group = extract_args(message) if group == 'all': lim = 0 @@ -99,33 +99,33 @@ def remove_profilepic(client, message): lim = 1 count = 0 - for photo in client.get_chat_photos('me', limit=lim): - client.delete_profile_photos(photo.file_id) + for photo in message._client.get_chat_photos('me', limit=lim): + message._client.delete_profile_photos(photo.file_id) count += 1 edit(message, f'`{get_translation("ppDeleted", [count])}`') -@sedenify(pattern='^.setbio', compat=False) -def setbio(client, message): +@sedenify(pattern='^.setbio') +def set_bio(message): newbio = extract_args(message) - client.invoke(UpdateProfile(about=newbio)) + message._client.invoke(UpdateProfile(about=newbio)) edit(message, f'`{get_translation("bioSuccess")}`') -@sedenify(pattern='^.username', compat=False) -def username(client, message): +@sedenify(pattern='^.username') +def set_username(message): newusername = extract_args(message) try: - client.invoke(UpdateUsername(username=newusername)) + message._client.invoke(UpdateUsername(username=newusername)) edit(message, f'`{get_translation("usernameSuccess")}`') except UsernameOccupied: edit(message, f'`{get_translation("usernameTaken")}`') -@sedenify(pattern='^.block$', compat=False) -def blockpm(client, message): - if message.reply_to_message: - reply = message.reply_to_message +@sedenify(pattern='^.block$') +def block_pm(message): + reply = message.reply_to_message + if reply: replied_user = reply.from_user if replied_user.is_self: edit(message, f'`{get_translation("cannotBlockMyself")}`') @@ -141,7 +141,7 @@ def blockpm(client, message): name0 = aname.first_name uid = aname.id - client.block_user(uid) + message._client.block_user(uid) edit(message, f'`{get_translation("pmBlocked")}`') @@ -155,17 +155,17 @@ def blockpm(client, message): send_log(get_translation('pmBlockedLog', [name0, uid])) -@sedenify(pattern='^.unblock$', compat=False) -def unblockpm(client, message): - if message.reply_to_message: - reply = message.reply_to_message +@sedenify(pattern='^.unblock$') +def unblock_pm(message): + reply = message.reply_to_message + if reply: replied_user = reply.from_user if replied_user.is_self: edit(message, f'`{get_translation("cannotUnblockMyself")}`') return name0 = str(replied_user.first_name) uid = replied_user.id - client.unblock_user(uid) + message._client.unblock_user(uid) edit(message, f'`{get_translation("pmUnblocked")}`') send_log(get_translation('pmUnblockedLog', [name0, replied_user.id])) @@ -173,8 +173,8 @@ def unblockpm(client, message): edit(message, f'`{get_translation("pmUnblockedUsage")}`') -@sedenify(pattern='^.online', compat=False) -def online(client, message): +@sedenify(pattern='^.online') +def always_online(message): args = extract_args(message) offline = ALWAYS_ONLINE in TEMP_SETTINGS if args == 'disable': @@ -195,14 +195,14 @@ def online(client, message): while ALWAYS_ONLINE in TEMP_SETTINGS: try: - client.invoke(UpdateStatus(offline=False)) + message._client.invoke(UpdateStatus(offline=False)) sleep(5) except BaseException: return -@sedenify(pattern='^.stats$', compat=False) -def user_stats(client, message): +@sedenify(pattern='^.stats$') +def user_stats(message): edit(message, f'`{get_translation("processing")}`') chats = 0 channels = 0 @@ -212,7 +212,7 @@ def user_stats(client, message): bots = 0 unread = 0 user = [] - for i in client.get_dialogs(): + for i in message._client.get_dialogs(): chats += 1 if i.chat.type == enums.ChatType.CHANNEL: channels += 1 @@ -227,7 +227,7 @@ def user_stats(client, message): if i.unread_messages_count > 0: unread += 1 - users = client.get_users(user) + users = message._client.get_users(user) for i in users: if i.is_bot: bots += 1 diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index 3f051c5..98d5b46 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -10,7 +10,7 @@ from time import sleep from pyrogram.errors import FloodWait -from sedenbot import HELP, TEMP_SETTINGS +from sedenbot import HELP from sedenecem.core import ( edit, extract_args, @@ -21,8 +21,8 @@ ) -@sedenify(pattern='^.purge$', compat=False, admin=True) -def purge(client, message): +@sedenify(pattern='^.purge$', admin=True) +def purge(message): msg = message.reply_to_message if msg: itermsg = list(range(msg.id, message.id)) @@ -35,7 +35,7 @@ def purge(client, message): for i in itermsg: try: count = count + 1 - client.delete_messages(chat_id=message.chat.id, message_ids=i, revoke=True) + message._client.delete_messages(chat_id=message.chat.id, message_ids=i, revoke=True) except FloodWait as e: sleep(e.x) except Exception as e: @@ -49,15 +49,15 @@ def purge(client, message): done.delete() -@sedenify(pattern='^.purgeme', compat=False) -def purgeme(client, message): - me = TEMP_SETTINGS['ME'] +@sedenify(pattern='^.purgeme') +def purgeme(message): + me = message._client.me count = extract_args(message) if not count.isdigit(): return edit(message, f'`{get_translation("purgemeUsage")}`') i = 1 - itermsg = client.get_chat_history(message.chat.id) + itermsg = message._client.get_chat_history(message.chat.id) for message in itermsg: if i > int(count) + 1: break @@ -72,13 +72,13 @@ def purgeme(client, message): smsg.delete() -@sedenify(pattern='^.del$', compat=False, admin=True) -def delete(client, message): +@sedenify(pattern='^.del$', admin=True) +def delete(message): msg_src = message.reply_to_message if msg_src: if msg_src.from_user.id: try: - client.delete_messages(message.chat.id, msg_src.id) + message._client.delete_messages(message.chat.id, msg_src.id) message.delete() send_log(f'`{get_translation("delResultLog")}`') except BaseException: diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index b86e7af..058ade2 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/quotly.py b/sedenbot/modules/quotly.py index af393a3..51c5091 100644 --- a/sedenbot/modules/quotly.py +++ b/sedenbot/modules/quotly.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -14,8 +14,8 @@ from sedenecem.core import PyroConversation, edit, get_translation, sedenify -@sedenify(pattern='^.q$', compat=False) -def quotly(client, message): +@sedenify(pattern='^.q$') +def quotly(message): reply = message.reply_to_message if reply and (reply.text or reply.photo or reply.sticker): edit(message, f'`{get_translation("makeQuote")}`') @@ -26,7 +26,7 @@ def quotly(client, message): sleep(1) chat = 'QuotLyBot' - with PyroConversation(client, chat) as conv: + with PyroConversation(message, chat) as conv: response = None try: conv.forward_msg(reply) diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 9547719..20a48ff 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index 537a63c..b5662f2 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -23,11 +23,11 @@ get_translation, reply_doc, sedenify, + useragent, ) opener = request.build_opener() -useragent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4464.5 Safari/537.36' -opener.addheaders = [('User-agent', useragent)] +opener.addheaders = [('User-agent', useragent())] @sedenify(pattern='^.reverse$') @@ -65,7 +65,7 @@ def reverse(message): edit(message, f'`{get_translation("reverseGoogle")}`') return - remove(photo) + #remove(photo) match = ParseSauce(fetchUrl + '&preferences?hl=en&fg=1#languages') guess = match['best_guess'] imgspage = match['similar_images'] diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index 5da2af6..e018563 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/sangmata.py b/sedenbot/modules/sangmata.py deleted file mode 100644 index 438345e..0000000 --- a/sedenbot/modules/sangmata.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from pyrogram.errors import YouBlockedUser -from sedenbot import HELP -from sedenecem.core import PyroConversation, edit, get_translation, sedenify - - -@sedenify(pattern='^.sangmata$', compat=False) -def sangmata(client, message): - reply = message.reply_to_message - if reply and reply.text: - edit(message, f'`{get_translation("processing")}`') - else: - edit(message, f'`{get_translation("replyMessage")}`') - return - - chat = 'SangMataInfo_bot' - - with PyroConversation(client, chat) as conv: - response = None - try: - conv.forward_msg(reply) - response = conv.recv_msg() - except YouBlockedUser: - edit(message, get_translation('unblockChat', ['**', '`', chat])) - return - except Exception as e: - raise e - - if not response: - edit(message, f'`{get_translation("answerFromBot")}`') - elif 'Forward' in response.text: - edit(message, f'`{get_translation("privacySettings")}`') - else: - edit(message, response.text) - - -HELP.update({'sangmata': get_translation('sangmataInfo')}) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 4059820..befc932 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -7,13 +7,13 @@ # All rights reserved. See COPYING, AUTHORS. # +from json import JSONDecodeError, loads from mimetypes import guess_type from os import path, remove -from random import choice +from random import choice, randrange from re import findall, sub from time import sleep from traceback import format_exc -from urllib.error import HTTPError from urllib.parse import quote_plus from bs4 import BeautifulSoup @@ -23,11 +23,14 @@ from gtts.lang import tts_langs from pyrogram import enums from pyrogram.types import InputMediaPhoto -from requests import get, post +from requests import RequestException, get, post +from selenium.webdriver.common.by import By + from sedenbot import HELP, SEDEN_LANG from sedenecem.core import ( edit, extract_args, + extract_args_split, get_translation, get_webdriver, google_domains, @@ -35,10 +38,8 @@ reply_voice, sedenify, send_log, + useragent, ) -from urbandic import search -from wikipedia import set_lang, summary -from wikipedia.exceptions import DisambiguationError, PageError CARBONLANG = 'auto' TTS_LANG = SEDEN_LANG @@ -78,8 +79,8 @@ def carbon(message): 'POST', '/session/$sessionId/chromium/send_command', ) - driver.find_element_by_xpath("//button[contains(text(),'Export')]").click() - edit(message, f'`{get_translation("processing")}\n%`75') + driver.find_element(By.XPATH, "//button[contains(text(),'Export')]").click() + edit(message, f'`{get_translation("processing")}\n%75`') while not path.isfile('./carbon.png'): sleep(0.5) edit(message, f'`{get_translation("processing")}\n%100`') @@ -119,15 +120,16 @@ def img(message): driver.get(url) count = 1 files = [] - for i in driver.find_elements_by_xpath( - '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]' + for i in driver.find_elements( + By.XPATH, '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]' ): i.click() try_count = 0 while ( len( - element := driver.find_elements_by_xpath( - '//img[contains(@class,"n3VNCb") and contains(@src,"http")]' + element := driver.find_elements( + By.XPATH, + '//img[contains(@class,"n3VNCb") and contains(@src,"http")]', ) ) < 1 @@ -150,7 +152,9 @@ def img(message): continue files.append(InputMediaPhoto(filename)) sleep(1) - driver.find_elements_by_xpath('//a[contains(@class,"hm60ue")]')[0].click() + elements = driver.find_elements(By.XPATH, '//a[contains(@class,"hm60ue")]') + for element in elements: + element.click() count += 1 if lim < count: break @@ -161,7 +165,7 @@ def img(message): reply_doc(message, files, delete_orig=True, delete_after_send=True) -@sedenify(pattern=r'^.google') +@sedenify(pattern='^.google') def google(message): match = extract_args(message) if len(match) < 1: @@ -223,7 +227,7 @@ def get_result(res): req = get( f'https://{choice(google_domains)}{temp}', headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', + 'User-Agent': useragent(), 'Content-Type': 'text/html', }, ) @@ -234,7 +238,7 @@ def get_result(res): req = get( f'https://{choice(google_domains)}{temp}', headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0', + 'User-Agent': useragent(), 'Content-Type': 'text/html', }, ) @@ -268,9 +272,7 @@ def ddgo(message): req = get( f'https://duckduckgo.com/lite?q={query}', headers={ - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)' - 'AppleWebKit/537.36 (KHTML, like Gecko)' - 'Chrome/81.0.4044.138 Safari/537.36', + 'User-Agent': useragent(), 'Content-Type': 'text/html', }, ) @@ -327,32 +329,22 @@ def urbandictionary(message): edit(message, f'`{get_translation("wrongCommand")}`') return edit(message, f'`{get_translation("processing")}`') - mean = [] - example = [] - try: - for i in search(query, 1): - mean.append(i.definition + "\n") - example.append(i.example + "\n") - except TypeError: - return edit(message, f'`{get_translation("udNotFound")}`') - except HTTPError: - edit(message, get_translation('udResult', ['**', query])) - return - deflen = sum(len(i) for i in mean) - exalen = sum(len(i) for i in example) - meanlen = deflen + exalen - if int(meanlen) >= 0: - if int(meanlen) >= 4096: + response = get(f'https://api.urbandictionary.com/v0/define?term={query}') + data = loads(response.text) + if len(data["list"]): + item = data['list'][randrange(9)] + meanlen = item['definition'] + item['example'] + if len(meanlen) >= 4096: edit(message, f'`{get_translation("outputTooLarge")}`') file = open('urbandictionary.txt', 'w+') file.write( 'Query: ' + query + '\n\nMeaning: ' - + "".join(mean) + + item['definition'] + '\n\n' + 'Örnek: \n' - + "".join(example) + + item['example'] ) file.close() reply_doc( @@ -368,7 +360,7 @@ def urbandictionary(message): message, get_translation( 'sedenQueryUd', - ['**', '`', query, "".join(choice(mean)), "".join(choice(example))], + ['**', '`', query, item['definition'], item['example']], ), ) else: @@ -381,31 +373,58 @@ def wiki(message): if len(args) < 1: edit(message, f'`{get_translation("wrongCommand")}`') return - set_lang(SEDEN_LANG) + try: - summary(args) - except DisambiguationError as error: - edit(message, get_translation('wikiError', [error])) - return - except PageError as pageerror: - edit(message, get_translation('wikiError2', [pageerror])) - return - result = summary(args) - if len(result) >= 4096: - file = open('wiki.txt', 'w+') - file.write(result) - file.close() - reply_doc( + result = search_wiki(args) + except BaseException as e: + raise e + + if len(result) > 4096: + with open(f'{args}.txt', 'w', encoding='utf-8') as file: + file.write(result) + return reply_doc( message, - 'wiki.txt', + f'{args}.txt', caption=f'`{get_translation("outputTooLarge")}`', delete_after_send=True, ) - edit(message, get_translation('sedenQuery', ['**', '`', args, result])) + edit(message, get_translation('sedenQuery', ['**', '`', args, result])) send_log(get_translation('wikiLog', ['`', args])) +def search_wiki(query): + url = f'https://{SEDEN_LANG or "en"}.wikipedia.org/w/api.php' + params = { + 'action': 'query', + 'format': 'json', + 'prop': 'extracts', + 'titles': query, + 'exsectionformat': 'wiki', + 'explaintext': 1, + } + + try: + response = get(url, params=params) + response.raise_for_status() + data = loads(response.text) + pages = data.get('query', {}).get('pages', {}) + result = '' + + for page in pages.values(): + extract = page.get('extract', '') + result += extract + + if not result: + result = get_translation('wikiError') + + return result + + except (RequestException, JSONDecodeError) as e: + print(f'API Error: {e}') + return '' + + @sedenify(pattern='^.tts') def text_to_speech(message): reply = message.reply_to_message @@ -483,7 +502,7 @@ def translate(message): @sedenify(pattern='^.lang') def lang(message): - arr = extract_args(message).split(' ', 1) + arr = extract_args_split(message) if len(arr) != 2: edit(message, f'`{get_translation("wrongCommand")}`') @@ -518,9 +537,7 @@ def lang(message): def doviz(message): req = get( 'https://www.doviz.com/', - headers={ - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.9999.0 Safari/537.36' - }, + headers={'User-Agent': useragent()}, ) page = BeautifulSoup(req.content, 'html.parser') res = page.find_all('div', {'class', 'item'}) @@ -533,12 +550,10 @@ def doviz(message): edit(message, out) -@sedenify(pattern='^.imei(checker)?') +@sedenify(pattern='^.imei(|check)') def imeichecker(message): - argv = extract_args(message) - imei = argv.split(' ', 1)[0] + imei = extract_args(message) edit(message, f'`{get_translation("processing")}`') - print(len(imei)) if len(imei) != 15: edit(message, f'`{get_translation("wrongCommand")}`') return @@ -553,8 +568,12 @@ def imeichecker(message): break _marka = findall(r'Marka:(.+) Model', result['markaModel']) _model = findall(r'Model Bilgileri:(.+)', result['markaModel']) - marka = _marka[0].replace(',','').strip() if _marka else "Marka Bilgisi Bulunamadi" - model = _model[0].replace(',','').strip() if _model else "Model Bilgisi Bulunamadi" + marka = ( + _marka[0].replace(',', '').strip() if _marka else "Marka Bilgisi Bulunamadi" + ) + model = ( + _model[0].replace(',', '').strip() if _model else "Model Bilgisi Bulunamadi" + ) reply_text = ( f"<b>Sorgu Tarihi</b> ⇾ <code>{result['sorguTarihi']}</code>\n" f"<b>IMEI</b> ⇾ <code>{result['imei'][:-5]+5*'*'}</code>\n" @@ -568,8 +587,35 @@ def imeichecker(message): raise e +@sedenify(pattern='^.currency') +def currency_convert(message): + input_str = extract_args(message) + input_sgra = input_str.split(' ') + if len(input_sgra) == 3: + try: + number = float(input_sgra[0]) + currency_from = input_sgra[1].upper() + currency_to = input_sgra[2].upper() + request_url = f'https://www.x-rates.com/calculator/?from={currency_from}&to={currency_to}&amount={number}' + current_response = get(request_url, headers={'User-Agent': useragent()}) + if current_response.status_code == 200: + soup = BeautifulSoup(current_response.text, 'html.parser') + rebmun = soup.find('span', {'class': 'ccOutputRslt'}) + result = rebmun.find('span') + result.extract() + edit(message, f'**{number} {currency_from} = {rebmun.text.strip()}**') + else: + edit(message, f'`{get_translation("currencyError")}`') + except Exception as e: + edit(message, str(e)) + else: + edit(message, f'`{get_translation("syntaxError")}`') + return + + HELP.update({'img': get_translation('imgInfo')}) -HELP.update({'imei': get_translation('imeiInfo')}) +HELP.update({'currency': get_translation('currencyInfo')}) +HELP.update({'imeicheck': get_translation('imeiInfo')}) HELP.update({'carbon': get_translation('carbonInfo')}) HELP.update({'goolag': get_translation('googleInfo')}) HELP.update({'duckduckgo': get_translation('ddgoInfo')}) diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index b242488..8cf0148 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/sed.py index c3c49fd..1cba5f1 100644 --- a/sedenbot/modules/sed.py +++ b/sedenbot/modules/sed.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index 27adf96..aa080fe 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 8599319..3d9fec5 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index d9fbea4..cfab721 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -13,7 +13,7 @@ from sedenecem.core import ( edit, extract_args, - extract_args_arr, + extract_args_split, get_translation, increment_spam_count, reply, @@ -73,7 +73,7 @@ def spam(message): @sedenify(pattern='^.picspam') def picspam(message): - arr = extract_args_arr(message) + arr = extract_args_split(message) if len(arr) < 2 or not arr[0].isdigit(): edit(message, f'`{get_translation("spamWrong")}`') return diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index 0bcdf23..8e1080b 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -19,13 +19,12 @@ class SWClient: @sedenify( - compat=False, outgoing=False, incoming=True, disable_edited=True, disable_notify=True, ) -def spamwatch_action(client, message): +def spamwatch_action(message): if not SWClient.spamwatch_client: message.continue_propagation() @@ -42,7 +41,7 @@ def spamwatch_action(client, message): if message.chat.type == enums.ChatType.PRIVATE: reply(message, text) - client.block_user(uid) + message._client.block_user(uid) else: myself = message.chat.get_member('me') if myself.privileges and myself.privileges.can_restrict_members: diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index 6b7c49e..2074d24 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index 7764727..f0154c1 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -19,7 +19,7 @@ from sedenbot import HELP from sedenecem.core import ( edit, - extract_args, + extract_args_split, get_translation, reply_audio, reply_doc, @@ -200,7 +200,7 @@ def show_users_detail( @sedenify(pattern='^.spoti(|fy)') def spotify_download(message): spotify = Spotipy() - args = extract_args(message).split() + args = extract_args_split(message) if len(args) == 2 and args[0] == 'dl' and not args[1] == 'zip': url = args[1] diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index 8ed01fa..e23cefd 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -33,9 +33,9 @@ # ================= CONSTANT ================= -@sedenify(pattern='^.(d[ıi]zla|kang)', compat=False) -def kang(client, message): - myacc = TEMP_SETTINGS['ME'] +@sedenify(pattern='^.(d[ıi]zla|kang)') +def kang(message): + myacc = message._client.me kanger = myacc.username or myacc.first_name if myacc.username: kanger = f'@{kanger}' @@ -119,7 +119,7 @@ def pack_created(pname): try: set_name = InputStickerSetShortName(short_name=TEMP_SETTINGS[pname]) set = GetStickerSet(stickerset=set_name, hash=0) - client.invoke(query=set) + message._client.invoke(query=set) return True except BaseException: return False @@ -192,7 +192,7 @@ def add_exist(conv, pack, pname, pnick): send_recv(conv, '/done') return True - with PyroConversation(client, chat) as conv: + with PyroConversation(message, chat) as conv: try: send_recv(conv, '/cancel') except YouBlockedUser: @@ -250,8 +250,8 @@ def getsticker(message): message.delete() -@sedenify(pattern='.packinfo$', compat=False) -def packinfo(client, message): +@sedenify(pattern='.packinfo$') +def packinfo(message): reply = message.reply_to_message if not reply: edit(message, f'`{get_translation("packinfoError")}`') @@ -263,7 +263,7 @@ def packinfo(client, message): edit(message, f'`{get_translation("processing")}`') - get_stickerset = client.invoke( + get_stickerset = message._client.invoke( GetStickerSet( stickerset=InputStickerSetShortName(short_name=reply.sticker.set_name), hash=0, diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 4a8a2d5..c4a6d8c 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -33,17 +33,18 @@ @sedenify(pattern='^.neofetch$') def neofetch(message): - try: + if which('neofetch'): from subprocess import PIPE, Popen process = Popen( ['neofetch', f'HOSTNAME={HOSTNAME}', f'USER={USER}', '--stdout'], stdout=PIPE, stderr=PIPE, + shell=True, ) result, _ = process.communicate() edit(message, f'`{result.decode()}`') - except BaseException: + else: edit(message, f'`{get_translation("neofetchNotFound")}`') @@ -99,9 +100,9 @@ def test_echo(message): edit(message, f'`{get_translation("echoHelp")}`') -@sedenify(pattern='^.dc$', compat=False) -def data_center(client, message): - result = client.invoke(GetNearestDc()) +@sedenify(pattern='^.dc$') +def data_center(message): + result = message._client.invoke(GetNearestDc()) edit( message, diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index a58593e..0c15823 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index f2bc943..42aa4c8 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index 9ef07ad..d513f00 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -13,15 +13,15 @@ # ===== CONSTANT ===== if WEATHER: - DEFCITY = WEATHER + DEFCITY = WEATHER.capitalize() else: DEFCITY = None # ==================== -@sedenify(pattern='^.(havadurumu|w(eathe|tt)r)') -def havadurumu(message): - args = extract_args(message) +@sedenify(pattern='^.(hava(|durumu)|w(eathe|tt)r)') +def weather(message): + args = extract_args(message).capitalize() if len(args) < 1: CITY = DEFCITY @@ -36,14 +36,16 @@ def havadurumu(message): try: req = get( - f'http://wttr.in/{CITY}?mqT0', + f'http://wttr.in/{CITY}?mQT0', headers={'User-Agent': 'curl/7.66.0', 'Accept-Language': SEDEN_LANG}, ) data = req.text if '===' in data: raise Exception + if '404' in data: + return edit(message, f'`{get_translation("weatherErrorServer")}`') data = data.replace('`', '‛') - edit(message, f'`{data}`') + edit(message, f'**{CITY}**\n\n`{data}`') except Exception: edit(message, f'`{get_translation("weatherErrorServer")}`') diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 3ceb8e1..371e855 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # Copyright (C) 2021 kisekinopureya <https://github.com/kisekinopureya> # # This file is part of TeamDerUntergang project, @@ -16,7 +16,7 @@ from sedenbot import HELP from sedenecem.core import ( edit, - extract_args_arr, + extract_args_split, get_download_dir, get_translation, reply_audio, @@ -28,7 +28,7 @@ @sedenify(pattern='^.y(outube|tdl)') def youtubedl(message): - args = extract_args_arr(message) + args = extract_args_split(message) if len(args) != 2: edit(message, f'`{get_translation("wrongCommand")}`') diff --git a/sedenecem/core/__init__.py b/sedenecem/core/__init__.py index 263a682..1aeeff4 100644 --- a/sedenecem/core/__init__.py +++ b/sedenecem/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -8,6 +8,7 @@ # from .conv import * +from .filters import * from .image import * from .misc import * from .proxy import * diff --git a/sedenecem/core/conv.py b/sedenecem/core/conv.py index d68b7a6..e49eb87 100644 --- a/sedenecem/core/conv.py +++ b/sedenecem/core/conv.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -14,8 +14,40 @@ class PyroConversation: - def __init__(self, client, chat_id): - self.client = client or app + """ + A context manager for a conversation with a specific chat ID. + + Args: + message (pyrogram.types.Message): The original message that initiated the conversation. + chat_id (int): The ID of the chat with which the conversation is taking place. + + Methods: + send_msg(text: str) -> None: + Sends a message to the chat. + + send_doc(doc: str, delete: bool = False) -> None: + Sends a document to the chat. + + recv_msg(read: bool = True) -> pyrogram.types.Message: + Receives a message from the chat. + + forward_msg(msg: pyrogram.types.Message) -> pyrogram.types.Message: + Forwards a message to the chat. + + init() -> None: + Initializes the conversation. + + stop() -> None: + Ends the conversation. + + Usage: + with PyroConversation(message, chat_id) as conv: + conv.send_msg("Hello!") + reply = conv.recv_msg() + """ + + def __init__(self, message, chat_id): + self.client = message._client or app self.chat_id = chat_id self.count = 0 diff --git a/sedenecem/core/filters.py b/sedenecem/core/filters.py new file mode 100644 index 0000000..98aeb83 --- /dev/null +++ b/sedenecem/core/filters.py @@ -0,0 +1,176 @@ +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from asyncio import run +from re import search +from traceback import format_exc +from typing import Callable, List, Union + +from pyrogram import ContinuePropagation, StopPropagation +from pyrogram.handlers import RawUpdateHandler +from pyrogram.raw.types import ( + MessageActionContactSignUp, + MessageService, + UpdateNewMessage, +) +from pyrogram.types import Message, User + +from sedenbot import BLACKLIST, LOGS + + +class BaseFilter: + def __init__(self, invert: bool = False): + self.invert = invert + + def __verify__(self, _: Message): + raise NotImplementedError + + def verify(self, message: Message): + if ( + not isinstance(message, Message) + or not message + or message.empty + or not message.from_user + ): + return False + + try: + ret = self.__verify__(message) + ret = not ret if self.invert else ret + except (ContinuePropagation, StopPropagation, RetardsException) as e: + raise e + except BaseException: + ret = False + LOGS.error(format_exc()) + + if not ret: + return message.continue_propagation() + + return True + + +class AndFilter(BaseFilter): + def __init__(self, invert: bool = False, *filters: BaseFilter): + super().__init__(invert) + self.filters: List[BaseFilter, OrFilter] = [] + self.add_filter(*filters) + + def add_filter(self, *filters: BaseFilter): + self.filters.extend(filters) + + def __verify__(self, message: Message): + for item in self.filters: + if not item.verify(message): + return False + return True + + +class OrFilter(AndFilter): + def verify(self, message: Message): + for item in self.filters: + if item.verify(message): + return True + return False + + +class RegexFilter(BaseFilter): + def __init__(self, regex: str, invert: bool = False): + super().__init__(invert) + self.regex = regex + + def __verify__(self, message: Message): + return search(self.regex, message.text) if message.text else False + + +class IncomingFilter(BaseFilter): + def __init__(self): + super().__init__(invert=True) + + def __verify__(self, message: Message): + return message.outgoing + + +class UserFilter(BaseFilter): + def __init__( + self, users: Union[int, List[int], User, List[User]], invert: bool = False + ): + super().__init__(invert) + self.users = users + + def extract_uid(self, user): + return user.id if type(user) is User else user + + def __verify__(self, message: Message): + uid = message.from_user.id if message.from_user else 0 + + if type(self.users) is List: + self.users = [self.extract_uid(x) for x in self.users] + else: + self.users = [self.extract_uid(self.users)] + + for user in self.users: + if uid != user: + return False + return True + + +class BotFilter(BaseFilter): + def __init__(self, invert: bool = False): + super().__init__(invert) + + def __verify__(self, message: Message): + return message.from_user.is_bot if message.from_user else False + + +class MeFilter(BaseFilter): + def __init__(self, invert: bool = False): + super().__init__(invert) + + def __verify__(self, message: Message): + return ( + message.from_user.is_self or message.chat.id == message._client.me.id + if message.from_user and message.chat + else False + ) + + +class SedenUpdateHandler(RawUpdateHandler): + def __init__(self, callback: Callable, filter: AndFilter, handlers: List): + super().__init__(self.__callback__) + self.filter = filter + self.handlers = handlers + self.seden_callback = callback + + def __callback__(self, client, update, users, chats): + if client.me.id in BLACKLIST: + raise RetardsException('RETARDS CANNOT USE THIS BOT') + + if ( + isinstance(update, UpdateNewMessage) + and isinstance(update.message, MessageService) + and isinstance(update.message.action, MessageActionContactSignUp) + ): + LOGS.warning('User created an account') + raise StopPropagation + else: + parser = client.dispatcher.update_parsers.get(type(update), None) + parsed_update, handler_type = ( + run(parser(update, users, chats)) + if parser is not None + else (None, type(None)) + ) + + verified = [i.verify(parsed_update) for i in self.filter.filters] + if handler_type in self.handlers and all(verified): + self.seden_callback(parsed_update) + raise ContinuePropagation + + +class RetardsException(Exception): + pass diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index c8bfe39..0a62c28 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -16,6 +16,16 @@ def sticker_resize(photo): + """ + Resizes a given sticker image file to have a maximum dimension of 512 pixels while maintaining aspect ratio. + If the image is already smaller than 512x512 pixels, it will be resized to its original size. + + Args: + photo (str): The file path to the sticker image file to be resized. + + Returns: + str: The file path to the resized image file in PNG format, stored in a temporary directory. + """ image = Image.open(photo) if (image.width and image.height) < 512: size1 = image.width @@ -42,6 +52,15 @@ def sticker_resize(photo): def video_convert(video): + """ + Converts a video file to a webm format with dimensions of 512x512 and duration of 3.0 seconds. + + Args: + video (str): Path of the video file to be converted. + + Returns: + str: Path of the converted webm file. + """ process = Popen( [ 'ffmpeg', diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index b4b4352..4e960d1 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -12,9 +12,12 @@ from subprocess import STDOUT, CalledProcessError, check_output from typing import List +from bs4 import BeautifulSoup from pyrogram import enums from pyrogram.types import Message, User -from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, TEMP_SETTINGS, app +from requests import get + +from sedenbot import BOT_PREFIX, BRAIN, LOG_VERBOSE, app MARKDOWN_FIX_CHAR = '\u2064' SPAM_COUNT = [0] @@ -224,6 +227,20 @@ def reply( delete_orig=False, parse=enums.ParseMode.MARKDOWN, ): + """ + Reply to a message with the given text. + + Args: + message (pyrogram.types.Message): The message to reply to. + text (str): The text to send in the reply. + preview (bool): Whether to enable link previews for URLs in the text. Defaults to True. + fix_markdown (bool): Whether to add a special character to fix issues with markdown formatting. Defaults to False. + delete_orig (bool): Whether to delete the original message after sending the reply. Defaults to False. + parse (pyrogram.enums.ParseMode): The parse mode to use for the text. Defaults to `pyrogram.enums.ParseMode.MARKDOWN`. + + Returns: + pyrogram.types.Message: The message object of the sent reply. + """ try: if fix_markdown: text += MARKDOWN_FIX_CHAR @@ -237,7 +254,18 @@ def reply( pass -def extract_args(message, markdown=True): +def extract_args(message, markdown=True, line=True): + """ + Extracts arguments from a given text. + + Args: + message (pyrogram.types.Message): The message to extract arguments from. + markdown (bool): Whether to treat the message text as Markdown. Defaults to True. + line (bool): Whether to remove line breaks from the message text. Defaults to True. + + Returns: + str: The extracted arguments. + """ if not (message.text or message.caption): return '' @@ -247,22 +275,46 @@ def extract_args(message, markdown=True): if ' ' not in text: return '' - text = sub(r'\s+', ' ', text) + text = sub(r'\s+', ' ', text) if line else text text = text[text.find(' ') :].strip() return text -def extract_args_arr(message, markdown=True): - return extract_args(message, markdown).split() +def extract_args_split(message, markdown=True, line=True): + """ + Extracts arguments from a given text and splits them into a list. + + Args: + message (pyrogram.types.Message): The message to extract arguments from. + markdown (bool): Whether to treat the message text as Markdown. Defaults to True. + line (bool): Whether to remove line breaks from the message text. Defaults to True. + + Returns: + List[str]: The extracted arguments, split into a list. + """ + return extract_args(message, markdown, line).split() def edit( message, text, preview=True, fix_markdown=False, parse=enums.ParseMode.MARKDOWN ): + """ + Edits the text of a message. + + Args: + message (pyrogram.types.Message): The message to edit. + text (str): The new text to replace the message with. + preview (bool): Whether to enable link previews for URLs in the text. Defaults to True. + fix_markdown (bool): Whether to add a special character to fix issues with markdown formatting. Defaults to False. + parse (pyrogram.enums.ParseMode): The parse mode to use for the text. Defaults to `pyrogram.enums.ParseMode.MARKDOWN`. + + Returns: + None + """ try: if fix_markdown: text += MARKDOWN_FIX_CHAR - if message.from_user.id != TEMP_SETTINGS['ME'].id: + if message.from_user.id != message._client.me.id: reply(message, text, preview=preview, parse=parse) return message.edit_text( @@ -273,6 +325,19 @@ def edit( def download_media(client, data, file_name=None, progress=None, sticker_orig=True): + """ + Downloads media from a given message and saves it to a file. + + Args: + client (pyrogram.Client): The client to use for downloading the media. + data (pyrogram.types.Message): The message containing the media to download. + file_name (str): The name of the file to save the media to. Defaults to None. + progress (callable, optional): A callback function to report the progress of the download. Defaults to None. + sticker_orig (bool): Whether to download the original sticker file. Defaults to True. + + Returns: + Union[None, str]: None if media download fails, else the absolute path of downloaded file. + """ if not file_name: if data.document: file_name = ( @@ -312,14 +377,37 @@ def download_media(client, data, file_name=None, progress=None, sticker_orig=Tru def download_media_wc(data, file_name=None, progress=None, sticker_orig=False): + """ + Downloads media from a given message and saves it to a file. + + Args: + data (pyrogram.types.Message): The message containing the media to download. + file_name (str): The name to save the downloaded file as. If not specified, the file will be saved with a default name based on the media type. + progress (callable, optional): A function to call with the download progress percentage as an argument. Useful for displaying a progress bar. The function should take a single `float` argument between 0 and 100. + sticker_orig (bool): If `True`, downloads the original TGS or JSON file for stickers, instead of converting to a static image format. Has no effect for non-sticker media. + + Returns: + None: If the media type is not supported or the download fails for any reason. + str: The name of the file the media was saved as, if the download was successful. + """ return download_media(app, data, file_name, progress, sticker_orig) -def get_me(): - return app.get_me() +def forward(message, chat_id): + """ + Forwards a given message to a specified chat. + + Args: + message (pyrogram.types.Message): The message to forward. + chat_id (int or str): The ID of the chat to forward the message to. + If not specified, forwards the message to the saved messages. + Raises: + Exception: If the message forwarding fails. -def forward(message, chat_id): + Returns: + pyrogram.types.Message: The forwarded message object. + """ try: return message.forward(chat_id or 'me') except Exception as e: @@ -327,6 +415,17 @@ def forward(message, chat_id): def get_messages(chat_id, msg_ids=None, client=app): + """ + Retrieves one or more messages from a specified chat. + + Args: + chat_id (int or str): The ID of the chat to retrieve messages from. If not specified, retrieves messages from the saved messages. + msg_ids (int or List[int]): The ID or list of IDs of the messages to retrieve. If not specified, retrieves the latest message in the chat. + client (pyrogram.Client): The client instance used to retrieve the messages. + + Returns: + List[pyrogram.types.Message]: A list of message objects that were retrieved, sorted in ascending order based on their IDs (oldest message first). If no messages are found or an error occurs, an empty list is returned. + """ try: ret = client.get_messages(chat_id=(chat_id or 'me'), message_ids=msg_ids) return [ret] if ret and isinstance(ret, Message) else ret @@ -335,19 +434,46 @@ def get_messages(chat_id, msg_ids=None, client=app): def amisudo(): - return TEMP_SETTINGS['ME'].id in BRAIN + """ + Checks if the user is authorized to execute sudo commands. + + Returns: + bool: True if the user is authorized to execute sudo commands, False otherwise. + """ + return app.me.id in BRAIN def increment_spam_count(): + """ + Increments the spam count and checks if the current spam count is within the allowed limit. + + Returns: + bool: True if the current spam count is within the allowed limit, False otherwise. + """ SPAM_COUNT[0] += 1 return spam_allowed() def spam_allowed(): + """ + Checks if spamming is allowed based on the current spam count or user permissions. + + Returns: + bool: True if spamming is allowed, False otherwise. + """ return amisudo() or SPAM_COUNT[0] < 50 def get_cmd(message): + """ + Extracts the command from a given message. + + Args: + message (pyrogram.types.Message): The message from which to extract the command. + + Returns: + str: The command string without the leading slash, or an empty string if no command was found. + """ text = message.text or message.caption if text: text = text.strip() @@ -356,6 +482,15 @@ def get_cmd(message): def parse_cmd(text): + """ + Parses a command string from the given text. + + Args: + text (str): The text from which to parse the command. + + Returns: + str: The parsed command string without the leading slash. + """ cmd = sub(r'\s+', ' ', text) cmd = cmd.split()[0] cmd = cmd.split(_parsed_prefix)[-1] if BOT_PREFIX else cmd[1:] @@ -363,6 +498,15 @@ def parse_cmd(text): def is_admin(message): + """ + Checks if the sender of the given message is an admin in the chat. + + Args: + message (pyrogram.types.Message): The message to check. + + Returns: + bool: True if the sender is an admin, False otherwise. + """ if not message.chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: return True @@ -371,6 +515,15 @@ def is_admin(message): def is_admin_myself(chat): + """ + Checks if the user is an admin in the given chat. + + Args: + chat (pyrogram.types.Chat): The chat to check. + + Returns: + bool: True if the bot is an admin, False otherwise. + """ if not chat.type in [enums.ChatType.SUPERGROUP, enums.ChatType.GROUP]: return True @@ -379,12 +532,27 @@ def is_admin_myself(chat): def get_download_dir() -> str: + """ + Gets the directory path for downloaded files. + + Returns: + str: The directory path. + """ dir = './downloads' makedirs(dir, exist_ok=True) return dir def get_duration(media): + """ + Returns the duration of a media file. + + Args: + media (str): The file path. + + Returns: + int: The duration in seconds or None if the duration cannot be determined. + """ out = __status_out__( f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "{media}"' ) @@ -396,6 +564,16 @@ def get_duration(media): def __status_out__(cmd, encoding='utf-8'): + """ + Runs a shell command and captures its output. + + Args: + cmd (str): The shell command to be run. + encoding (str): The encoding to use for the command's output. Defaults to 'utf-8'. + + Returns: + tuple: A tuple containing the return code of the command and its output. + """ try: output = check_output( cmd, shell=True, text=True, stderr=STDOUT, encoding=encoding @@ -410,6 +588,15 @@ def __status_out__(cmd, encoding='utf-8'): def extract_user(message: Message) -> List[User]: + """ + Extracts user information from a given message. + + Args: + message (pyrogram.types.Message): Message object. + + Returns: + List[User]: A list of User objects representing the users mentioned or replied to in the message. + """ users: List[User] = [] mentions = None @@ -444,3 +631,19 @@ def extract_user(message: Message) -> List[User]: pass return users + + +def useragent(): + """ + Generate a random user agent string. + + Returns: + str: A random user agent string. + """ + req = get('https://useragents.io/random') + soup = BeautifulSoup(req.text, 'html.parser') + agent = soup.find_all('td') + for i in agent: + return i.find('a').text + + return 'Googlebot/2.1 (+http://www.google.com/bot.html)' diff --git a/sedenecem/core/proxy.py b/sedenecem/core/proxy.py index 6f8229c..5255cbb 100644 --- a/sedenecem/core/proxy.py +++ b/sedenecem/core/proxy.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -9,81 +9,112 @@ from bs4 import BeautifulSoup from requests import get + from sedenbot import TEMP_SETTINGS, get_translation -from .misc import edit +from .misc import edit, useragent -def use_proxy(message) -> None: - edit(message, f'`{get_translation("fetchProxy")}`') - proxy = get_random_proxy() - edit(message, f'`{get_translation("providedProxy")}`') - return proxy +class ProxyHandler: + """ + A class for handling the retrieval and use of HTTP proxies. + Attributes: + proxy (Union[None, dict[str, str]]): The current proxy to be used for HTTP requests. -def get_stored_proxy(): - return TEMP_SETTINGS.get('VALID_PROXY_URL', '') + Methods: + use_proxy(message: Message) -> Union[None, dict[str, str]]: + Retrieves a valid proxy and returns it as a dictionary containing 'http' and 'https' keys. + get_stored_proxy() -> str: + Retrieves the stored proxy URL from temporary settings. -def put_stored_proxy(proxy): - TEMP_SETTINGS['VALID_PROXY_URL'] = proxy + put_stored_proxy(proxy: str) -> None: + Sets the stored proxy URL in temporary settings. + _xget_random_proxy() -> Union[None, tuple[str, str]]: + Attempts to retrieve a random proxy URL from the SSL Proxies website. -def _xget_random_proxy(): - proxy = get_stored_proxy() - try_valid = tuple(proxy.split(":")) if len(proxy) else None - if try_valid: - valid = _try_proxy(try_valid) - if valid[0] == 200 and "<title>Too" not in valid[1]: - return try_valid + _try_proxy(proxy: tuple[str, str]) -> tuple[int, Union[str, None]]: + Tests the provided proxy to ensure that it can be used for HTTP requests. - head = { - 'Accept-Encoding': 'gzip, deflate, sdch', - 'Accept-Language': 'en-US,en;q=0.8', - 'User-Agent': 'ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)', - 'Referer': 'https://www.google.com/search?q=sslproxies', - } + get_random_proxy() -> Union[None, dict[str, str]]: + Retrieves a random, valid proxy URL and sets it as the current proxy for HTTP requests. - req = get('https://sslproxies.org/', headers=head) - soup = BeautifulSoup(req.text, 'html.parser') - res = soup.find('div', {'class': 'fpl-list'}).find('tbody') - res = res.findAll('tr') - for item in res: - infos = item.findAll('td') - ip = infos[0].text - port = infos[1].text - proxy = (ip, port) - if _try_proxy(proxy)[0] == 200: - return proxy + Usage: + handler = ProxyHandler() + proxy = handler.use_proxy("Fetching proxy...") + req = requests.get("https://www.example.com", proxies=proxy) + """ - return None + def __init__(self): + self.proxy = None + def use_proxy(self, message) -> None: + edit(message, f'`{get_translation("fetchProxy")}`') + proxy = self.get_random_proxy() + edit(message, f'`{get_translation("providedProxy")}`') + return proxy -def _try_proxy(proxy): - try: - prxy = f'http://{proxy[0]}:{proxy[1]}' - req = get( - 'https://www.gsmarena.com/', - proxies={'http': prxy, 'https': prxy}, - timeout=1, - ) - if req.status_code == 200: - return (200, req.text) - raise Exception - except BaseException: - return (404, None) + def get_stored_proxy(self): + return TEMP_SETTINGS.get('VALID_PROXY_URL', '') + def put_stored_proxy(self, proxy): + TEMP_SETTINGS['VALID_PROXY_URL'] = proxy -def get_random_proxy(): - proxy = _xget_random_proxy() - if not proxy: - return None - proxy = f'http://{proxy[0]}:{proxy[1]}' - put_stored_proxy(proxy) + def _xget_random_proxy(self): + proxy = self.get_stored_proxy() + try_valid = tuple(proxy.split(":")) if len(proxy) else None + if try_valid: + valid = self._try_proxy(try_valid) + if valid[0] == 200 and "<title>Too" not in valid[1]: + return try_valid + + head = { + 'Accept-Encoding': 'gzip, deflate, sdch', + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': useragent(), + 'Referer': 'https://www.google.com/search?q=sslproxies', + } - proxy_dict = { - 'https': proxy, - 'http': proxy, - } + req = get('https://sslproxies.org/', headers=head) + soup = BeautifulSoup(req.text, 'html.parser') + res = soup.find('div', {'class': 'fpl-list'}).find('tbody') + res = res.findAll('tr') + for item in res: + infos = item.findAll('td') + ip = infos[0].text + port = infos[1].text + proxy = (ip, port) + if self._try_proxy(proxy)[0] == 200: + return proxy + + return None - return proxy_dict + def _try_proxy(self, proxy): + try: + prxy = f'http://{proxy[0]}:{proxy[1]}' + req = get( + 'https://www.gsmarena.com/', + proxies={'http': prxy, 'https': prxy}, + timeout=1, + ) + if req.status_code == 200: + return (200, req.text) + raise Exception + except BaseException: + return (404, None) + + def get_random_proxy(self): + proxy = self._xget_random_proxy() + if not proxy: + return None + proxy = f'http://{proxy[0]}:{proxy[1]}' + self.put_stored_proxy(proxy) + + proxy_dict = { + 'https': proxy, + 'http': proxy, + } + + return proxy_dict diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 503dfa5..fc4fcfa 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -11,9 +11,10 @@ from pyrogram import enums from pyrogram.types import Message + from sedenbot import LOG_VERBOSE -from .misc import MARKDOWN_FIX_CHAR, __status_out__, get_duration +from .misc import MARKDOWN_FIX_CHAR, __status_out__, get_download_dir, get_duration def reply_img( @@ -25,6 +26,18 @@ def reply_img( delete_file=False, parse=enums.ParseMode.MARKDOWN, ): + """ + Replies to a given message with a photo and caption. + + Args: + message (pyrogram.types.Message): The message to reply to. + photo (str): The path to the photo file to send. + caption (str): The caption for the photo. + fix_markdown (bool): Whether to fix markdown issues in the caption. + delete_orig (bool): Whether to delete the original message after replying. + delete_file (bool): Whether to delete the photo file after sending. + parse (pyrogram.enums.ParseMode): The parse mode to use for the caption. Defaults to `pyrogram.enums.ParseMode.MARKDOWN`. + """ try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -46,6 +59,18 @@ def reply_audio( delete_orig=False, delete_file=False, ): + """ + Reply to the given message with the given audio file and caption. + + Args: + message (pyrogram.types.Message): The message to reply to. + audio (str): The path to the audio file to send. + caption (str): The caption to send with the audio file. + duration (int): The duration of the audio file, in seconds. If not provided, the function will attempt to determine the duration automatically. + fix_markdown (bool): Whether to fix any issues with the Markdown in the caption. Defaults to False. + delete_orig (bool): Whether to delete the original message after sending the reply. Defaults to False. + delete_file (bool): Whether to delete the audio file after sending the reply. Defaults to False. + """ try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -74,9 +99,24 @@ def reply_video( delete_file=False, parse=enums.ParseMode.MARKDOWN, ): + """ + Reply to message with a video file. + + Args: + message (pyrogram.types.Message): The message object to reply to. + video (str): The path to the video file to send. + caption (str): The caption to attach to the video. Defaults to ''. + duration (str): The duration of the video in seconds. If not specified, it will be calculated automatically. + thumb (str): The path to a thumbnail image to attach to the video. If not specified, a thumbnail will be automatically generated from the video. Defaults to None. + fix_markdown (bool): If True, fix any markdown formatting issues in the caption. Defaults to False. + progress (callable, optional): A callback function to report the progress of the upload. Defaults to None. + delete_orig (bool): If True, delete the original message that the reply is being sent to. Defaults to False. + delete_file (bool): If True, delete the video file after sending it. Defaults to False. + parse (pyrogram.enums.ParseMode): The parse mode to use for the caption. Defaults to `pyrogram.enums.ParseMode.MARKDOWN`. + """ try: if not thumb: - thumb = 'downloads/thumb.png' + thumb = f'{get_download_dir()}/thumb.png' if path.exists(thumb): remove(thumb) out = __status_out__( @@ -113,8 +153,7 @@ def reply_video( message.delete() if delete_file: remove(video) - except BaseException as e: - raise e + except BaseException: pass @@ -127,6 +166,18 @@ def reply_voice( delete_orig=False, delete_file=False, ): + """ + Reply to message with a voice message. + + Args: + message (pyrogram.types.Message): The message object to reply to. + voice (str): The path to the voice message file to send. + caption (str): The caption to attach to the voice message. + duration (int): The duration of the voice message in seconds. If not specified, it will be calculated automatically. Defaults to None. + fix_markdown (bool): If True, fix any markdown formatting issues in the caption. Defaults to False. + delete_orig (bool): If True, delete the original message that the reply is being sent to. Defaults to False. + delete_file (bool): If True, delete the voice message file after sending it. Defaults to False. + """ try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -152,6 +203,18 @@ def reply_doc( progress=None, delete_after_send=False, ): + """ + Reply to message with a document or a media group of documents. + + Args: + message (pyrogram.types.Message): The message object to reply to. + doc (Union[str, List[pyrogram.types.Document]]): The document or media group of documents to send. + caption (str): The caption to attach to the document. + fix_markdown (bool): If True, fix any markdown formatting issues in the caption. Defaults to False. + delete_orig (bool): If True, delete the original message that the reply is being sent to. Defaults to False. + progress (callable, optional): A callback function that will be called with the number of bytes uploaded as an argument. Defaults to None. + delete_after_send (bool): If True, delete the document file(s) after sending. Defaults to False. + """ try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR @@ -171,6 +234,15 @@ def reply_doc( def reply_sticker(message, sticker, delete_orig=False, delete_file=False): + """ + Reply to message with a sticker. + + Args: + message (pyrogram.types.Message): The message object to reply to. + sticker (str): The file path of the sticker to send. + delete_orig (bool): If True, delete the original message that the reply is being sent to. Defaults to False. + delete_file (bool): If True, delete the sticker file after sending. Defaults to False. + """ try: message.reply_sticker(sticker) if delete_orig: @@ -182,6 +254,14 @@ def reply_sticker(message, sticker, delete_orig=False, delete_file=False): def reply_msg(message: Message, message2: Message, delete_orig=False): + """ + Reply to message with another message. + + Args: + message (pyrogram.types.Message): The original message object to reply to. + message2 (pyrogram.types.Message): The message object to send as the reply. + delete_orig (bool): If True, delete the original message that the reply is being sent to. Defaults to False. + """ try: message2.copy(chat_id=message.chat.id, reply_to_message_id=message.id) diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 1898295..1534121 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -7,17 +7,29 @@ # All rights reserved. See COPYING, AUTHORS. # -from os import getpid, system +from os import getpid, kill +from signal import SIGTERM from subprocess import PIPE, Popen from sys import exc_info from time import gmtime, strftime from traceback import format_exc -from pyrogram import ContinuePropagation, StopPropagation, enums, filters -from pyrogram.handlers import MessageHandler, EditedMessageHandler -from pyrogram.raw.types import MessageActionContactSignUp -from sedenbot import BLACKLIST, BOT_VERSION, BRAIN, TEMP_SETTINGS, app, get_translation - +from pyrogram import ContinuePropagation, StopPropagation, enums +from pyrogram.handlers import EditedMessageHandler, MessageHandler + +from sedenbot import BOT_VERSION, BRAIN, app, get_translation + +from .filters import ( + AndFilter, + BotFilter, + IncomingFilter, + MeFilter, + OrFilter, + RegexFilter, + RetardsException, + SedenUpdateHandler, + UserFilter, +) from .misc import _parsed_prefix, edit, get_cmd, is_admin from .sedenlog import send_log_doc @@ -28,7 +40,6 @@ def sedenify(**args): incoming = args.get('incoming', False) disable_edited = args.get('disable_edited', False) disable_notify = args.get('disable_notify', False) - compat = args.get('compat', True) brain = args.get('brain', False) private = args.get('private', True) group = args.get('group', True) @@ -43,24 +54,14 @@ def sedenify(**args): args['pattern'] = pattern = f'{pattern}(?: |$)' def msg_decorator(func): - def wrap(client, message): + def wrap(message): if message.empty or not message.from_user: return try: - if not TEMP_SETTINGS.get('ME'): - me = app.get_me() - TEMP_SETTINGS['ME'] = me - - if me.id in BLACKLIST: - raise RetardsException('RETARDS CANNOT USE THIS BOT') - if message.service and not service: return - if message.service and isinstance(message.action, MessageActionContactSignUp): - return - if message.chat.type == enums.ChatType.CHANNEL: return @@ -87,14 +88,10 @@ def wrap(client, message): if not disable_notify: edit(message, f'`{get_translation("adminUsage")}`') message.continue_propagation() - - if not compat: - func(client, message) - else: - func(message) + func(message) except RetardsException: try: - system(f'kill -9 {getpid()}') + kill(getpid(), SIGTERM) except BaseException: pass except (ContinuePropagation, StopPropagation) as c: @@ -146,29 +143,29 @@ def wrap(client, message): except Exception as x: raise x - filter = None + filter = AndFilter() if pattern: - filter = filters.regex(pattern) + filter.add_filter(RegexFilter(pattern)) if brain: - filter &= filters.user(BRAIN) + filter.add_filter(UserFilter(BRAIN)) if outgoing and not incoming: - filter &= filters.me + filter.add_filter(MeFilter()) elif incoming and not outgoing: - filter &= filters.incoming & ~filters.bot & ~filters.me + filter.add_filter(IncomingFilter(), BotFilter(True), MeFilter(True)) else: if outgoing and not incoming: - filter = filters.me + filter.add_filter(MeFilter()) elif incoming and not outgoing: - filter = filters.incoming & ~filters.bot & ~filters.me + filter.add_filter(IncomingFilter(), BotFilter(True), MeFilter(True)) else: - filter = (filters.me | filters.incoming) & ~filters.bot + filter.add_filter( + OrFilter(MeFilter(), IncomingFilter()), BotFilter(True) + ) + handlers = [MessageHandler] if not disable_edited: - app.add_handler(EditedMessageHandler(wrap, filter)) - app.add_handler(MessageHandler(wrap, filter)) - - return msg_decorator + handlers.append(EditedMessageHandler) + app.add_handler(SedenUpdateHandler(wrap, filter, handlers)) -class RetardsException(Exception): - pass + return msg_decorator diff --git a/sedenecem/core/sedenlog.py b/sedenecem/core/sedenlog.py index 1136e06..779733f 100644 --- a/sedenecem/core/sedenlog.py +++ b/sedenecem/core/sedenlog.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -15,10 +15,26 @@ def send_log(text, fix_markdown=False): + """ + Sends a log message to the specified Telegram chat or user(self). + + Args: + text (str): The log message to send. + fix_markdown (bool): Whether to fix the markdown of the log message. Defaults to False. + """ send(app, LOG_ID or 'me', text, fix_markdown=fix_markdown) def send_log_doc(doc, caption='', fix_markdown=False, remove_file=False): + """ + Sends a document file to the specified Telegram chat or user(self), along with an caption. + + Args: + doc (str): The path to the document file to send. + caption (str): The caption to include with the document file. Defaults to ''. + fix_markdown (bool): Whether to fix the markdown of the caption. Defaults to False. + remove_file (bool): Whether to remove the document file after sending. Defaults to False. + """ send_doc(app, LOG_ID or 'me', doc, caption=caption, fix_markdown=fix_markdown) if remove_file: remove(doc) diff --git a/sedenecem/core/send.py b/sedenecem/core/send.py index 55bda18..e1bdd96 100644 --- a/sedenecem/core/send.py +++ b/sedenecem/core/send.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -13,6 +13,16 @@ def send(client, chat, text, fix_markdown=False, reply_id=None): + """ + Sends a message to the specified chat. If the message is too long, it is sent as a document. + + Args: + client (pyrogram.Client): The client used to send the message. + chat (Union[pyrogram.types.Chat, int, str]): The ID, username, or `Chat` object of the chat to send the message to. + text (str): The text of the message to send. + fix_markdown (bool): Whether to fix the markdown of the message. Defaults to False. + reply_id (int): The ID of the message to reply to. Defaults to None. + """ if fix_markdown: text += MARKDOWN_FIX_CHAR @@ -33,14 +43,34 @@ def send(client, chat, text, fix_markdown=False, reply_id=None): send_doc(client, chat, 'temp.txt') -def send_sticker(client, chat, sticker): +def send_sticker(message, chat, sticker): + """ + Sends a sticker to the specified chat. + + Args: + message (pyrogram.types.Message): The original message object that the sticker is being sent in reply to. + chat (Union[pyrogram.types.Chat, int, str]): The ID, username, or `Chat` object of the chat to send the sticker to. + sticker (Union[pyrogram.types.Sticker, str]): The `Sticker` object or file ID of the sticker to send. + """ try: - client.send_sticker(chat.id if isinstance(chat, Chat) else chat, sticker) + message._client.send_sticker( + chat.id if isinstance(chat, Chat) else chat, sticker + ) except BaseException: pass def send_doc(client, chat, doc, caption='', fix_markdown=False): + """ + Sends a document to the specified chat. + + Args: + client (pyrogram.Client): The client used to send the message. + chat (Union[pyrogram.types.Chat, int, str]): The ID, username, or `Chat` object of the chat to send the document to. + doc (str): The file path of the document to send. + caption (str): The caption of the document. Defaults to an empty string. + fix_markdown (bool): If True, the caption will be appended with a markdown fix character. Defaults to False. + """ try: if len(caption) > 0 and fix_markdown: caption += MARKDOWN_FIX_CHAR diff --git a/sedenecem/core/webdriver.py b/sedenecem/core/webdriver.py index 3848c26..dbcbf03 100644 --- a/sedenecem/core/webdriver.py +++ b/sedenecem/core/webdriver.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. @@ -12,6 +12,15 @@ def get_webdriver(): + """ + Returns a headless Chrome webdriver object with specified options. + + Returns: + selenium.webdriver.chrome.webdriver.WebDriver: The Chrome webdriver object. + + Raises: + Exception: If the CHROME_DRIVER executable path is not found. + """ try: options = ChromeOptions() options.add_argument('--headless') diff --git a/sedenecem/sql/blacklist_sql.py b/sedenecem/sql/blacklist_sql.py index 2386760..337bcd5 100644 --- a/sedenecem/sql/blacklist_sql.py +++ b/sedenecem/sql/blacklist_sql.py @@ -24,7 +24,7 @@ def __eq__(self, other): ) -BlackListFilters.__table__.create(checkfirst=True) +BlackListFilters.__table__.create(bind=SESSION.get_bind(), checkfirst=True) BLACKLIST_FILTER_INSERTION_LOCK = RLock() diff --git a/sedenecem/sql/filters_sql.py b/sedenecem/sql/filters_sql.py index 760d0ac..412c7ff 100644 --- a/sedenecem/sql/filters_sql.py +++ b/sedenecem/sql/filters_sql.py @@ -26,7 +26,7 @@ def __eq__(self, other): ) -Filters.__table__.create(checkfirst=True) +Filters.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def get_filter(chat_id, keyword): diff --git a/sedenecem/sql/gban_sql.py b/sedenecem/sql/gban_sql.py index 460bb3a..b3d2250 100644 --- a/sedenecem/sql/gban_sql.py +++ b/sedenecem/sql/gban_sql.py @@ -14,7 +14,7 @@ def __init__(self, sender): self.sender = str(sender) -GBan.__table__.create(checkfirst=True) +GBan.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def is_gbanned(sender): diff --git a/sedenecem/sql/gdrive_sql.py b/sedenecem/sql/gdrive_sql.py index 055fef2..f952b23 100644 --- a/sedenecem/sql/gdrive_sql.py +++ b/sedenecem/sql/gdrive_sql.py @@ -14,7 +14,7 @@ def __init__(self, user_id): self.user_id = user_id -GDriveCreds.__table__.create(checkfirst=True) +GDriveCreds.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def set(user_id, credentials): diff --git a/sedenecem/sql/gmute_sql.py b/sedenecem/sql/gmute_sql.py index 29612bc..44b546d 100644 --- a/sedenecem/sql/gmute_sql.py +++ b/sedenecem/sql/gmute_sql.py @@ -14,7 +14,7 @@ def __init__(self, sender): self.sender = str(sender) -GMute.__table__.create(checkfirst=True) +GMute.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def is_gmuted(sender): diff --git a/sedenecem/sql/keep_read_sql.py b/sedenecem/sql/keep_read_sql.py index 642d063..08ac3d5 100644 --- a/sedenecem/sql/keep_read_sql.py +++ b/sedenecem/sql/keep_read_sql.py @@ -14,7 +14,7 @@ def __init__(self, sender): self.groupid = str(sender) -KRead.__table__.create(checkfirst=True) +KRead.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def is_kread(): diff --git a/sedenecem/sql/mute_sql.py b/sedenecem/sql/mute_sql.py index 48b3be6..56c9517 100644 --- a/sedenecem/sql/mute_sql.py +++ b/sedenecem/sql/mute_sql.py @@ -16,7 +16,7 @@ def __init__(self, chat_id, sender): self.sender = str(sender) -Mute.__table__.create(checkfirst=True) +Mute.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def is_muted(chat_id, sender): diff --git a/sedenecem/sql/notes_sql.py b/sedenecem/sql/notes_sql.py index decd1ea..a833505 100644 --- a/sedenecem/sql/notes_sql.py +++ b/sedenecem/sql/notes_sql.py @@ -19,7 +19,7 @@ def __init__(self, chat_id, keyword, reply, f_mesg_id): self.f_mesg_id = f_mesg_id -Notes.__table__.create(checkfirst=True) +Notes.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def get_note(chat_id, keyword): diff --git a/sedenecem/sql/pm_permit_sql.py b/sedenecem/sql/pm_permit_sql.py index d9e8413..c34174c 100644 --- a/sedenecem/sql/pm_permit_sql.py +++ b/sedenecem/sql/pm_permit_sql.py @@ -14,13 +14,13 @@ def __init__(self, chat_id): self.chat_id = str(chat_id) # ensure string -PMPermit.__table__.create(checkfirst=True) +PMPermit.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def is_approved(chat_id): try: return SESSION.query(PMPermit).filter(PMPermit.chat_id == str(chat_id)).one() - except BaseException: + except BaseException as e: return None finally: SESSION.close() diff --git a/sedenecem/sql/snips_sql.py b/sedenecem/sql/snips_sql.py index 1eb29b0..18a4ef3 100644 --- a/sedenecem/sql/snips_sql.py +++ b/sedenecem/sql/snips_sql.py @@ -18,7 +18,7 @@ def __init__(self, snip, reply, f_mesg_id): self.f_mesg_id = f_mesg_id -Snips.__table__.create(checkfirst=True) +Snips.__table__.create(bind=SESSION.get_bind(), checkfirst=True) def get_snip(keyword): diff --git a/sedenecem/translator/__init__.py b/sedenecem/translator/__init__.py index 0f81797..f32076a 100644 --- a/sedenecem/translator/__init__.py +++ b/sedenecem/translator/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 180d0d5..744acee 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -109,20 +109,10 @@ "codenameUsage": "Usage: .codename <brand> <device> eg: .codename Xiaomi Mi 9T Pro", "colorsInfo": ".color <color code>\nUsage: Print out color code you specified.\neg: .color #330066", "colorsUsage": "Maybe you can learn something by reading here..\n.color <color code> | eg: .color #330066", - "covidCases": "Cases", - "covidData": "\ud83c\uddf9\ud83c\uddf7 Coronavirus Data \ud83c\uddf9\ud83c\uddf7", - "covidDate": "Date", - "covidDeaths": "Deaths", "covidError": "Something went wrong.", - "covidHealed": "Healed", - "covidInfo": ".covid\nUsage: Latest Covid 19 statistics for Turkey.", - "covidPatients": "Patients", - "covidPneumonia": "Pneumonia", - "covidSeriouslyill": "Seriously ill", - "covidTests": "Test", - "covidToday": "Today", - "covidTotal": "Total", "cpUsage": "\ud83d\ude02\ud83c\udd71\ufe0fIvE\ud83d\udc50sOME\ud83d\udc45text\ud83d\udc45for\u270c\ufe0fMe\ud83d\udc4ctO\ud83d\udc50MAkE\ud83d\udc40iT\ud83d\udc9efunNy!\ud83d\udca6", + "currencyError": "What you're writing looks like an alien currency, so I can't convert it.", + "currencyInfo": ".currency <amount> <from> <to>\nUsage: Converts various currencies for you.", "ddgoDesc": "No description found.", "ddgoInfo": ".ddgo <query>\nUsage: Does a search on DuckDuckGo.", "ddgoLog": "DuckDuckGo Search query %1 was executed successfully", @@ -137,6 +127,7 @@ "delErrorLog": "Well, I can't delete a message", "delInfo": ".del\nUsage: Deletes the message you replied to.", "delResultLog": "Deletion of message was successful.", + "delWelcome": "Welcome message deleted for this chat", "delayspamLog": "#DELAYSPAM\nDelaySpam was executed successfully", "deleted": "Deleted", "deletedAcc": "Deleted Account", @@ -147,9 +138,7 @@ "deviceSearchResultChild": "%1Brand:%1 %2\n%1Name:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Usage: .device <codename> eg: .device raphael", "directError": "Error occurred while processing %1", - "directGdriveCookie": "This file requires login Google account to access & download.\nUse cookies.txt to download file.\n\n", - "directGdriveCookieUsage": "Usage: `wget --load-cookies cookies.txt` directLink", - "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 not supported", "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", @@ -178,9 +167,6 @@ "exifLog": "EXIF data for image:\n", "exifMaps": "Google Maps Link: ", "exifProcess": "Extracting EXIF information.", - "ezanvaktiErrorInfo": "No information was found for %1.", - "ezanvaktiKonum": "Please specify a city next to the command.", - "ezanvaktiShowInfo": "%1Prayer Times%1\n\n\ud83d\udccd %1Location:%1 %2%3%2\n\n\ud83c\udfd9 %1Fajr:%1 %2%4%2\n\ud83c\udf05 %1Tulu:%1 %2%5%2\n\ud83c\udf07 %1Zuhr:%1 %2%6%2\n\ud83c\udf06 %1Asr:%1 %2%7%2\n\ud83c\udf03 %1Maghrib:%1 %2%8%2\n\ud83c\udf0c %1Isha:%1 %2%9%2", "fetchProxy": "Fetching proxy\u2026", "filterAdded": "%2Filter%2 %1%3%1 %2added%2", "filterChats": "Filters in chat:", @@ -256,7 +242,7 @@ "herokuInfo": ".quota\nUsage: It shows your Heroku resource & dyno usage.\n\n.drestart\nUsage: Restarts your Heroku dyno.\n\n.logs\nUsage: Sends Heroku app log.", "herokuQuotaInHM": "%1h %2m", "herokuQuotaInfo": "%2Heroku Remaining Quota%2\n\n%2Total:%2 %1%3%1\n%2Used:%2 %1%4 (%5\u00bd)%1\n%2Remaining:%2 %1%6 (%7\u00bd)%1\n%2App usage:%2 %1%8 (%9\u00bd)%1", - "imeiInfo": ".imei <imeinumber>\nUsage: Check if IMEI is registered and get device information about model, vendor, etc.", + "imeiInfo": ".imeicheck <imeinumber>\nUsage: Check if IMEI is registered and get device information about model, vendor, etc.", "imgInfo": ".img <query>\nUsage: Does an image search on Google and shows 5 images.", "imgUsage": "You must enter a search term.", "infoWeather": ".weather <city> or .weather\nUsage: Gets the weather of a city.", @@ -304,6 +290,8 @@ "locksUnlockNoArgs": "I can't unlock void bruh", "locksUnlockSuccess": "%1unlocked %2 for this chat!%1", "logidTest": "This is a test report, it is for LOG_ID check.", + "losBuild": "%1Latest LineageOS for%1 %2%3%2%1:\nFilename: [%4](%5)\nSize:%1 %2%6%2\n%1Version:%1 %2%7%2\n%1Date:%1 %2%8%2\n\n%2For other builds:%2\n%1https://download.lineageos.org/%3%1", + "losNoBuild": "%1No suitable version found for%1 %2%3%2%1!%1", "lyricsError": "Error: please use '-' as divider for <artist> and <song>\neg: Adele - Hello", "lyricsError2": "Please give the artist and song name", "lyricsInfo": ".lyrics\nUsage: .lyrics <artist> - <song>\nNOTE: '-' separator is important!", @@ -328,6 +316,8 @@ "noFilter": "There are no filters in this chat.", "noNote": "No notes found in this chat", "noSnip": "No snip is currently available.", + "noWelcome": "No welcome message found in this chat", + "noWelcome2": "I didn't have any welcome messages here!", "nonSqlMode": "Running on Non-SQL mode!", "notFound": "Not Found", "notHeroku": "Bot is not running on Heroku", @@ -365,7 +355,7 @@ "packinfoResult": "%1Sticker Title:%1 %2%3%2\n%1Sticker Short Name:%1 %2%4%2\n%1Official:%1 %2%5%2\n%1Archived:%1 %2%6%2\n%1Animated:%1 %2%7%2\n%1Stickers In Pack:%1 %2%8%2\n%1Emojis In Pack:%1\n%9", "pasteConnErr": "Could not connect to server.", "pasteErr": "Enter text for paste.", - "pasteInfo": ".paste <text/reply>\nUsage: Pastes the given text on the Hastebin\n\n.getpaste <text/reply>\nUsage: Gets the content of a paste url from Hastebin\n\n(https://hastebin.com/)", + "pasteInfo": ".paste <text/reply>\nUsage: Pastes the given text on the Pastebin\n\n.getpaste <text/reply>\nUsage: Gets the content of a paste url from Pastebin\n\n(https://dpaste.org/)", "phhError": "%1Couldn't find anything for%1 %2%3%2", "picspamLog": "#PICSPAM\nPicSpam was executed successfully", "pinLog": "#PIN\nGROUP: %1 (%2%3%2)", @@ -409,7 +399,7 @@ "purgemeUsage": "Purgeme failed, please specify number.", "pyrogramDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%4%2", "pyrogramUp": "%1Filename:%1 %2%3%2\n%1Uploaded%1 %2%4%2", - "pythonVersionError": "You must have at least a Python version 3.10\nMultiple features depend on this. Bot quitting.", + "pythonVersionError": "You must have at least a Python version 3.8\nMultiple features depend on this. Bot quitting.", "quotlyInfo": ".q\nUsage: Enhance ur text to sticker.", "randomResult": "%1Query:%1\n%2%3%2\n%1Output:%1\n%2%4%2", "randomUsage": "2 or more argument required.", @@ -485,7 +475,6 @@ "runstr8": "You run, you die.", "runstr9": "Jokes on you, I'm everywhere", "safeEval": "This may not be a safe eval query.", - "sangmataInfo": ".sangmata\nUsage: View the name history of specified user.", "scraper1": "Translator", "scraper2": "Text to Speech", "scraperLog": "%1Language for %2 changed to %3.%1", @@ -515,6 +504,7 @@ "shippingNoResult": "Tracking information not found!", "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4", "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", + "showWelcome": "I'm currently welcoming new users with this welcome message", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", "snipChats": "Available snips:", @@ -637,17 +627,23 @@ "videoUploader": "Uploader:", "weatherErrorCity": "Specify a city as default with the WEATHER variable, or specify which city you want the weather when typing the command!", "weatherErrorServer": "Weather information couldn't be retrieved.", + "welcomeAdded": "Welcome message succesfully added!", + "welcomeError": "Welcome message couldn't be added", + "welcomeInfo": ".setwelcome <text/reply>\nUsage: Saves message as a welcome message in the chat\n\nAvailable variables for welcome messages\n{mention}, {title}, {first}, {last}, {userid}, {username}, {my_first}, {my_last}, {my_mention}, {my_username}\n\n.checkwelcome\nUsage: Check whether you have a welcome message in the chat\n\n.delwelcome\nUsage: Deletes the welcome message for the current chat", + "welcomeLog": "#WELCOME\nChat ID: %1%2%1\n\nAbove message saved for reply note, please don't delete!", + "welcomeSqlLog": "Unable to run welcomes module, no SQL connection found", + "welcomeUpdated": "Welcome message succesfully updated!", "whoisError": "Failed to fetching user..", "whoisProcess": "Fetching user information..", "whoisResult": "%1User Info:\n\nFirst name:%1 %2%3%2\n%1Last name:%1 %2%4%2\n%1Username: %5\nUser ID:%1 %2%6%2\n%1Profile Photos:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Common chats:%1 %2%9%2\n%1Is Premium:%1 %2%10%2\n\n%1Bio:%1 %2%11%2\n%1Last Seen:%1 %2%12%2\n%1Profile link: [%3](tg://user?id=%6)\n\n%13\n%14%1", - "wikiError": "Disambiguated page found.\n\n%1", - "wikiError2": "Page not found.\n\n%1", + "wikiError": "Page not found.", "wikiInfo": ".wiki <query>\nUsage: Does a search on Wikipedia.", "wikiLog": "Wiki query %1%2%1 was executed successfully", "wrongCommand": "Command usage is wrong.", "wrongFilter": "Filter is wrong!", "wrongMedia": "Wrong media type!", "wrongUrl": "Wrong URL!", + "getPasteOut": "%1Fetched Pastebin URL content successfully!\n\nContent:%1 %2", "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl <mp3 or mp4> <url>\nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354", @@ -659,4 +655,4 @@ "zombiesRemove": "Removing deleted account(s)\u2026", "zombiesResult": "%1Removed%1 %2%3%2 %1deleted account(s).%1", "zombiesResult2": "%1Removed%1 %2%3%2 %1deleted account(s).%1\n%2%4%2 %1deleted admin account are not removed.%1" -} +} \ No newline at end of file diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 4ed4557..780cae3 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -110,20 +110,10 @@ "codenameUsage": "Kullan\u0131m: .codename <marka> <cihaz> \u00d6rnek: .codename Xiaomi Mi 9T Pro", "colorsInfo": ".color <renk kodu>\nKullan\u0131m: Belirtti\u011fniz renk kodunun \u00e7\u0131kt\u0131s\u0131n\u0131 al\u0131n.\n\u00d6rnek: .color #330066", "colorsUsage": "Belki buray\u0131 okuyarak bir \u015feyler \u00f6\u011frenebilirsin..\n.color <renk kodu> | \u00d6rnek: .color #330066", - "covidCases": "Vaka", - "covidData": "\ud83c\uddf9\ud83c\uddf7 Koronavir\u00fcs Verileri \ud83c\uddf9\ud83c\uddf7", - "covidDate": "Tarih", - "covidDeaths": "\u00d6l\u00fcm", "covidError": "Bir hata olu\u015ftu.", - "covidHealed": "\u0130yile\u015fen", - "covidInfo": ".covid\nKullan\u0131m: T\u00fcrkiye i\u00e7in g\u00fcncel Covid 19 istatistikleri.", - "covidPatients": "Hasta", - "covidPneumonia": "Zat\u00fcrre", - "covidSeriouslyill": "A\u011f\u0131r hasta", - "covidTests": "Test", - "covidToday": "Bug\u00fcn", - "covidTotal": "Toplam", "cpUsage": "\ud83d\ude02Bana\ud83d\udcafBIR\u270c\ufe0fmE\ud83c\udd71\ufe0fIn\ud83d\udc50Ver\ud83d\udc4f", + "currencyError": "Yazd\u0131\u011f\u0131n \u015fey uzayl\u0131lar\u0131n kulland\u0131\u011f\u0131 bir para birimine benziyor, bu y\u00fczden d\u00f6n\u00fc\u015ft\u00fcremiyorum.", + "currencyInfo": ".currency <miktar> <d\u00f6n\u00fc\u015ft\u00fcr\u00fclecek birim> <d\u00f6n\u00fc\u015fecek birim>\nKullan\u0131m: Belirtilen para miktarlar\u0131n\u0131 d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n\n.d\u00f6viz\nKullan\u0131m: G\u00fcncel d\u00f6viz kurlar\u0131n\u0131 getirir.", "ddgoDesc": "A\u00e7\u0131klama sa\u011flanmam\u0131\u015f", "ddgoInfo": ".ddgo <kelime>\nKullan\u0131m: H\u0131zl\u0131 bir DuckDuckGo aramas\u0131 yapar.", "ddgoLog": "%1 s\u00f6zc\u00fc\u011f\u00fc ba\u015far\u0131yla DuckDuckGo'da arat\u0131ld\u0131!", @@ -138,6 +128,7 @@ "delErrorLog": "Bu mesaj\u0131 silemiyorum.", "delInfo": ".del\nKullan\u0131m: Yan\u0131tlad\u0131\u011f\u0131n\u0131z mesaj\u0131 siler.", "delResultLog": "Hedeflenen mesaj\u0131n silinmesi ba\u015far\u0131l\u0131yla tamamland\u0131", + "delWelcome": "Welcome message deleted for this chat", "delayspamLog": "#DELAYSPAM\nDelaySpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "deleted": "Silinmi\u015f", "deletedAcc": "Silinen Hesap", @@ -148,9 +139,7 @@ "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", - "directGdriveCookie": "Bu dosyay\u0131 indirebilmek i\u00E7in Google hesab\u0131n\u0131za giri\u015F yapmal\u0131s\u0131n\u0131z.\nDosyay\u0131 indirmek i\u00E7in cookies.txt\u0027i kullan\u0131n.\n\n", - "directGdriveCookieUsage": "Kullan\u0131m: `wget --load-cookies cookies.txt` directLink", - "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", + "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 desteklenmiyor", "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", @@ -179,9 +168,6 @@ "exifLog": "EXIF verisi:\n", "exifMaps": "Google Maps Linki: ", "exifProcess": "EXIF verisi \u00e7\u0131kart\u0131l\u0131yor.", - "ezanvaktiErrorInfo": "%1 i\u00e7in bir bilgi bulunamad\u0131.", - "ezanvaktiKonum": "L\u00fctfen komutun yan\u0131na bir \u015fehir belirtin", - "ezanvaktiShowInfo": "%1Diyanet Namaz Vakitleri%1\n\n\ud83d\udccd %1Yer:%1 %2%3%2\n\n\ud83c\udfd9 %1\u0130msak:%1 %2%4%2\n\ud83c\udf05 %1G\u00fcne\u015f:%1 %2%5%2\n\ud83c\udf07 %1\u00d6\u011fle:%1 %2%6%2\n\ud83c\udf06 %1\u0130kindi:%1 %2%7%2\n\ud83c\udf03 %1Ak\u015fam:%1 %2%8%2\n\ud83c\udf0c %1Yats\u0131:%1 %2%9%2", "fetchProxy": "Proxy getiriliyor\u2026", "filterAdded": "%2Filtre%2 %1%3%1 %2eklendi%2", "filterChats": "Sohbetteki filtreler:", @@ -258,7 +244,7 @@ "herokuInfo": ".kota\nKullan\u0131m: Heroku kaynak kullan\u0131m\u0131n\u0131z\u0131 g\u00f6sterir.\n\n.drestart\nKullan\u0131m: Heroku dynosunu yeniden ba\u015flat\u0131r.\n\n.logs\nKullan\u0131m: Heroku uygulama g\u00fcnl\u00fc\u011f\u00fc g\u00f6nderir.", "herokuQuotaInHM": "%1s %2d", "herokuQuotaInfo": "%2Heroku Kalan Kota%2\n\n%2Toplam:%2 %1%3%1\n%2Kullan\u0131lan:%2 %1%4 (\u00bd%5)%1\n%2Kalan:%2 %1%6 (\u00bd%7)%1\n%2Uygulama kullan\u0131m\u0131:%2 %1%8 (\u00bd%9)%1", - "imeiInfo": ".imei <imeinumber>\nKullan\u0131m: IMEI numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda bilgi verir", + "imeiInfo": ".imeicheck <imeinumber>\nKullan\u0131m: IMEI numaras\u0131n\u0131n kay\u0131tl\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder ve cihaz hakk\u0131nda bilgi verir", "imgInfo": ".img <kelime>\nKullan\u0131m: Google \u00fczerinde h\u0131zl\u0131 bir resim aramas\u0131 yapar ve ilk 5 resmi g\u00f6sterir.", "imgUsage": "Bir arama terimi girmelisiniz.", "infoWeather": "Kullan\u0131m: .havadurumu <\u015fehir-ad\u0131> veya .havadurumu\nBir b\u00f6lgenin hava durumunu verir.", @@ -306,6 +292,8 @@ "locksUnlockNoArgs": "Hi\u00e7li\u011fin kilidini a\u00e7amam dostum", "locksUnlockSuccess": "%1Bu sohbet i\u00e7in %2 kilidi a\u00e7\u0131ld\u0131!%1", "logidTest": "Bu bir test raporudur, LOG_ID kontrol\u00fc i\u00e7indir.", + "losBuild": "%2%3%2 %1i\u00e7in G\u00fcncel LineageOS:\nDosya: [%4](%5)\nBoyut:%1 %2%6%2\n%1S\u00fcr\u00fcm:%1 %2%7%2\n%1Tarih:%1 %2%8%2\n\n%2Di\u011fer s\u00fcr\u00fcmler i\u00e7in:%2\n%1https://download.lineageos.org/%3%1", + "losNoBuild": "%2%3%2 %1i\u00e7in uygun s\u00fcr\u00fcm bulunamad\u0131!%1", "lyricsError": "Hata: l\u00fctfen <sanat\u00e7\u0131> ve <\u015fark\u0131> i\u00e7in b\u00f6l\u00fcc\u00fc olarak '-' kullan\u0131n\n\u00d6rnek: Rota - Belki Ba\u015fka Zaman", "lyricsError2": "L\u00fctfen sanat\u00e7\u0131 ve \u015fark\u0131 ismini veriniz", "lyricsInfo": ".lyrics\nKullan\u0131m: .`lyrics <sanat\u00e7\u0131 ad\u0131> - <\u015fark\u0131 ismi>\nNOT: '-' ayrac\u0131 \u00f6nemli!", @@ -330,6 +318,8 @@ "noFilter": "Bu sohbette hi\u00e7 filtre yok.", "noNote": "Bu sohbette kaydedilmi\u015f not bulunamad\u0131", "noSnip": "No snip is currently available.", + "noWelcome": "No welcome message found in this chat", + "noWelcome2": "I didn't have any welcome messages here!", "nonSqlMode": "SQL d\u0131\u015f\u0131 modda \u00e7al\u0131\u015f\u0131yorum, bunu ger\u00e7ekle\u015ftiremem", "notFound": "Bulunamad\u0131", "notHeroku": "Bot \u015fu an Heroku'da \u00e7al\u0131\u015fm\u0131yor", @@ -366,7 +356,7 @@ "packinfoResult": "%1\u00c7\u0131kartma paketi ba\u015fl\u0131\u011f\u0131:%1 %2%3%2\n%1\u00c7\u0131kartma paketi k\u0131sa ad\u0131:%1 %2%4%2\n%1Resmi paket mi:%1 %2%5%2\n%1Ar\u015fivlenmi\u015f mi:%1 %2%6%2\n%1Animasyonlu mu:%1 %2%7%2\n%1Pakettki \u00e7\u0131kartma say\u0131s\u0131:%1 %2%8%2\n%1Paketteki emoji say\u0131s\u0131:%1\n%9", "pasteConnErr": "Sunucuya ba\u011flan\u0131lamad\u0131", "pasteErr": "Yap\u0131\u015ft\u0131rmak i\u00e7in bir \u015fey yaz\u0131n.", - "pasteInfo": ".paste <metin/yan\u0131tlama>\nKullan\u0131m: Verilen metni Hastebin'e yap\u0131\u015ft\u0131r\u0131r\n\n.getpaste <metin/yan\u0131tlama>\nKullan\u0131m: Hastebin link i\u00e7eri\u011fini metne aktar\u0131r\n\n(https://hastebin.com/)", + "pasteInfo": ".paste <metin/yan\u0131tlama>\nKullan\u0131m: Verilen metni Pastebine'e yap\u0131\u015ft\u0131r\u0131r\n\n.getpaste <metin/yan\u0131tlama>\nKullan\u0131m: Pastebin link i\u00e7eri\u011fini metne aktar\u0131r\n\n(https://dpaste.org/)", "phhError": "%2%3%2 %1aramas\u0131 i\u00e7in bir ROM bulunamad\u0131%1", "picspamLog": "#PICSPAM\nPicSpam ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi", "pinLog": "#PIN\nGRUP: %1 (%2%3%2)", @@ -410,7 +400,7 @@ "purgemeUsage": "Temizlik yap\u0131lamad\u0131, say\u0131 ge\u00e7ersiz.", "pyrogramDown": "%1Dosya Ad\u0131:%1 %2%3%2\n%1\u0130ndirildi:%1 %2%4%2", "pyrogramUp": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Y\u00fcklendi%1 %2%4%2", - "pythonVersionError": "En az Python 3.10 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", + "pythonVersionError": "En az Python 3.8 s\u00fcr\u00fcm\u00fcne sahip olman\u0131z gerekir.\nBirden fazla \u00f6zellik buna ba\u011fl\u0131d\u0131r. Bot kapat\u0131l\u0131yor.", "quotlyInfo": ".q\nKullan\u0131m: Metninizi \u00e7\u0131kartmaya d\u00f6n\u00fc\u015ft\u00fcr\u00fcn.", "randomResult": "%1Sorgu:%1\n%2%3%2\n%1\u00c7\u0131kt\u0131:%1\n%2%4%2", "randomUsage": "2 veya daha fazla se\u00e7enek gerekli.", @@ -485,7 +475,6 @@ "runstr8": "Ka\u00e7arsan, \u00f6l\u00fcrs\u00fcn.", "runstr9": "\u015eakac\u0131 seni, Ben heryerdeyim.", "safeEval": "Bu g\u00fcvenli bir eval sorgusu olmayabilir.", - "sangmataInfo": ".sangmata\nKullan\u0131m: Belirtilen kullan\u0131c\u0131n\u0131n isim ge\u00e7mi\u015fini g\u00f6r\u00fcnt\u00fcleyin.", "scraper1": "\u00c7eviri", "scraper2": "Yaz\u0131dan sese", "scraperLog": "%1%2 mod\u00fcl\u00fc i\u00e7in varsay\u0131lan dil %3 diline \u00e7evirildi.%1", @@ -515,6 +504,7 @@ "shippingNoResult": "Takip bilgisi bulunamad\u0131!", "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4", "shippingTrack": ".<firma> <takipNo>\n\nKullan\u0131m: Kargo bilgilerini g\u00f6sterir.\n\n\u00d6rnek: `.ups 1234567890`\n\nDesteklenen firmalar:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", + "showWelcome": "\u015eu anda a\u015fa\u011f\u0131daki kar\u015f\u0131lama mesaj\u0131 ile yeni kullan\u0131c\u0131lar\u0131 a\u011f\u0131rl\u0131yorum", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", "snipChats": "Mevcut snipler:", @@ -637,17 +627,23 @@ "videoUploader": "Y\u00fckleyen:", "weatherErrorCity": "WEATHER de\u011fi\u015fkeniyle bir \u015fehri varsay\u0131lan olarak belirt, ya da komutu yazarken hangi \u015fehrin hava durumunu istedi\u011fini de belirt!", "weatherErrorServer": "Hava durumu bilgisi al\u0131namad\u0131.", + "welcomeAdded": "Kar\u015f\u0131lama mesaj\u0131 kaydedildi!", + "welcomeError": "Kar\u015f\u0131lama mesaj\u0131 eklenemedi!", + "welcomeInfo": ".setwelcome <metin/yan\u0131tlama>\nUsage: Kullan\u0131m: Mesaj\u0131 sohbete kar\u015f\u0131lama mesaj\u0131 olarak kaydeder\n\nKar\u015f\u0131lama mesaj\u0131 i\u00e7in kullan\u0131labilir de\u011fi\u015fkenler:\n{mention}, {title}, {first}, {last}, {userid}, {username}, {my_first}, {my_last}, {my_mention}, {my_username}\n\n.checkwelcome\nKullan\u0131m: Sohbette kar\u015f\u0131lama mesaj\u0131 olup olmad\u0131\u011f\u0131n\u0131 kontrol eder\n\n.delwelcome\nKullan\u0131m: Ge\u00e7erli sohbet i\u00e7in kar\u015f\u0131lama mesaj\u0131n\u0131 siler", + "welcomeLog": "#WELCOME\nGrup ID: %1%2%1\n\nA\u015fa\u011f\u0131daki mesaj sohbet i\u00e7in yeni Kar\u015f\u0131lama Mesaj\u0131 olarak kaydedildi, l\u00fctfen silmeyin!", + "welcomeSqlLog": "Welcomes mod\u00fcl\u00fc \u00e7al\u0131\u015ft\u0131r\u0131lam\u0131yor, SQL ba\u011flant\u0131s\u0131 bulunamad\u0131", + "welcomeUpdated": "Kar\u015f\u0131lama mesaj\u0131 g\u00fcncellendi!", "whoisError": "Kullan\u0131c\u0131 bulunamad\u0131..", "whoisProcess": "Kullan\u0131c\u0131 bilgisi getiriliyor..", "whoisResult": "%1Kullan\u0131c\u0131 Bilgisi:\n\n\u0130sim:%1 %2%3%2\n%1Soyisim:%1 %2%4%2\n%1Kullan\u0131c\u0131 Ad\u0131: %5\nKullan\u0131c\u0131 ID:%1 %2%6%2\n%1Profil Foto\u011fraf Say\u0131s\u0131:%1 %2%7%2\n%1DC ID:%1 %2%8%2\n%1Ortak Sohbetler:%1 %2%9%2\n%1Premium Kullan\u0131c\u0131:%1 %2%10%2\n\n%1Biyografi:%1 %2%11%2\n%1Son G\u00f6r\u00fclme:%1 %2%12%2\n%1Profil Ba\u011flant\u0131s\u0131: [%3](tg://user?id=%6)\n\n%13\n%14%1", - "wikiError": "Belirsiz bir sayfa bulundu.\n\n%1", - "wikiError2": "Arad\u0131\u011f\u0131n\u0131z sayfa bulunamad\u0131.\n\n%1", + "wikiError": "Arad\u0131\u011f\u0131n\u0131z sayfa bulunamad\u0131.", "wikiInfo": ".wiki <terim>\nKullan\u0131m: Bir Vikipedi aramas\u0131 ger\u00e7ekle\u015ftirir.", "wikiLog": "%1%2%1 teriminin Wikipedia sorgusu ba\u015far\u0131yla ger\u00e7ekle\u015ftirildi!", "wrongCommand": "Komut kullan\u0131m\u0131 hatal\u0131.", "wrongFilter": "Filtre hatal\u0131!", "wrongMedia": "Ge\u00e7ersiz medya t\u00fcr\u00fc!", "wrongUrl": "Hatal\u0131 link!", + "getPasteOut": "%1Pastebin içeriği başarıyla getirildi!\n\nİçerik:%1 %2", "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl <mp3 ya da mp4> <link>\nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334", @@ -659,4 +655,4 @@ "zombiesRemove": "Silinen hesap(lar) \u00e7\u0131kar\u0131l\u0131yor\u2026", "zombiesResult": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1", "zombiesResult2": "%2%3%2 %1adet silinen hesap gruptan at\u0131ld\u0131.%1\n%2%4%2 %1adet silinen y\u00f6netici hesab\u0131 at\u0131lamad\u0131.%1" -} +} \ No newline at end of file diff --git a/session.py b/session.py index 99ca723..17cb3f8 100644 --- a/session.py +++ b/session.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. From 3e344e3e553287a5453799894bada1cee5fef6b0 Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Thu, 23 Mar 2023 00:32:57 +0300 Subject: [PATCH 195/242] Release Seden v1.7.0 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 261a138..87b7175 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -116,7 +116,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.6.9' +BOT_VERSION = '1.7.0' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 2b22d39aaef0adc0102fa4d03fd8602729747951 Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Sat, 25 Mar 2023 02:54:07 +0300 Subject: [PATCH 196/242] remove ctrl-c-event in shutdown function --- sedenbot/modules/horeke.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index 0853db5..b572025 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -8,8 +8,8 @@ # from math import floor -from os import execl, getpid, kill, name -from signal import CTRL_C_EVENT, SIGINT +from os import execl, getpid, kill +from signal import SIGINT from sys import argv, executable from heroku3 import from_key @@ -189,7 +189,7 @@ def shutdown(message): def std_off(): try: - kill(getpid(), CTRL_C_EVENT if name == 'nt' else SIGINT) + kill(getpid(), SIGINT) except Exception: pass From f453f91513ad47df8dc1f47d04d67817b561d4ea Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Mon, 3 Apr 2023 03:30:54 +0300 Subject: [PATCH 197/242] improve get_status_out usage --- sedenbot/modules/effects.py | 51 +++++++------------------------------ sedenbot/modules/system.py | 33 ++++++------------------ sedenecem/core/image.py | 43 +++++++++++-------------------- sedenecem/core/misc.py | 14 ++++++---- sedenecem/core/replier.py | 4 +-- 5 files changed, 43 insertions(+), 102 deletions(-) diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index af9d30f..7a7f22f 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -8,13 +8,13 @@ # from os import path, remove -from subprocess import Popen from sedenbot import HELP from sedenecem.core import ( download_media_wc, edit, extract_args_split, + get_status_out, get_translation, reply_audio, reply_video, @@ -42,17 +42,9 @@ def earrape(message): else: edit(message, f'`{get_translation("applyEarrape")}`') media = download_media_wc(reply) - process = Popen( - [ - 'ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp4', - ] + get_status_out( + f'ffmpeg -i {media} -af acrusher=.1:1:64:0:log {media}.mp4' ) - process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_video(reply or message, f'{media}.mp4', delete_file=True) remove(media) @@ -74,17 +66,9 @@ def earrape(message): else: edit(message, f'`{get_translation("applyEarrape")}`') media = download_media_wc(reply) - process = Popen( - [ - 'ffmpeg', - '-i', - f'{media}', - '-af', - 'acrusher=.1:1:64:0:log', - f'{media}.mp3', - ] + get_status_out( + f'ffmpeg -i {media} -af acrusher=.1:1:64:0:log {media}.mp3' ) - process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) @@ -115,18 +99,9 @@ def nightcore(message): filename = f'{media}.mp3' if path.exists(filename): remove(filename) - - process = Popen( - [ - 'ffmpeg', - '-i', - media, - '-af', - 'asetrate=44100*1.16,aresample=44100,atempo=1', - filename, - ] + get_status_out( + f'ffmpeg -i {media} -af asetrate=44100*1.16,aresample=44100,atempo=1 {filename}' ) - process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) @@ -155,17 +130,9 @@ def slowedtoperfection(message): if path.exists(filename): remove(filename) - process = Popen( - [ - 'ffmpeg', - '-i', - media, - '-af', - 'aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1', - filename, - ] + get_status_out( + f'ffmpeg -i {media} -af aecho=1.0:0.7:20:0.5,asetrate=44100*0.84,aresample=44100,atempo=1 {filename}' ) - process.communicate() edit(message, f'`{get_translation("uploadMedia")}`') reply_audio(reply or message, f'{media}.mp3', delete_file=True) remove(media) diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index c4a6d8c..02c2fcb 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -14,11 +14,13 @@ from shutil import which from pyrogram.raw.functions.help import GetNearestDc -from sedenbot import ALIVE_MSG, BOT_VERSION, CHANNEL, HELP, HOSTNAME, USER + +from sedenbot import ALIVE_MSG, BOT_VERSION, CHANNEL, HELP from sedenbot.modules.ecem import ecem from sedenecem.core import ( edit, extract_args, + get_status_out, get_translation, reply, reply_doc, @@ -34,16 +36,8 @@ @sedenify(pattern='^.neofetch$') def neofetch(message): if which('neofetch'): - from subprocess import PIPE, Popen - - process = Popen( - ['neofetch', f'HOSTNAME={HOSTNAME}', f'USER={USER}', '--stdout'], - stdout=PIPE, - stderr=PIPE, - shell=True, - ) - result, _ = process.communicate() - edit(message, f'`{result.decode()}`') + _, process = get_status_out('neofetch --stdout') + edit(message, f'`{process}`') else: edit(message, f'`{get_translation("neofetchNotFound")}`') @@ -51,21 +45,12 @@ def neofetch(message): @sedenify(pattern='^.botver$') def bot_version(message): if which('git'): - from subprocess import PIPE, Popen - - changes = Popen( - ['git', 'rev-list', '--all', '--count'], - stdout=PIPE, - stderr=PIPE, - universal_newlines=True, - ) - result, _ = changes.communicate() - + _, changes = get_status_out('git rev-list --all --count') edit( message, get_translation( 'sedenShowBotVersion', - ['**', '`', CHANNEL, BOT_VERSION, result], + ['**', '`', CHANNEL, BOT_VERSION, changes], ), preview=False, ) @@ -135,9 +120,7 @@ def terminal(message): result = get_translation("termNoResult") try: - from sedenecem.core.misc import __status_out__ - - _, result = __status_out__(command) + _, result = get_status_out(command) except BaseException as e: pass diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index 0a62c28..4431cfa 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -8,21 +8,20 @@ # from math import floor -from subprocess import Popen from PIL import Image -from .misc import get_download_dir +from .misc import get_download_dir, get_status_out def sticker_resize(photo): """ Resizes a given sticker image file to have a maximum dimension of 512 pixels while maintaining aspect ratio. If the image is already smaller than 512x512 pixels, it will be resized to its original size. - + Args: photo (str): The file path to the sticker image file to be resized. - + Returns: str: The file path to the resized image file in PNG format, stored in a temporary directory. """ @@ -54,35 +53,23 @@ def sticker_resize(photo): def video_convert(video): """ Converts a video file to a webm format with dimensions of 512x512 and duration of 3.0 seconds. - + Args: video (str): Path of the video file to be converted. - + Returns: str: Path of the converted webm file. """ - process = Popen( - [ - 'ffmpeg', - '-i', - f'{video}', - '-vf', - 'scale=512:512:force_original_aspect_ratio=decrease', - '-c:v', - 'libvpx-vp9', - '-crf', - '30', - '-b:v', - '500k', - '-pix_fmt', - 'yuv420p', - '-t', - '2.9', - '-an', - '-y', - f'{get_download_dir()}/temp.webm', - ] + get_status_out( + f'ffmpeg -i {video} \ + -vf scale=512:512:force_original_aspect_ratio=decrease \ + -c:v libvpx-vp9 \ + -crf 30 \ + -b:v 500k \ + -pix_fmt yuv420p \ + -t 2.9 \ + -an \ + -y {get_download_dir()}/temp.webm' ) - _ = process.communicate() output = f'{get_download_dir()}/temp.webm' return output diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 4e960d1..36b920a 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -9,7 +9,7 @@ from os import makedirs from re import escape, sub -from subprocess import STDOUT, CalledProcessError, check_output +from subprocess import STDOUT, DEVNULL, CalledProcessError, check_output from typing import List from bs4 import BeautifulSoup @@ -553,7 +553,7 @@ def get_duration(media): Returns: int: The duration in seconds or None if the duration cannot be determined. """ - out = __status_out__( + out = get_status_out( f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "{media}"' ) if LOG_VERBOSE: @@ -563,7 +563,7 @@ def get_duration(media): return None -def __status_out__(cmd, encoding='utf-8'): +def get_status_out(cmd, encoding='utf-8'): """ Runs a shell command and captures its output. @@ -576,14 +576,18 @@ def __status_out__(cmd, encoding='utf-8'): """ try: output = check_output( - cmd, shell=True, text=True, stderr=STDOUT, encoding=encoding + cmd, + shell=True, + text=True, + stderr=STDOUT if LOG_VERBOSE else DEVNULL, + encoding=encoding, ) return (0, output) except CalledProcessError as ex: return (ex.returncode, ex.output) except BaseException as e: if encoding != 'latin-1': - return __status_out__(cmd, 'latin-1') + return get_status_out(cmd, 'latin-1') raise e diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index fc4fcfa..2151a26 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -14,7 +14,7 @@ from sedenbot import LOG_VERBOSE -from .misc import MARKDOWN_FIX_CHAR, __status_out__, get_download_dir, get_duration +from .misc import MARKDOWN_FIX_CHAR, get_download_dir, get_duration, get_status_out def reply_img( @@ -119,7 +119,7 @@ def reply_video( thumb = f'{get_download_dir()}/thumb.png' if path.exists(thumb): remove(thumb) - out = __status_out__( + out = get_status_out( f'ffmpeg -i {video} -ss 00:00:01.000 -vframes 1 {thumb}' ) if LOG_VERBOSE: From da3fe356ae32426320308d8692bf7b449fc23324 Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Mon, 3 Apr 2023 03:31:49 +0300 Subject: [PATCH 198/242] Release Seden v1.7.1 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 87b7175..faa4f03 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -116,7 +116,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.0' +BOT_VERSION = '1.7.1' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 4f9e8ea0905d3a8d1a952b8b185c7e7f15fbb53b Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Mon, 3 Apr 2023 03:33:27 +0300 Subject: [PATCH 199/242] improve init databases --- sedenbot/__init__.py | 48 +++++++++----------------------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index faa4f03..25dec6a 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -144,10 +144,6 @@ def set_logger(): # Change Alive Message ALIVE_MSG = environ.get('ALIVE_MSG', None) -# For neofetch -HOSTNAME = environ.get('HOSTNAME', 'DerUntergang') -USER = environ.get('USER', 'sedenecem') - # Chrome Driver and Headless Google Chrome Binaries CHROME_DRIVER = environ.get('CHROME_DRIVER', 'chromedriver') @@ -202,50 +198,26 @@ def set_logger(): ENV_RESTRICTED_KEYS = ['HEROKU_KEY', 'HEROKU_APPNAME', 'SESSION', 'API_ID', 'API_HASH'] -def load_brain(): - try: - if path.exists('learning-data-root.check'): - remove('learning-data-root.check') - URL = ( - 'https://raw.githubusercontent.com/NaytSeyd/' - 'databasescape/master/learning-data-root.check' - ) - with open('learning-data-root.check', 'wb') as load: - load.write(get(URL).content) - DB = connect('learning-data-root.check') - CURSOR = DB.cursor() - CURSOR.execute('SELECT * FROM BRAIN1') - ALL_ROWS = CURSOR.fetchall() - for i in ALL_ROWS: - BRAIN.append(i[0]) - DB.close() - except BaseException: - pass - - -def load_bl(): +def load_data(file_name, table_name, data_list): try: - if path.exists('blacklist.check'): - remove('blacklist.check') - URL = ( - 'https://raw.githubusercontent.com/NaytSeyd/' - 'databaseblacklist/master/blacklist.check' - ) - with open('blacklist.check', 'wb') as load: + if path.exists(file_name): + remove(file_name) + URL = f'https://raw.githubusercontent.com/NaytSeyd/databasescape/master/{file_name}' + with open(file_name, 'wb') as load: load.write(get(URL).content) - DB = connect('blacklist.check') + DB = connect(file_name) CURSOR = DB.cursor() - CURSOR.execute('SELECT * FROM RETARDS') + CURSOR.execute(f'SELECT * FROM {table_name}') ALL_ROWS = CURSOR.fetchall() for i in ALL_ROWS: - BLACKLIST.append(i[0]) + data_list.append(i[0]) DB.close() except BaseException: pass -load_brain() -load_bl() +load_data('learning-data-root.check', 'BRAIN1', BRAIN) +load_data('blacklist.check', 'RETARDS', BLACKLIST) class PyroClient(Client): From 8a1a032a6d290b9af37ff8a1936355db9621e3ff Mon Sep 17 00:00:00 2001 From: naytseyd <naytseyd@yandex.com> Date: Mon, 3 Apr 2023 03:33:47 +0300 Subject: [PATCH 200/242] Release Seden v1.7.2 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 25dec6a..7bc6d5f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -116,7 +116,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.1' +BOT_VERSION = '1.7.2' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 7e3e8a2fe2c983a6a25059a0112642778996f448 Mon Sep 17 00:00:00 2001 From: Fatih Eser <fatihesergg@gmail.com> Date: Thu, 6 Apr 2023 19:52:07 +0300 Subject: [PATCH 201/242] ezanvakti: fix scraper result.body.find can't return list --- sedenbot/modules/ezanvakti.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 84fba7b..3fd71e7 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -28,10 +28,10 @@ def ezanvakti(message): result = get_result(konum) except BaseException: return edit(message, f'`{konum} için bir bilgi bulunamadı.`') - res1 = result.body.findAll('div', {'class': ['body-content']}) - res1 = res1[0].findAll('script') + res1 = result.body.find('div', {'class': 'body-content'}) + res1 = res1.find('script') # type: ignore res1 = sub( - r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1[0]), flags=DOTALL + r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1), flags=DOTALL ) res1 = sub('\n\n', '\n', res1)[:-1].split('\n') @@ -65,10 +65,10 @@ def ramazan(message): result = get_result(konum) except BaseException: return edit(message, f'`{konum} için bir bilgi bulunamadı.`') - res1 = result.body.findAll('div', {'class': ['body-content']}) - res1 = res1[0].findAll('script') + res1 = result.body.find('div', {'class': 'body-content'}) + res1 = res1.find('script') res1 = sub( - r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1[0]), flags=DOTALL + r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1), flags=DOTALL ) res1 = sub('\n\n', '\n', res1)[:-1].split('\n') From 28b9eb0dc967f5c64849f606f7b4a7b41d8d00b2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 7 Apr 2023 23:30:54 +0300 Subject: [PATCH 202/242] update pyrogram --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e007581..7dd23bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.102 +pyrogram==2.0.103 pysmartDL python-barcode python-dotenv @@ -30,4 +30,4 @@ speedtest-cli spotipy sqlalchemy tgcrypto -yt-dlp \ No newline at end of file +yt-dlp From b03c5b61ab259c145457ec68dae81f064b632092 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 7 Apr 2023 23:31:49 +0300 Subject: [PATCH 203/242] docker-compose support [WIP] --- docker-compose.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..357b078 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.9' +services: + seden: + init: true + restart: unless-stopped + network_mode: host + build: + context: https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git#seden + env_file: + - config.env + command: python seden.py \ No newline at end of file From 0acde24e908243b57f076a54b028cc8d70023b8b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 7 Apr 2023 23:32:29 +0300 Subject: [PATCH 204/242] Release Seden v1.7.3 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 7bc6d5f..b5f05d6 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -116,7 +116,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.2' +BOT_VERSION = '1.7.3' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 8920aa4feb5dff3d9431189ae405fe2b70b4b138 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 15:50:08 +0300 Subject: [PATCH 205/242] globals.py: fix missing argument --- sedenbot/modules/globals.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 357a0b2..2767883 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -92,8 +92,8 @@ def gban_user(message): return -@sedenify(pattern='^.(ung|gun)ban', compat=False) -def ungban_user(client, message): +@sedenify(pattern='^.(ung|gun)ban') +def ungban_user(message): args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') @@ -139,7 +139,7 @@ def find_member(dialog): return False try: - dialogs = client.get_dialogs() + dialogs = message._client.get_dialogs() me_id = message._client.me.id chats = [ dialog.chat @@ -190,8 +190,8 @@ def gban_check(message): message.continue_propagation() -@sedenify(pattern='^.gmute', compat=False) -def gmute_user(client, message): +@sedenify(pattern='^.gmute') +def gmute_user(message): reply = message.reply_to_message edit(message, f'`{get_translation("muteProcess")}`') @@ -226,7 +226,7 @@ def gmute_user(client, message): ), ) try: - common_chats = client.get_common_chats(user.id) + common_chats = message._client.get_common_chats(user.id) for i in common_chats: i.restrict_member(user.id, permissions=ChatPermissions()) except BaseException: @@ -243,8 +243,8 @@ def gmute_user(client, message): return -@sedenify(pattern='^.(ung|gun)mute', compat=False) -def ungmute_user(client, message): +@sedenify(pattern='^.(ung|gun)mute') +def ungmute_user(message): args = extract_args_split(message) if len(args) > 1: return edit(message, f'`{get_translation("wrongCommand")}`') @@ -268,7 +268,7 @@ def ungmute_user(client, message): return edit(message, f'`{get_translation("alreadyUnmuted")}`') sql2.ungmute(user.id) try: - common_chats = client.get_common_chats(user.id) + common_chats = message._client.get_common_chats(user.id) for i in common_chats: i.unban_member(user.id) except BaseException: From 5b280813699928cb7ee7c1f2a59c6c051b7001fc Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:50:58 +0300 Subject: [PATCH 206/242] update pyrogram version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7dd23bd..29856ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.103 +pyrogram==2.0.106 pysmartDL python-barcode python-dotenv From 52952a8b79ceaee56710cd1d715a9cb4b7995e82 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:54:23 +0300 Subject: [PATCH 207/242] config: update API_ID declaration as integer --- sample_config.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample_config.env b/sample_config.env index 6087461..f3ea2fd 100644 --- a/sample_config.env +++ b/sample_config.env @@ -1,6 +1,6 @@ # Get these from https://my.telegram.org/ # REQUIRED -API_ID='' +API_ID= # int API_HASH='' # Get this value by running python3 session.py locally From 3a38636351f57039f394c316c8905e8ca054ecd6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:55:52 +0300 Subject: [PATCH 208/242] ezanvakti.py: commented out unused function (ramazan) --- sedenbot/modules/ezanvakti.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 3fd71e7..132ef60 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -54,7 +54,7 @@ def get_val(st): edit(message, vakitler) - +""" @sedenify(pattern='^.ramazan') def ramazan(message): konum = extract_args(message).lower() @@ -115,7 +115,7 @@ def get_remaining_time(vakt, current_hour, current_minute): ) edit(message, vakitler) - +""" def find_loc(konum): if konum.isdigit(): From cb604870a89f9a5ed39a28bc46dcc3fef7a8602f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:57:15 +0300 Subject: [PATCH 209/242] info.py: optimize last_seen function --- sedenbot/modules/info.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index ad8eeff..1a3b990 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -105,20 +105,18 @@ def __init__(self, brain, blacklist): self.blacklist = blacklist def last_seen(self, bot, status): + status_list = { + enums.UserStatus.ONLINE: get_translation('statusOnline'), + enums.UserStatus.OFFLINE: get_translation('statusOffline'), + enums.UserStatus.RECENTLY: get_translation('statusRecently'), + enums.UserStatus.LAST_WEEK: get_translation('statusWeek'), + enums.UserStatus.LAST_MONTH: get_translation('statusMonth'), + enums.UserStatus.LONG_AGO: get_translation('statusLong') + } if bot: return 'BOT' - elif status == enums.UserStatus.ONLINE: - return get_translation('statusOnline') - elif status == enums.UserStatus.OFFLINE: - return get_translation('statusOffline') - elif status == enums.UserStatus.RECENTLY: - return get_translation('statusRecently') - elif status == enums.UserStatus.LAST_WEEK: - return get_translation('statusWeek') - elif status == enums.UserStatus.LAST_MONTH: - return get_translation('statusMonth') - elif status == enums.UserStatus.LONG_AGO: - return get_translation('statusLong') + elif status in status_list: + return status_list[status] def sudo_check(self, user_id): if user_id in self.brain: From ec92e7e7a8d04139a621764646ff2ae33c11c48a Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:58:00 +0300 Subject: [PATCH 210/242] optimize .seden command --- sedenbot/modules/seden.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index aa080fe..0dff650 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -8,23 +8,25 @@ # from collections import OrderedDict +from re import match from sedenbot import HELP from sedenecem.core import edit, extract_args, get_translation, reply, sedenify @sedenify(pattern='^.(seden|help)') -def seden(message): - seden = extract_args(message).lower() +def seden_cmds(message): + args = extract_args(message).lower() cmds = OrderedDict(sorted(HELP.items())) - if len(seden) > 0: - if seden in cmds: - edit(message, str(cmds[seden])) - else: - edit(message, f'**{get_translation("sedenUsage")}**') - else: + + if not args: edit(message, get_translation('sedenUsage2', ['**', '`'])) metin = f'{get_translation("sedenShowLoadedModules", ["**", "`", len(cmds)])}\n' - for item in cmds: - metin += f'• `{item}`\n' - reply(message, metin) + metin += '\n'.join([f'• `{item}`' for item in cmds]) + return reply(message, metin) + + matching_cmds = [cmd for cmd in cmds if match(args, cmd)] + if matching_cmds: + edit(message, str(cmds[matching_cmds[0]])) + else: + edit(message, f'**{get_translation("sedenUsage")}**') \ No newline at end of file From 398e2e7bfceaab53981b42aaff259424acc25b99 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:58:46 +0300 Subject: [PATCH 211/242] update translations --- sedenecem/translator/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 744acee..f2d0494 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -413,7 +413,7 @@ "replyMessage": "Reply to a message.", "replySticker": "Reply a sticker.", "reqDown": "%1Filename:%1 %2%3%2\n%1Downloading:%1 %2%%4%2", - "restart": "BRB\u2026 *PornHub intro*", + "restart": "Restarting\u2026", "restartLog": "#RESTART\nBot restarted", "reverseError": "Unsupported type", "reverseError2": "I couldn't find anything for your ugly ass.", @@ -502,7 +502,7 @@ "sedenVersion": "Bot version; Seden v%1", "shippingMovements": "\n\n%1%3Last movement%4%2\n\n%5Unit: %7\nStatus: %8\nDate: %9\nTime: %10\nAction: %11%6", "shippingNoResult": "Tracking information not found!", - "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sended Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4", + "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sent Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4", "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", "showWelcome": "I'm currently welcoming new users with this welcome message", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", From f896bf3e0e16a9bb3ee053a94af2cd88755b8af6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 17:59:22 +0300 Subject: [PATCH 212/242] session.py: optimize functions --- session.py | 63 ++++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/session.py b/session.py index 17cb3f8..a060e5c 100644 --- a/session.py +++ b/session.py @@ -19,17 +19,14 @@ def select_lang(): lang = '' - while not lang: - lang = input('Select lang (tr, en): ') - - while lang.lower() not in ('tr', 'en'): - lang = input('Select lang (tr, en): ') + while lang not in ('tr', 'en'): + lang = input('Select lang (tr, en): ').lower() return lang def get_api_hash(): API_ID = environ.get('API_ID', '') - API_HASH = environ.get('API_HASH', '') + API_HASH = environ.get('API_HASH', '') while not API_ID.isdigit() or len(API_ID) < 5 or len(API_ID) > 8: API_ID = input('API ID: ') @@ -49,26 +46,6 @@ def get_session(): Click on API Development Tools Create a new application, by entering the required details\n''' ) - API_ID, API_HASH = get_api_hash() - app = Client('sedenify', api_id=API_ID, api_hash=API_HASH) - with app: - self = app.get_me() - session = app.export_session_string() - out = f'''**Hi [{self.first_name}](tg://user?id={self.id}) -\nAPI_ID:** `{API_ID}` -\n**API_HASH:** `{API_HASH}` -\n**SESSION:** `{session}` -\n**NOTE: Don't give your account information to others!**''' - out2 = 'Session successfully generated!' - if self.is_bot: - print(f'{session}\n{out2}') - else: - app.send_message('me', out) - print( - '''Session successfully generated! -Please check your Telegram Saved Messages''' - ) - elif lang == 'tr': print( '''Lütfen my.telegram.org adresine gidin @@ -76,21 +53,37 @@ def get_session(): API Development Tools kısmına tıklayın Gerekli ayrıntıları girerek yeni bir uygulama oluşturun\n''' ) - API_ID, API_HASH = get_api_hash() - app = Client('sedenify', api_id=API_ID, api_hash=API_HASH) - with app: - self = app.get_me() - session = app.export_session_string() + + API_ID, API_HASH = get_api_hash() + app = Client('sedenify', api_id=API_ID, api_hash=API_HASH) + with app: + self = app.get_me() + session = app.export_session_string() + if lang == 'en': + out = f'''**Hi [{self.first_name}](tg://user?id={self.id}) +\nAPI_ID:** `{API_ID}` +\n**API_HASH:** `{API_HASH}` +\n**SESSION:** `{session}` +\n**NOTE: Don't give your account information to others!**''' + out2 = 'Session successfully generated!' + elif lang == 'tr': out = f'''**Merhaba [{self.first_name}](tg://user?id={self.id}) \nAPI_ID:** `{API_ID}` \n**API_HASH:** `{API_HASH}` \n**SESSION:** `{session}` \n**NOT: Hesap bilgileriniz başkalarına vermeyin!**''' out2 = 'Session başarıyla oluşturuldu!' - if self.is_bot: - print(f'{session}\n{out2}') - else: - app.send_message('me', out) + + if self.is_bot: + print(f'{session}\n{out2}') + else: + app.send_message('me', out) + if lang == 'en': + print( + '''Session successfully generated! +Please check your Telegram Saved Messages''' + ) + elif lang == 'tr': print( '''Session başarıyla oluşturuldu! Lütfen Telegram Kayıtlı Mesajlarınızı kontrol edin.''' From 30c423552843a27958675b8f77b86a0232525f1e Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 18:00:09 +0300 Subject: [PATCH 213/242] Release Seden v1.7.4 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index b5f05d6..c779646 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -116,7 +116,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.3' +BOT_VERSION = '1.7.4' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 97014badaef7d7659790a339e6fc62798da8a002 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Wed, 31 May 2023 18:17:55 +0300 Subject: [PATCH 214/242] optimized logger configuration by setting critical level for specific loggers --- sedenbot/__init__.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index c779646..377a515 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -88,19 +88,17 @@ def unset_local_env(key: str): def set_logger(): - # Turns off out printing Session value - pyrogram_syncer = getLogger('pyrogram.syncer') - pyrogram_syncer.setLevel(CRITICAL) - - # Closes some junk outputs - pyrogram_session = getLogger('pyrogram.session.session') - pyrogram_session.setLevel(CRITICAL) - - pyrogram_auth = getLogger('pyrogram.session.auth') - pyrogram_auth.setLevel(CRITICAL) + loggers = [ + 'pyrogram.syncer', + 'pyrogram.session.session', + 'pyrogram.session.auth', + 'asyncio', + ] + level = CRITICAL - asyncio = getLogger('asyncio') - asyncio.setLevel(CRITICAL) + for logger_name in loggers: + logger = getLogger(logger_name) + logger.setLevel(level) set_logger() From 2620e57af6cc30bb99dceb456807a532ae96fe43 Mon Sep 17 00:00:00 2001 From: fatihesergg <fatihesergg@gmail.com> Date: Fri, 30 Jun 2023 14:54:00 +0300 Subject: [PATCH 215/242] docker-compose: [WIP] Add postgresql image for fast-easy deploy on local machine. --- docker-compose.yml | 13 ++++++++++++- sample_config.env | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 357b078..6627e69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,16 @@ version: '3.9' services: + postgres: + container_name: postgres + image: postgres:latest + environment: + - POSTGRES_USER=sedenuserbot + - POSTGRES_PASSWORD=TeamDerUnterGang + - POSTGRES_DB=userbotdb + ports: + - "5432:5432" + restart: always + seden: init: true restart: unless-stopped @@ -7,5 +18,5 @@ services: build: context: https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git#seden env_file: - - config.env + - ./config.env command: python seden.py \ No newline at end of file diff --git a/sample_config.env b/sample_config.env index f3ea2fd..c3fd9ec 100644 --- a/sample_config.env +++ b/sample_config.env @@ -8,8 +8,9 @@ API_HASH='' SESSION='' # Your Database URL +# Leave default if you deploy on local machine otherwise enter the url by following format. # Example: 'postgresql://username:passwd@localhost:5432/db_name' -DATABASE_URL='' +DATABASE_URL='postgresql://sedenuserbot:TeamDerUnterGang@localhost:5432/userbotdb' # Chat ID for Log Group LOG_ID='' From bb1844f3f4fd190e981ada586b73bc5777b648b6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Mon, 31 Jul 2023 19:01:52 +0300 Subject: [PATCH 216/242] improve some funcs --- sedenbot/modules/scrapers.py | 49 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index befc932..4fa6d51 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -18,6 +18,7 @@ from bs4 import BeautifulSoup from emoji import demojize +import emoji from googletrans import LANGUAGES, Translator from gtts import gTTS from gtts.lang import tts_langs @@ -540,16 +541,31 @@ def doviz(message): headers={'User-Agent': useragent()}, ) page = BeautifulSoup(req.content, 'html.parser') - res = page.find_all('div', {'class', 'item'}) + res = page.find_all('div', {'class': 'item'}) out = '**Güncel döviz kurları:**\n\n' - for i in res: - name = i.find('span', {'class': 'name'}).text - value = i.find('span', {'class': 'value'}).text - changes = i.find('span', {'data-socket-attr': 'a'}).text - out += f'`•` **{name}:** `{value}` // `{changes}`\n' + + for item in res: + name = item.find('span', {'class': 'name'}).text + value = item.find('span', {'class': 'value'}).text + + rate_elem = item.find('div', {'class': ['change-rate status down', 'change-rate status up']}) + rate_class = rate_elem['class'][-1] if rate_elem else None + + changes_emoji = '' + if rate_class == 'down': + changes_emoji = '⬇️' + elif rate_class == 'up': + changes_emoji = '⬆️' + + if changes_emoji: + out += f'{changes_emoji} **{name}:** `{value}`\n' + else: + out += f'**{name}:** `{value}`\n' + edit(message, out) + @sedenify(pattern='^.imei(|check)') def imeichecker(message): imei = extract_args(message) @@ -568,20 +584,23 @@ def imeichecker(message): break _marka = findall(r'Marka:(.+) Model', result['markaModel']) _model = findall(r'Model Bilgileri:(.+)', result['markaModel']) + _pazaradi = findall(r'Pazar Adı:(.+) Marka', result['markaModel']) marka = ( - _marka[0].replace(',', '').strip() if _marka else "Marka Bilgisi Bulunamadi" + _marka[0].replace(',', '').strip() if _marka else None ) model = ( - _model[0].replace(',', '').strip() if _model else "Model Bilgisi Bulunamadi" + _model[0].replace(',', '').strip() if _model else None ) - reply_text = ( - f"<b>Sorgu Tarihi</b> ⇾ <code>{result['sorguTarihi']}</code>\n" - f"<b>IMEI</b> ⇾ <code>{result['imei'][:-5]+5*'*'}</code>\n" - f"<b>Durum</b> ⇾ <code>{result['durum']}</code>\n" - f"<b>Marka</b> ⇾ <code>{marka}</code>\n" - f"<b>Model</b> ⇾ <code>{model}</code>\n\n" - f"<b>Powered by</b> ⇾ <a href='https://github.com/TeamDerUntergang/Telegram-SedenUserBot'>Seden ♥</a>\n" + pazaradi = ( + _pazaradi[0].replace(',', '').strip() if _pazaradi else None ) + reply_text = f"<b>Sorgu Tarihi:</b> <code>{result['sorguTarihi']}</code>\n\n" + reply_text += f"<b>IMEI:</b> <code>{result['imei'][:-5]+5*'*'}</code>\n" + reply_text += f"<b>Durum:</b> <code>{result['durum']}</code>\n" + reply_text += f"<b>Pazar Adı:</b> <code>{pazaradi}</code>\n" if pazaradi is not None else "" + reply_text += f"<b>Marka:</b> <code>{marka}</code>\n" if marka is not None else "" + reply_text += f"<b>Model:</b> <code>{model}</code>\n\n" if model is not None else "" + edit(message, reply_text, parse=enums.parse_mode.ParseMode.HTML, preview=False) except Exception as e: raise e From 909c81cc116bc0f82f49198aa08ef8867b7cb1c2 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Mon, 31 Jul 2023 19:03:52 +0300 Subject: [PATCH 217/242] typo fix --- docker-compose.yml | 2 +- sample_config.env | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6627e69..ec73a42 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: image: postgres:latest environment: - POSTGRES_USER=sedenuserbot - - POSTGRES_PASSWORD=TeamDerUnterGang + - POSTGRES_PASSWORD=TeamDerUntergang - POSTGRES_DB=userbotdb ports: - "5432:5432" diff --git a/sample_config.env b/sample_config.env index c3fd9ec..fb63f20 100644 --- a/sample_config.env +++ b/sample_config.env @@ -10,7 +10,7 @@ SESSION='' # Your Database URL # Leave default if you deploy on local machine otherwise enter the url by following format. # Example: 'postgresql://username:passwd@localhost:5432/db_name' -DATABASE_URL='postgresql://sedenuserbot:TeamDerUnterGang@localhost:5432/userbotdb' +DATABASE_URL='postgresql://sedenuserbot:TeamDerUntergang@localhost:5432/userbotdb' # Chat ID for Log Group LOG_ID='' @@ -75,4 +75,4 @@ LASTFM_PASSWORD='' # Last.fm Password # SpamWatch module # Get from https://t.me/SpamWatchBot?start=token -SPAMWATCH_KEY='' +SPAMWATCH_KEY='' \ No newline at end of file From 90f103cdbbaa84d85b844847ef34126e30f2ef31 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:20:12 +0300 Subject: [PATCH 218/242] Release Seden v1.7.5 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 377a515..e4d9083 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -114,7 +114,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.4' +BOT_VERSION = '1.7.5' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From bd0a388588743577e181edcca0412f45005c5ea7 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:21:07 +0300 Subject: [PATCH 219/242] improve speedtest module --- sedenbot/modules/speedtest.py | 109 +++++++++++++++++----------------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index 2074d24..67397f1 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -7,6 +7,7 @@ # All rights reserved. See COPYING, AUTHORS. # + from datetime import datetime from sedenbot import HELP @@ -15,82 +16,80 @@ from speedtest import Speedtest +def convert_bytes_to_human_readable(size_in_bytes): + power = 1024 + size = size_in_bytes + units = ['bytes', 'kilobytes', 'megabytes', 'gigabytes', 'terabytes'] + + for unit in units: + if size < power: + return f'{size:.2f} {unit}' + size /= power + + return f'{size:.2f} {units[-1]}' + + @sedenify(pattern='^.speedtest') def speed_test(message): - input_str = extract_args(message) - as_text = False - if input_str == 'text': - as_text = True + as_text = extract_args(message) == 'text' edit(message, f'`{get_translation("speedtest")}`') - start = datetime.now() + start_time = datetime.now() spdtst = Speedtest() spdtst.get_best_server() spdtst.download() spdtst.upload() - end = datetime.now() - ms = (end - start).microseconds / 1000 - response = spdtst.results.dict() - download_speed = response.get('download') - upload_speed = response.get('upload') - ping_time = response.get('ping') - client_infos = response.get('client') - i_s_p = client_infos.get('isp') - i_s_p_rating = client_infos.get('isprating') + end_time = datetime.now() + elapsed_seconds = int((end_time - start_time).total_seconds()) + + results = spdtst.results.dict() + download_speed = results['download'] + upload_speed = results['upload'] + ping_time = int(results['ping']) + client_info = results['client'] + isp_name = client_info['isp'] + isp_rating = client_info['isprating'] + try: response = spdtst.results.share() speedtest_image = response if as_text: - edit( - message, - get_translation( - 'speedtestResultText', - [ - '**', - ms, - convert_from_bytes(download_speed), - convert_from_bytes(upload_speed), - ping_time, - i_s_p, - i_s_p_rating, - '', - ], - ), + result_text = get_translation( + 'speedtestResultText', + [ + '**', + elapsed_seconds, + convert_bytes_to_human_readable(download_speed), + convert_bytes_to_human_readable(upload_speed), + ping_time, + isp_name, + isp_rating, + '', + ], ) + edit(message, result_text) else: reply_img( message, speedtest_image, - caption=get_translation('speedtestResultDoc', ['**', ms]), + caption=get_translation('speedtestResultDoc', ['**', elapsed_seconds]), delete_file=True, delete_orig=True, ) except Exception as exc: - edit( - message, - get_translation( - 'speedtestResultText', - [ - '**', - ms, - convert_from_bytes(download_speed), - convert_from_bytes(upload_speed), - ping_time, - i_s_p, - i_s_p_rating, - f'ERROR: {str(exc)}', - ], - ), + error_result_text = get_translation( + 'speedtestResultText', + [ + '**', + elapsed_seconds, + convert_bytes_to_human_readable(download_speed), + convert_bytes_to_human_readable(upload_speed), + ping_time, + isp_name, + isp_rating, + f'ERROR: {str(exc)}', + ], ) - - -def convert_from_bytes(size): - power = 2 ** 10 - _ = 0 - units = {0: '', 1: 'kilobytes', 2: 'megabytes', 3: 'gigabytes', 4: 'terabytes'} - while size > power: - size /= power - _ += 1 - return f'{round(size, 2)} {units[_]}' + edit(message, error_result_text) HELP.update({'speedtest': get_translation('speedtestInfo')}) From cb6888529be02d895c207ab3fde8acdd8ae10354 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:21:16 +0300 Subject: [PATCH 220/242] improve weather module --- sedenbot/modules/weather.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index d513f00..fe509c2 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -12,10 +12,7 @@ from sedenecem.core import edit, extract_args, get_translation, sedenify # ===== CONSTANT ===== -if WEATHER: - DEFCITY = WEATHER.capitalize() -else: - DEFCITY = None +DEFAULT_CITY = WEATHER.capitalize() if WEATHER else None # ==================== @@ -23,27 +20,16 @@ def weather(message): args = extract_args(message).capitalize() - if len(args) < 1: - CITY = DEFCITY - if not CITY: - edit(message, f'`{get_translation("weatherErrorCity")}`') - return - else: - CITY = args - - if ',' in CITY: - CITY = CITY[: CITY.find(',')].strip() + CITY = DEFAULT_CITY if len(args) < 1 else args.split(',')[0].strip() try: - req = get( + response = get( f'http://wttr.in/{CITY}?mQT0', headers={'User-Agent': 'curl/7.66.0', 'Accept-Language': SEDEN_LANG}, ) - data = req.text - if '===' in data: + data = response.text + if '===' in data or '404' in data: raise Exception - if '404' in data: - return edit(message, f'`{get_translation("weatherErrorServer")}`') data = data.replace('`', '‛') edit(message, f'**{CITY}**\n\n`{data}`') except Exception: From fb5e7b0d2cf173154c0fa8bebfce85f9e86de310 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:21:39 +0300 Subject: [PATCH 221/242] Release Seden v1.7.6 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index e4d9083..4d13992 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -114,7 +114,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.5' +BOT_VERSION = '1.7.6' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From afba990bfca186a60531c6990a070df4ef49e46f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:25:27 +0300 Subject: [PATCH 222/242] remove unused import --- sedenbot/modules/scrapers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 4fa6d51..248af18 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -18,7 +18,6 @@ from bs4 import BeautifulSoup from emoji import demojize -import emoji from googletrans import LANGUAGES, Translator from gtts import gTTS from gtts.lang import tts_langs From 850b9090efe85d7ad8f7985a2b6116941ecdf2b5 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 1 Aug 2023 00:25:44 +0300 Subject: [PATCH 223/242] Release Seden v1.7.7 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 4d13992..1d986e1 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -114,7 +114,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.6' +BOT_VERSION = '1.7.7' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From e8fb37be62d6166c75a3a3d81d4351cdec831b4f Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 21:23:01 +0300 Subject: [PATCH 224/242] update license --- seden.py | 2 +- seden_translate_sorter.py | 2 +- sedenbot/__init__.py | 2 +- sedenbot/modules/afk.py | 2 +- sedenbot/modules/android.py | 2 +- sedenbot/modules/autopp.py | 2 +- sedenbot/modules/ban.py | 2 +- sedenbot/modules/blacklist.py | 2 +- sedenbot/modules/chat.py | 2 +- sedenbot/modules/colors.py | 2 +- sedenbot/modules/deepfry.py | 2 +- sedenbot/modules/direct_link.py | 2 +- sedenbot/modules/ecem.py | 2 +- sedenbot/modules/effects.py | 2 +- sedenbot/modules/env.py | 2 +- sedenbot/modules/exif.py | 2 +- sedenbot/modules/ezanvakti.py | 2 +- sedenbot/modules/filters.py | 2 +- sedenbot/modules/gdrive.py | 2 +- sedenbot/modules/git.py | 2 +- sedenbot/modules/globals.py | 2 +- sedenbot/modules/horeke.py | 2 +- sedenbot/modules/info.py | 2 +- sedenbot/modules/kargotakip.py | 2 +- sedenbot/modules/lastfm.py | 2 +- sedenbot/modules/locks.py | 2 +- sedenbot/modules/lyrics.py | 2 +- sedenbot/modules/memes.py | 2 +- sedenbot/modules/misc.py | 2 +- sedenbot/modules/notes.py | 2 +- sedenbot/modules/ocr.py | 2 +- sedenbot/modules/paste.py | 2 +- sedenbot/modules/pmpermit.py | 2 +- sedenbot/modules/profile.py | 2 +- sedenbot/modules/purge.py | 2 +- sedenbot/modules/qrcode.py | 2 +- sedenbot/modules/quotly.py | 2 +- sedenbot/modules/remove_bg.py | 2 +- sedenbot/modules/reverse.py | 2 +- sedenbot/modules/rgb.py | 2 +- sedenbot/modules/scrapers.py | 2 +- sedenbot/modules/screencapture.py | 2 +- sedenbot/modules/sed.py | 2 +- sedenbot/modules/seden.py | 2 +- sedenbot/modules/snips.py | 2 +- sedenbot/modules/spammer.py | 2 +- sedenbot/modules/spamwatch.py | 2 +- sedenbot/modules/speedtest.py | 2 +- sedenbot/modules/spotify_api.py | 2 +- sedenbot/modules/stickers.py | 2 +- sedenbot/modules/system.py | 2 +- sedenbot/modules/updater.py | 2 +- sedenbot/modules/updown.py | 2 +- sedenbot/modules/weather.py | 2 +- sedenbot/modules/youtubedl.py | 2 +- sedenecem/core/__init__.py | 2 +- sedenecem/core/conv.py | 2 +- sedenecem/core/filters.py | 2 +- sedenecem/core/image.py | 2 +- sedenecem/core/misc.py | 2 +- sedenecem/core/proxy.py | 2 +- sedenecem/core/replier.py | 2 +- sedenecem/core/sedenify.py | 2 +- sedenecem/core/sedenlog.py | 2 +- sedenecem/core/send.py | 2 +- sedenecem/core/webdriver.py | 2 +- sedenecem/translator/__init__.py | 2 +- session.py | 2 +- 68 files changed, 68 insertions(+), 68 deletions(-) diff --git a/seden.py b/seden.py index 469fd68..3267c43 100644 --- a/seden.py +++ b/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/seden_translate_sorter.py b/seden_translate_sorter.py index b7beb4b..1591816 100644 --- a/seden_translate_sorter.py +++ b/seden_translate_sorter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 1d986e1..935599f 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/afk.py index 675f0c5..fd30fcc 100644 --- a/sedenbot/modules/afk.py +++ b/sedenbot/modules/afk.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/android.py b/sedenbot/modules/android.py index 64dfb6e..d7b0199 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/android.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/autopp.py index 3130491..95b60ae 100644 --- a/sedenbot/modules/autopp.py +++ b/sedenbot/modules/autopp.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/ban.py index 52f5b2c..1fb73bc 100644 --- a/sedenbot/modules/ban.py +++ b/sedenbot/modules/ban.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/blacklist.py index b17b233..e39a40f 100644 --- a/sedenbot/modules/blacklist.py +++ b/sedenbot/modules/blacklist.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat.py index 492a031..c737876 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/colors.py b/sedenbot/modules/colors.py index 985616e..cb40a4e 100644 --- a/sedenbot/modules/colors.py +++ b/sedenbot/modules/colors.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/deepfry.py index 8459126..69fd444 100644 --- a/sedenbot/modules/deepfry.py +++ b/sedenbot/modules/deepfry.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/direct_link.py index dbe8174..fcbf869 100644 --- a/sedenbot/modules/direct_link.py +++ b/sedenbot/modules/direct_link.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ecem.py b/sedenbot/modules/ecem.py index a3e543a..10cd58e 100644 --- a/sedenbot/modules/ecem.py +++ b/sedenbot/modules/ecem.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/effects.py index 7a7f22f..560a03c 100644 --- a/sedenbot/modules/effects.py +++ b/sedenbot/modules/effects.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/env.py b/sedenbot/modules/env.py index 0418b1e..fc4e057 100644 --- a/sedenbot/modules/env.py +++ b/sedenbot/modules/env.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/exif.py index a4a4419..a7f726a 100644 --- a/sedenbot/modules/exif.py +++ b/sedenbot/modules/exif.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/ezanvakti.py index 132ef60..1d36317 100644 --- a/sedenbot/modules/ezanvakti.py +++ b/sedenbot/modules/ezanvakti.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/filters.py index 9c1cc17..11f48d9 100644 --- a/sedenbot/modules/filters.py +++ b/sedenbot/modules/filters.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/gdrive.py index a49ed8b..0c0e4dd 100644 --- a/sedenbot/modules/gdrive.py +++ b/sedenbot/modules/gdrive.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/git.py b/sedenbot/modules/git.py index f4b72df..f7d126e 100644 --- a/sedenbot/modules/git.py +++ b/sedenbot/modules/git.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/globals.py index 2767883..4b7befd 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/globals.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/horeke.py index b572025..f821adc 100644 --- a/sedenbot/modules/horeke.py +++ b/sedenbot/modules/horeke.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/info.py b/sedenbot/modules/info.py index 1a3b990..a933cc2 100644 --- a/sedenbot/modules/info.py +++ b/sedenbot/modules/info.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/kargotakip.py index 63a1a0f..edf4d56 100644 --- a/sedenbot/modules/kargotakip.py +++ b/sedenbot/modules/kargotakip.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/lastfm.py b/sedenbot/modules/lastfm.py index 645e19b..a943358 100644 --- a/sedenbot/modules/lastfm.py +++ b/sedenbot/modules/lastfm.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/locks.py index 128c819..d720fab 100644 --- a/sedenbot/modules/locks.py +++ b/sedenbot/modules/locks.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/lyrics.py index 029bbf5..a506f24 100644 --- a/sedenbot/modules/lyrics.py +++ b/sedenbot/modules/lyrics.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/memes.py index cb63ba3..687c454 100644 --- a/sedenbot/modules/memes.py +++ b/sedenbot/modules/memes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/misc.py index a397fce..e9a7283 100644 --- a/sedenbot/modules/misc.py +++ b/sedenbot/modules/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/notes.py index d4152d0..56371f9 100644 --- a/sedenbot/modules/notes.py +++ b/sedenbot/modules/notes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/ocr.py index eec6163..061f113 100644 --- a/sedenbot/modules/ocr.py +++ b/sedenbot/modules/ocr.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/paste.py b/sedenbot/modules/paste.py index 6984c35..646d02f 100644 --- a/sedenbot/modules/paste.py +++ b/sedenbot/modules/paste.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/pmpermit.py index 9c49b39..29b5f8d 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/pmpermit.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/profile.py index 5cc3622..57b0678 100644 --- a/sedenbot/modules/profile.py +++ b/sedenbot/modules/profile.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/purge.py index 98d5b46..a05a728 100644 --- a/sedenbot/modules/purge.py +++ b/sedenbot/modules/purge.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/qrcode.py index 058ade2..a726c4b 100644 --- a/sedenbot/modules/qrcode.py +++ b/sedenbot/modules/qrcode.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/quotly.py b/sedenbot/modules/quotly.py index 51c5091..960e07a 100644 --- a/sedenbot/modules/quotly.py +++ b/sedenbot/modules/quotly.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/remove_bg.py index 20a48ff..7a46b1a 100644 --- a/sedenbot/modules/remove_bg.py +++ b/sedenbot/modules/remove_bg.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/reverse.py index b5662f2..ccb6142 100644 --- a/sedenbot/modules/reverse.py +++ b/sedenbot/modules/reverse.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/rgb.py index e018563..8e3f17f 100644 --- a/sedenbot/modules/rgb.py +++ b/sedenbot/modules/rgb.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 248af18..9c26fe3 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/screencapture.py index 8cf0148..0d6e75b 100644 --- a/sedenbot/modules/screencapture.py +++ b/sedenbot/modules/screencapture.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/sed.py index 1cba5f1..b0ff555 100644 --- a/sedenbot/modules/sed.py +++ b/sedenbot/modules/sed.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/seden.py index 0dff650..1c13aa7 100644 --- a/sedenbot/modules/seden.py +++ b/sedenbot/modules/seden.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/snips.py index 3d9fec5..dd1ec5a 100644 --- a/sedenbot/modules/snips.py +++ b/sedenbot/modules/snips.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/spammer.py index cfab721..c3ecda4 100644 --- a/sedenbot/modules/spammer.py +++ b/sedenbot/modules/spammer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/spamwatch.py index 8e1080b..8821458 100644 --- a/sedenbot/modules/spamwatch.py +++ b/sedenbot/modules/spamwatch.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/speedtest.py index 67397f1..477c978 100644 --- a/sedenbot/modules/speedtest.py +++ b/sedenbot/modules/speedtest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/spotify_api.py index f0154c1..bf20e06 100644 --- a/sedenbot/modules/spotify_api.py +++ b/sedenbot/modules/spotify_api.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/stickers.py index e23cefd..74ab1ae 100644 --- a/sedenbot/modules/stickers.py +++ b/sedenbot/modules/stickers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/system.py b/sedenbot/modules/system.py index 02c2fcb..23f3e18 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/system.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/updater.py index 0c15823..63510cc 100644 --- a/sedenbot/modules/updater.py +++ b/sedenbot/modules/updater.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/updown.py index 42aa4c8..b32665f 100644 --- a/sedenbot/modules/updown.py +++ b/sedenbot/modules/updown.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/weather.py index fe509c2..0213f91 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/weather.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/youtubedl.py index 371e855..d0add78 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/youtubedl.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # Copyright (C) 2021 kisekinopureya <https://github.com/kisekinopureya> # # This file is part of TeamDerUntergang project, diff --git a/sedenecem/core/__init__.py b/sedenecem/core/__init__.py index 1aeeff4..cf0bd35 100644 --- a/sedenecem/core/__init__.py +++ b/sedenecem/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/conv.py b/sedenecem/core/conv.py index e49eb87..b763a53 100644 --- a/sedenecem/core/conv.py +++ b/sedenecem/core/conv.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/filters.py b/sedenecem/core/filters.py index 98aeb83..6181214 100644 --- a/sedenecem/core/filters.py +++ b/sedenecem/core/filters.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/image.py b/sedenecem/core/image.py index 4431cfa..2f9f52b 100644 --- a/sedenecem/core/image.py +++ b/sedenecem/core/image.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index 36b920a..ee0013f 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/proxy.py b/sedenecem/core/proxy.py index 5255cbb..09a21e0 100644 --- a/sedenecem/core/proxy.py +++ b/sedenecem/core/proxy.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index 2151a26..cbd2e9e 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/sedenify.py b/sedenecem/core/sedenify.py index 1534121..ba67f61 100644 --- a/sedenecem/core/sedenify.py +++ b/sedenecem/core/sedenify.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/sedenlog.py b/sedenecem/core/sedenlog.py index 779733f..615e825 100644 --- a/sedenecem/core/sedenlog.py +++ b/sedenecem/core/sedenlog.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/send.py b/sedenecem/core/send.py index e1bdd96..406532d 100644 --- a/sedenecem/core/send.py +++ b/sedenecem/core/send.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/core/webdriver.py b/sedenecem/core/webdriver.py index dbcbf03..edd343e 100644 --- a/sedenecem/core/webdriver.py +++ b/sedenecem/core/webdriver.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/sedenecem/translator/__init__.py b/sedenecem/translator/__init__.py index f32076a..8ef1003 100644 --- a/sedenecem/translator/__init__.py +++ b/sedenecem/translator/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. diff --git a/session.py b/session.py index a060e5c..4da9b3e 100644 --- a/session.py +++ b/session.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 TeamDerUntergang <https://github.com/TeamDerUntergang> +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> # # This file is part of TeamDerUntergang project, # and licensed under GNU Affero General Public License v3. From ec89e47038a604f8a417a853ee5cae4769282ab4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 21:38:45 +0300 Subject: [PATCH 225/242] remove distutils import * We made this change because Python 3.12 deprecate distutils module. --- sedenbot/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 935599f..0a2ef22 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -8,7 +8,6 @@ # -from distutils.util import strtobool as sb from importlib import import_module from logging import CRITICAL, DEBUG, INFO, basicConfig, getLogger from os import environ, listdir, path, remove @@ -68,7 +67,7 @@ def get_translation(transKey, params: list = None): TEMP_SETTINGS['PM_LAST_MSG'] = {} # Console verbose logging -LOG_VERBOSE = sb(environ.get('LOG_VERBOSE', 'False')) +LOG_VERBOSE = bool(environ.get('LOG_VERBOSE', 'False')) basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -184,7 +183,7 @@ def set_logger(): del _LOG_ID # PmPermit PM Auto Ban Stuffs -PM_AUTO_BAN = sb(environ.get('PM_AUTO_BAN', 'False')) +PM_AUTO_BAN = bool(environ.get('PM_AUTO_BAN', 'False')) _PM_MSG_COUNT = environ.get('PM_MSG_COUNT', 'default') PM_MSG_COUNT = int(_PM_MSG_COUNT) if _PM_MSG_COUNT.isdigit() else 5 del _PM_MSG_COUNT From 9587a34f4f1b23a9c465eb1357c8076719f85eb4 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 21:39:18 +0300 Subject: [PATCH 226/242] Release Seden v1.7.8 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 0a2ef22..5563007 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -113,7 +113,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.7' +BOT_VERSION = '1.7.8' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From cbf7e3c095bb72a681cf9b504667393da09ea5c0 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 21:53:47 +0300 Subject: [PATCH 227/242] scrapers: fix index error in urbandictionary module --- sedenbot/modules/scrapers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 9c26fe3..71aaa11 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -332,7 +332,8 @@ def urbandictionary(message): response = get(f'https://api.urbandictionary.com/v0/define?term={query}') data = loads(response.text) if len(data["list"]): - item = data['list'][randrange(9)] + list_size = len(data['list']) + item = data['list'][randrange(list_size)] meanlen = item['definition'] + item['example'] if len(meanlen) >= 4096: edit(message, f'`{get_translation("outputTooLarge")}`') From be41e52e5aca7ff1c6da2d0d16cf94cff4006b8a Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 22:09:06 +0300 Subject: [PATCH 228/242] scrapers: improve imeicheck function --- sedenbot/modules/scrapers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py index 71aaa11..256b6a1 100644 --- a/sedenbot/modules/scrapers.py +++ b/sedenbot/modules/scrapers.py @@ -597,6 +597,7 @@ def imeichecker(message): reply_text = f"<b>Sorgu Tarihi:</b> <code>{result['sorguTarihi']}</code>\n\n" reply_text += f"<b>IMEI:</b> <code>{result['imei'][:-5]+5*'*'}</code>\n" reply_text += f"<b>Durum:</b> <code>{result['durum']}</code>\n" + reply_text += f"<b>Kaynak:</b> <code>{result['kaynak']}</code>\n" reply_text += f"<b>Pazar Adı:</b> <code>{pazaradi}</code>\n" if pazaradi is not None else "" reply_text += f"<b>Marka:</b> <code>{marka}</code>\n" if marka is not None else "" reply_text += f"<b>Model:</b> <code>{model}</code>\n\n" if model is not None else "" From 5a08791d654691f1f32a54849fcb99b0f51dfc13 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 8 Feb 2024 22:09:17 +0300 Subject: [PATCH 229/242] Release Seden v1.7.9 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 5563007..a7f5381 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -113,7 +113,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.8' +BOT_VERSION = '1.7.9' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 9dfc84bcf6aba74629ae9d07da94fd6229166b0d Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 8 Mar 2024 21:56:17 +0300 Subject: [PATCH 230/242] major changes * Switched from Pyrogram to Pyrofork * Implemented modules folder structure * Added kernelsu command * Fixed bugs * Improved functions --- requirements.txt | 2 +- sedenbot/__init__.py | 28 +- sedenbot/modules/{ => admin}/ecem.py | 0 sedenbot/modules/{ => admin}/env.py | 2 +- sedenbot/modules/{ => admin}/seden.py | 0 sedenbot/modules/{ => admin}/system.py | 2 +- sedenbot/modules/{ => admin}/updater.py | 0 sedenbot/modules/{ => admin}/updown.py | 0 sedenbot/modules/{ => chat}/afk.py | 0 sedenbot/modules/{ => chat}/ban.py | 0 sedenbot/modules/{ => chat}/blacklist.py | 0 sedenbot/modules/{ => chat}/chat.py | 4 +- sedenbot/modules/{ => chat}/filters.py | 0 sedenbot/modules/{ => chat}/globals.py | 2 +- sedenbot/modules/{ => chat}/locks.py | 0 sedenbot/modules/{ => chat}/notes.py | 0 sedenbot/modules/{ => chat}/pmpermit.py | 2 +- sedenbot/modules/{ => chat}/profile.py | 0 sedenbot/modules/{ => chat}/purge.py | 0 sedenbot/modules/{ => chat}/snips.py | 0 sedenbot/modules/{ => chat}/spamwatch.py | 0 sedenbot/modules/{ => chat}/stickers.py | 0 sedenbot/modules/fun/carbon_text.py | 124 ++++ sedenbot/modules/{ => fun}/colors.py | 0 sedenbot/modules/{ => fun}/deepfry.py | 0 sedenbot/modules/{ => fun}/effects.py | 0 sedenbot/modules/{ => fun}/lastfm.py | 0 sedenbot/modules/{ => fun}/lyrics.py | 0 sedenbot/modules/{ => fun}/memes.py | 0 sedenbot/modules/{ => fun}/quotly.py | 0 sedenbot/modules/{ => fun}/rgb.py | 0 sedenbot/modules/{ => fun}/spammer.py | 0 sedenbot/modules/{ => miscs}/autopp.py | 0 sedenbot/modules/{ => miscs}/direct_link.py | 0 sedenbot/modules/{ => miscs}/ezanvakti.py | 0 sedenbot/modules/{ => miscs}/horeke.py | 0 sedenbot/modules/{ => miscs}/misc.py | 0 sedenbot/modules/{ => miscs}/ocr.py | 0 sedenbot/modules/{ => miscs}/paste.py | 0 sedenbot/modules/{ => miscs}/remove_bg.py | 0 sedenbot/modules/{ => miscs}/reverse.py | 0 sedenbot/modules/{ => miscs}/spotify_api.py | 0 sedenbot/modules/scrapers.py | 644 ------------------ sedenbot/modules/{ => tools}/android.py | 33 +- sedenbot/modules/tools/currency.py | 76 +++ sedenbot/modules/tools/ddgo_search.py | 83 +++ sedenbot/modules/{ => tools}/exif.py | 0 sedenbot/modules/{ => tools}/gdrive.py | 0 .../modules/{git.py => tools/git_search.py} | 0 sedenbot/modules/tools/google_search.py | 126 ++++ sedenbot/modules/tools/image_search.py | 101 +++ sedenbot/modules/tools/imei_check.py | 62 ++ .../{kargotakip.py => tools/kargo_takip.py} | 0 .../{info.py => tools/profile_info.py} | 0 sedenbot/modules/{ => tools}/qrcode.py | 0 sedenbot/modules/{ => tools}/screencapture.py | 0 sedenbot/modules/{ => tools}/sed.py | 0 sedenbot/modules/{ => tools}/speedtest.py | 0 sedenbot/modules/tools/translate.py | 125 ++++ sedenbot/modules/tools/urban_dictionary.py | 62 ++ sedenbot/modules/{ => tools}/weather.py | 2 +- sedenbot/modules/tools/wiki_search.py | 83 +++ sedenbot/modules/{ => tools}/youtubedl.py | 17 +- sedenecem/core/misc.py | 15 +- sedenecem/core/replier.py | 3 +- sedenecem/translator/en.json | 5 +- sedenecem/translator/tr.json | 5 +- 67 files changed, 924 insertions(+), 684 deletions(-) rename sedenbot/modules/{ => admin}/ecem.py (100%) rename sedenbot/modules/{ => admin}/env.py (99%) rename sedenbot/modules/{ => admin}/seden.py (100%) rename sedenbot/modules/{ => admin}/system.py (99%) rename sedenbot/modules/{ => admin}/updater.py (100%) rename sedenbot/modules/{ => admin}/updown.py (100%) rename sedenbot/modules/{ => chat}/afk.py (100%) rename sedenbot/modules/{ => chat}/ban.py (100%) rename sedenbot/modules/{ => chat}/blacklist.py (100%) rename sedenbot/modules/{ => chat}/chat.py (96%) rename sedenbot/modules/{ => chat}/filters.py (100%) rename sedenbot/modules/{ => chat}/globals.py (99%) rename sedenbot/modules/{ => chat}/locks.py (100%) rename sedenbot/modules/{ => chat}/notes.py (100%) rename sedenbot/modules/{ => chat}/pmpermit.py (99%) rename sedenbot/modules/{ => chat}/profile.py (100%) rename sedenbot/modules/{ => chat}/purge.py (100%) rename sedenbot/modules/{ => chat}/snips.py (100%) rename sedenbot/modules/{ => chat}/spamwatch.py (100%) rename sedenbot/modules/{ => chat}/stickers.py (100%) create mode 100644 sedenbot/modules/fun/carbon_text.py rename sedenbot/modules/{ => fun}/colors.py (100%) rename sedenbot/modules/{ => fun}/deepfry.py (100%) rename sedenbot/modules/{ => fun}/effects.py (100%) rename sedenbot/modules/{ => fun}/lastfm.py (100%) rename sedenbot/modules/{ => fun}/lyrics.py (100%) rename sedenbot/modules/{ => fun}/memes.py (100%) rename sedenbot/modules/{ => fun}/quotly.py (100%) rename sedenbot/modules/{ => fun}/rgb.py (100%) rename sedenbot/modules/{ => fun}/spammer.py (100%) rename sedenbot/modules/{ => miscs}/autopp.py (100%) rename sedenbot/modules/{ => miscs}/direct_link.py (100%) rename sedenbot/modules/{ => miscs}/ezanvakti.py (100%) rename sedenbot/modules/{ => miscs}/horeke.py (100%) rename sedenbot/modules/{ => miscs}/misc.py (100%) rename sedenbot/modules/{ => miscs}/ocr.py (100%) rename sedenbot/modules/{ => miscs}/paste.py (100%) rename sedenbot/modules/{ => miscs}/remove_bg.py (100%) rename sedenbot/modules/{ => miscs}/reverse.py (100%) rename sedenbot/modules/{ => miscs}/spotify_api.py (100%) delete mode 100644 sedenbot/modules/scrapers.py rename sedenbot/modules/{ => tools}/android.py (91%) create mode 100644 sedenbot/modules/tools/currency.py create mode 100644 sedenbot/modules/tools/ddgo_search.py rename sedenbot/modules/{ => tools}/exif.py (100%) rename sedenbot/modules/{ => tools}/gdrive.py (100%) rename sedenbot/modules/{git.py => tools/git_search.py} (100%) create mode 100644 sedenbot/modules/tools/google_search.py create mode 100644 sedenbot/modules/tools/image_search.py create mode 100644 sedenbot/modules/tools/imei_check.py rename sedenbot/modules/{kargotakip.py => tools/kargo_takip.py} (100%) rename sedenbot/modules/{info.py => tools/profile_info.py} (100%) rename sedenbot/modules/{ => tools}/qrcode.py (100%) rename sedenbot/modules/{ => tools}/screencapture.py (100%) rename sedenbot/modules/{ => tools}/sed.py (100%) rename sedenbot/modules/{ => tools}/speedtest.py (100%) create mode 100644 sedenbot/modules/tools/translate.py create mode 100644 sedenbot/modules/tools/urban_dictionary.py rename sedenbot/modules/{ => tools}/weather.py (95%) create mode 100644 sedenbot/modules/tools/wiki_search.py rename sedenbot/modules/{ => tools}/youtubedl.py (92%) diff --git a/requirements.txt b/requirements.txt index 29856ef..5a08969 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ psycopg2-binary pybase64 pydrive pylast -pyrogram==2.0.106 +pyrofork pysmartDL python-barcode python-dotenv diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index a7f5381..6e0b9bc 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -11,7 +11,7 @@ from importlib import import_module from logging import CRITICAL, DEBUG, INFO, basicConfig, getLogger from os import environ, listdir, path, remove -from os.path import isfile +from os.path import isfile, join, isdir from pathlib import PurePath from re import search as resr from sqlite3 import connect @@ -92,6 +92,7 @@ def set_logger(): 'pyrogram.session.session', 'pyrogram.session.auth', 'asyncio', + 'aiosqlite', ] level = CRITICAL @@ -258,18 +259,27 @@ def export_session_string(self): def __get_modules(): folder = 'sedenbot/modules' - modules = [ - f[:-3] - for f in listdir(folder) - if isfile(f'{folder}/{f}') and f[-3:] == '.py' and f != '__init__.py' + subdirectories = [ + sub + for sub in listdir(folder) + if isdir(join(folder, sub)) ] + modules = [] + for subdirectory in subdirectories: + subfolder_path = join(folder, subdirectory) + py_files = [ + f[:-3] + for f in listdir(subfolder_path) + if isfile(join(subfolder_path, f)) and f.endswith('.py') + ] + for module in py_files: + module_name = f"{subdirectory.replace('/', '.')}.{module}" + modules.append(module_name) return modules def __import_modules(): get_modules = sorted(__get_modules()) - modules = ', '.join(get_modules) - LOGS.info(get_translation('loadedModules', [modules])) for module in get_modules: try: import_module(f'sedenbot.modules.{module}') @@ -278,5 +288,9 @@ def __import_modules(): LOGS.warn(format_exc()) LOGS.warn(get_translation('loadedModulesError', [module])) + imported_modules = [module.split('.')[-1] for module in get_modules if module.count('.') > 0] + modules = ', '.join(imported_modules) + LOGS.info(get_translation('loadedModules', [modules])) + __import_modules() diff --git a/sedenbot/modules/ecem.py b/sedenbot/modules/admin/ecem.py similarity index 100% rename from sedenbot/modules/ecem.py rename to sedenbot/modules/admin/ecem.py diff --git a/sedenbot/modules/env.py b/sedenbot/modules/admin/env.py similarity index 99% rename from sedenbot/modules/env.py rename to sedenbot/modules/admin/env.py index fc4e057..bcd6f43 100644 --- a/sedenbot/modules/env.py +++ b/sedenbot/modules/admin/env.py @@ -21,7 +21,7 @@ set_local_env, unset_local_env, ) -from sedenbot.modules.horeke import restart +from sedenbot.modules.miscs.horeke import restart from sedenecem.core import edit, extract_args, get_translation, sedenify diff --git a/sedenbot/modules/seden.py b/sedenbot/modules/admin/seden.py similarity index 100% rename from sedenbot/modules/seden.py rename to sedenbot/modules/admin/seden.py diff --git a/sedenbot/modules/system.py b/sedenbot/modules/admin/system.py similarity index 99% rename from sedenbot/modules/system.py rename to sedenbot/modules/admin/system.py index 23f3e18..478fdf2 100644 --- a/sedenbot/modules/system.py +++ b/sedenbot/modules/admin/system.py @@ -16,7 +16,7 @@ from pyrogram.raw.functions.help import GetNearestDc from sedenbot import ALIVE_MSG, BOT_VERSION, CHANNEL, HELP -from sedenbot.modules.ecem import ecem +from sedenbot.modules.admin.ecem import ecem from sedenecem.core import ( edit, extract_args, diff --git a/sedenbot/modules/updater.py b/sedenbot/modules/admin/updater.py similarity index 100% rename from sedenbot/modules/updater.py rename to sedenbot/modules/admin/updater.py diff --git a/sedenbot/modules/updown.py b/sedenbot/modules/admin/updown.py similarity index 100% rename from sedenbot/modules/updown.py rename to sedenbot/modules/admin/updown.py diff --git a/sedenbot/modules/afk.py b/sedenbot/modules/chat/afk.py similarity index 100% rename from sedenbot/modules/afk.py rename to sedenbot/modules/chat/afk.py diff --git a/sedenbot/modules/ban.py b/sedenbot/modules/chat/ban.py similarity index 100% rename from sedenbot/modules/ban.py rename to sedenbot/modules/chat/ban.py diff --git a/sedenbot/modules/blacklist.py b/sedenbot/modules/chat/blacklist.py similarity index 100% rename from sedenbot/modules/blacklist.py rename to sedenbot/modules/chat/blacklist.py diff --git a/sedenbot/modules/chat.py b/sedenbot/modules/chat/chat.py similarity index 96% rename from sedenbot/modules/chat.py rename to sedenbot/modules/chat/chat.py index c737876..60240e3 100644 --- a/sedenbot/modules/chat.py +++ b/sedenbot/modules/chat/chat.py @@ -81,8 +81,8 @@ def keep_read(message): @sedenify(pattern='^.call') def call_notes(message): try: - from sedenbot.modules.notes import get_note - from sedenbot.modules.snips import get_snip + from sedenbot.modules.deneme.notes import get_note + from sedenbot.modules.var.snips import get_snip except BaseException: edit(message, f'`{get_translation("nonSqlMode")}`') return diff --git a/sedenbot/modules/filters.py b/sedenbot/modules/chat/filters.py similarity index 100% rename from sedenbot/modules/filters.py rename to sedenbot/modules/chat/filters.py diff --git a/sedenbot/modules/globals.py b/sedenbot/modules/chat/globals.py similarity index 99% rename from sedenbot/modules/globals.py rename to sedenbot/modules/chat/globals.py index 4b7befd..1146ab7 100644 --- a/sedenbot/modules/globals.py +++ b/sedenbot/modules/chat/globals.py @@ -12,7 +12,7 @@ from pyrogram import enums from pyrogram.types import ChatPermissions from sedenbot import BRAIN, HELP, LOGS -from sedenbot.modules.ban import get_reason +from sedenbot.modules.chat.ban import get_reason from sedenecem.core import ( edit, extract_args_split, diff --git a/sedenbot/modules/locks.py b/sedenbot/modules/chat/locks.py similarity index 100% rename from sedenbot/modules/locks.py rename to sedenbot/modules/chat/locks.py diff --git a/sedenbot/modules/notes.py b/sedenbot/modules/chat/notes.py similarity index 100% rename from sedenbot/modules/notes.py rename to sedenbot/modules/chat/notes.py diff --git a/sedenbot/modules/pmpermit.py b/sedenbot/modules/chat/pmpermit.py similarity index 99% rename from sedenbot/modules/pmpermit.py rename to sedenbot/modules/chat/pmpermit.py index 29b5f8d..269721f 100644 --- a/sedenbot/modules/pmpermit.py +++ b/sedenbot/modules/chat/pmpermit.py @@ -11,7 +11,7 @@ from pyrogram.raw.functions.messages import ReportSpam from pyrogram.raw.types import InputPeerUser from sedenbot import HELP, LOGS, PM_AUTO_BAN, PM_MSG_COUNT, PM_UNAPPROVED, TEMP_SETTINGS -from sedenbot.modules.chat import is_muted +from sedenbot.modules.chat.chat import is_muted from sedenecem.core import edit, get_translation, reply, sedenify, send_log from sqlalchemy.exc import IntegrityError diff --git a/sedenbot/modules/profile.py b/sedenbot/modules/chat/profile.py similarity index 100% rename from sedenbot/modules/profile.py rename to sedenbot/modules/chat/profile.py diff --git a/sedenbot/modules/purge.py b/sedenbot/modules/chat/purge.py similarity index 100% rename from sedenbot/modules/purge.py rename to sedenbot/modules/chat/purge.py diff --git a/sedenbot/modules/snips.py b/sedenbot/modules/chat/snips.py similarity index 100% rename from sedenbot/modules/snips.py rename to sedenbot/modules/chat/snips.py diff --git a/sedenbot/modules/spamwatch.py b/sedenbot/modules/chat/spamwatch.py similarity index 100% rename from sedenbot/modules/spamwatch.py rename to sedenbot/modules/chat/spamwatch.py diff --git a/sedenbot/modules/stickers.py b/sedenbot/modules/chat/stickers.py similarity index 100% rename from sedenbot/modules/stickers.py rename to sedenbot/modules/chat/stickers.py diff --git a/sedenbot/modules/fun/carbon_text.py b/sedenbot/modules/fun/carbon_text.py new file mode 100644 index 0000000..826e183 --- /dev/null +++ b/sedenbot/modules/fun/carbon_text.py @@ -0,0 +1,124 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from os import path, remove +from time import sleep +from urllib.parse import quote_plus + +from gtts import gTTS +from selenium.webdriver.common.by import By + +from sedenbot import HELP, SEDEN_LANG +from sedenecem.core import ( + edit, + extract_args, + extract_args_split, + get_translation, + get_webdriver, + reply_doc, + reply_voice, + sedenify, + send_log, +) + +CARBONLANG = 'auto' +TTS_LANG = SEDEN_LANG +TRT_LANG = SEDEN_LANG + + +@sedenify(pattern='^.crblang') +def carbonlang(message): + global CARBONLANG + CARBONLANG = extract_args(message) + edit(message, get_translation('carbonLang', ['**', CARBONLANG])) + + +@sedenify(pattern='^.carbon') +def carbon(message): + match = extract_args(message) + if len(match) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + edit(message, f'`{get_translation("processing")}`') + reply = message.reply_to_message + pcode = message.text + if pcode[8:]: + pcode = str(pcode[8:]) + elif reply: + pcode = str(reply.message) + code = quote_plus(pcode) + global CARBONLANG + CARBON = f'https://carbon.now.sh/?l={CARBONLANG}&code={code}' + edit(message, f'`{get_translation("processing")}\n%25`') + if path.isfile('./carbon.png'): + remove('./carbon.png') + driver = get_webdriver() + driver.get(CARBON) + edit(message, f'`{get_translation("processing")}\n%50`') + driver.command_executor._commands['send_command'] = ( + 'POST', + '/session/$sessionId/chromium/send_command', + ) + driver.find_element(By.XPATH, "//button[contains(text(),'Export')]").click() + edit(message, f'`{get_translation("processing")}\n%75`') + while not path.isfile('./carbon.png'): + sleep(0.5) + edit(message, f'`{get_translation("processing")}\n%100`') + file = './carbon.png' + edit(message, f'`{get_translation("carbonUpload")}`') + reply_doc( + reply if reply else message, + file, + caption=get_translation('carbonResult'), + delete_after_send=True, + ) + message.delete() + driver.quit() + + +@sedenify(pattern='^.tts') +def text_to_speech(message): + reply = message.reply_to_message + args = extract_args(message) + if args: + pass + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("ttsUsage")}`') + args = reply.text + else: + edit(message, f'`{get_translation("ttsUsage")}`') + return + + try: + gTTS(args, lang=TTS_LANG) + except AssertionError: + edit(message, f'`{get_translation("ttsBlank")}`') + return + except ValueError: + edit(message, f'`{get_translation("ttsNoSupport")}`') + return + except RuntimeError: + edit(message, f'`{get_translation("ttsError")}`') + return + tts = gTTS(args, lang=TTS_LANG) + tts.save('h.mp3') + with open('h.mp3', 'rb') as audio: + linelist = list(audio) + linecount = len(linelist) + if linecount == 1: + tts = gTTS(args, lang=TTS_LANG) + tts.save('h.mp3') + with open('h.mp3', 'r'): + reply_voice(reply if reply else message, 'h.mp3', delete_file=True) + + message.delete() + send_log(get_translation('ttsLog')) + +HELP.update({'carbon': get_translation('carbonInfo')}) diff --git a/sedenbot/modules/colors.py b/sedenbot/modules/fun/colors.py similarity index 100% rename from sedenbot/modules/colors.py rename to sedenbot/modules/fun/colors.py diff --git a/sedenbot/modules/deepfry.py b/sedenbot/modules/fun/deepfry.py similarity index 100% rename from sedenbot/modules/deepfry.py rename to sedenbot/modules/fun/deepfry.py diff --git a/sedenbot/modules/effects.py b/sedenbot/modules/fun/effects.py similarity index 100% rename from sedenbot/modules/effects.py rename to sedenbot/modules/fun/effects.py diff --git a/sedenbot/modules/lastfm.py b/sedenbot/modules/fun/lastfm.py similarity index 100% rename from sedenbot/modules/lastfm.py rename to sedenbot/modules/fun/lastfm.py diff --git a/sedenbot/modules/lyrics.py b/sedenbot/modules/fun/lyrics.py similarity index 100% rename from sedenbot/modules/lyrics.py rename to sedenbot/modules/fun/lyrics.py diff --git a/sedenbot/modules/memes.py b/sedenbot/modules/fun/memes.py similarity index 100% rename from sedenbot/modules/memes.py rename to sedenbot/modules/fun/memes.py diff --git a/sedenbot/modules/quotly.py b/sedenbot/modules/fun/quotly.py similarity index 100% rename from sedenbot/modules/quotly.py rename to sedenbot/modules/fun/quotly.py diff --git a/sedenbot/modules/rgb.py b/sedenbot/modules/fun/rgb.py similarity index 100% rename from sedenbot/modules/rgb.py rename to sedenbot/modules/fun/rgb.py diff --git a/sedenbot/modules/spammer.py b/sedenbot/modules/fun/spammer.py similarity index 100% rename from sedenbot/modules/spammer.py rename to sedenbot/modules/fun/spammer.py diff --git a/sedenbot/modules/autopp.py b/sedenbot/modules/miscs/autopp.py similarity index 100% rename from sedenbot/modules/autopp.py rename to sedenbot/modules/miscs/autopp.py diff --git a/sedenbot/modules/direct_link.py b/sedenbot/modules/miscs/direct_link.py similarity index 100% rename from sedenbot/modules/direct_link.py rename to sedenbot/modules/miscs/direct_link.py diff --git a/sedenbot/modules/ezanvakti.py b/sedenbot/modules/miscs/ezanvakti.py similarity index 100% rename from sedenbot/modules/ezanvakti.py rename to sedenbot/modules/miscs/ezanvakti.py diff --git a/sedenbot/modules/horeke.py b/sedenbot/modules/miscs/horeke.py similarity index 100% rename from sedenbot/modules/horeke.py rename to sedenbot/modules/miscs/horeke.py diff --git a/sedenbot/modules/misc.py b/sedenbot/modules/miscs/misc.py similarity index 100% rename from sedenbot/modules/misc.py rename to sedenbot/modules/miscs/misc.py diff --git a/sedenbot/modules/ocr.py b/sedenbot/modules/miscs/ocr.py similarity index 100% rename from sedenbot/modules/ocr.py rename to sedenbot/modules/miscs/ocr.py diff --git a/sedenbot/modules/paste.py b/sedenbot/modules/miscs/paste.py similarity index 100% rename from sedenbot/modules/paste.py rename to sedenbot/modules/miscs/paste.py diff --git a/sedenbot/modules/remove_bg.py b/sedenbot/modules/miscs/remove_bg.py similarity index 100% rename from sedenbot/modules/remove_bg.py rename to sedenbot/modules/miscs/remove_bg.py diff --git a/sedenbot/modules/reverse.py b/sedenbot/modules/miscs/reverse.py similarity index 100% rename from sedenbot/modules/reverse.py rename to sedenbot/modules/miscs/reverse.py diff --git a/sedenbot/modules/spotify_api.py b/sedenbot/modules/miscs/spotify_api.py similarity index 100% rename from sedenbot/modules/spotify_api.py rename to sedenbot/modules/miscs/spotify_api.py diff --git a/sedenbot/modules/scrapers.py b/sedenbot/modules/scrapers.py deleted file mode 100644 index 256b6a1..0000000 --- a/sedenbot/modules/scrapers.py +++ /dev/null @@ -1,644 +0,0 @@ -# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> -# -# This file is part of TeamDerUntergang project, -# and licensed under GNU Affero General Public License v3. -# See the GNU Affero General Public License for more details. -# -# All rights reserved. See COPYING, AUTHORS. -# - -from json import JSONDecodeError, loads -from mimetypes import guess_type -from os import path, remove -from random import choice, randrange -from re import findall, sub -from time import sleep -from traceback import format_exc -from urllib.parse import quote_plus - -from bs4 import BeautifulSoup -from emoji import demojize -from googletrans import LANGUAGES, Translator -from gtts import gTTS -from gtts.lang import tts_langs -from pyrogram import enums -from pyrogram.types import InputMediaPhoto -from requests import RequestException, get, post -from selenium.webdriver.common.by import By - -from sedenbot import HELP, SEDEN_LANG -from sedenecem.core import ( - edit, - extract_args, - extract_args_split, - get_translation, - get_webdriver, - google_domains, - reply_doc, - reply_voice, - sedenify, - send_log, - useragent, -) - -CARBONLANG = 'auto' -TTS_LANG = SEDEN_LANG -TRT_LANG = SEDEN_LANG - - -@sedenify(pattern='^.crblang') -def carbonlang(message): - global CARBONLANG - CARBONLANG = extract_args(message) - edit(message, get_translation('carbonLang', ['**', CARBONLANG])) - - -@sedenify(pattern='^.carbon') -def carbon(message): - match = extract_args(message) - if len(match) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - edit(message, f'`{get_translation("processing")}`') - reply = message.reply_to_message - pcode = message.text - if pcode[8:]: - pcode = str(pcode[8:]) - elif reply: - pcode = str(reply.message) - code = quote_plus(pcode) - global CARBONLANG - CARBON = f'https://carbon.now.sh/?l={CARBONLANG}&code={code}' - edit(message, f'`{get_translation("processing")}\n%25`') - if path.isfile('./carbon.png'): - remove('./carbon.png') - driver = get_webdriver() - driver.get(CARBON) - edit(message, f'`{get_translation("processing")}\n%50`') - driver.command_executor._commands['send_command'] = ( - 'POST', - '/session/$sessionId/chromium/send_command', - ) - driver.find_element(By.XPATH, "//button[contains(text(),'Export')]").click() - edit(message, f'`{get_translation("processing")}\n%75`') - while not path.isfile('./carbon.png'): - sleep(0.5) - edit(message, f'`{get_translation("processing")}\n%100`') - file = './carbon.png' - edit(message, f'`{get_translation("carbonUpload")}`') - reply_doc( - reply if reply else message, - file, - caption=get_translation('carbonResult'), - delete_after_send=True, - ) - message.delete() - driver.quit() - - -@sedenify(pattern='^.img') -def img(message): - query = extract_args(message) - lim = findall(r'lim=\d+', query) - try: - lim = lim[0] - lim = lim.replace('lim=', '') - query = query.replace('lim=' + lim[0], '') - lim = int(lim) - if lim > 10: - lim = 10 - except IndexError: - lim = 3 - - if len(query) < 1: - edit(message, f'`{get_translation("imgUsage")}`') - return - edit(message, f'`{get_translation("processing")}`') - - url = f'https://{choice(google_domains)}/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' - driver = get_webdriver() - driver.get(url) - count = 1 - files = [] - for i in driver.find_elements( - By.XPATH, '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]' - ): - i.click() - try_count = 0 - while ( - len( - element := driver.find_elements( - By.XPATH, - '//img[contains(@class,"n3VNCb") and contains(@src,"http")]', - ) - ) - < 1 - and try_count < 20 - ): - try_count += 1 - sleep(0.1) - if len(element) < 1: - continue - link = element[0].get_attribute('src') - filename = f'result_{count}.jpg' - try: - with open(filename, 'wb') as result: - result.write(get(link).content) - ftype = guess_type(filename) - if not ftype[0] or ftype[0].split('/')[1] not in ['png', 'jpg', 'jpeg']: - remove(filename) - continue - except Exception: - continue - files.append(InputMediaPhoto(filename)) - sleep(1) - elements = driver.find_elements(By.XPATH, '//a[contains(@class,"hm60ue")]') - for element in elements: - element.click() - count += 1 - if lim < count: - break - sleep(1) - - driver.quit() - - reply_doc(message, files, delete_orig=True, delete_after_send=True) - - -@sedenify(pattern='^.google') -def google(message): - match = extract_args(message) - if len(match) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - page = findall(r"page=\d+", match) - try: - page = page[0] - page = page.replace('page=', '') - match = match.replace('page=' + page[0], '') - page = int(page) - except BaseException: - page = 1 - msg = do_gsearch(match, page) - edit( - message, get_translation('googleResult', ['**', '`', match, msg]), preview=False - ) - - send_log(get_translation('googleLog', [match])) - - -def do_gsearch(query, page): - def find_page(num): - if num < 1: - num = 1 - return (num - 1) * 10 - - def parse_key(keywords): - return keywords.replace(' ', '+') - - def replacer(st): - return ( - sub(r'[`\*_]', '', st) - .replace('\n', ' ') - .replace('(', '〈') - .replace(')', '〉') - .replace('!', 'ⵑ') - .strip() - ) - - def get_result(res): - link = res.find('a', href=True) - title = res.find('h3') - if title: - title = title.text - desc = res.find( - 'div', attrs={'class': ['VwiC3b', 'yXK7lf', 'MUxGbd', 'yDYNvb', 'lyLwlc']} - ) - if desc: - desc = desc.text - - if link and title and desc: - return f'[{replacer(title)}]({link["href"]})\n{desc or ""}' - - query = parse_key(query) - page = find_page(page) - temp = f'/search?q={query}&start={find_page(page)}&hl={SEDEN_LANG}' - - req = get( - f'https://{choice(google_domains)}{temp}', - headers={ - 'User-Agent': useragent(), - 'Content-Type': 'text/html', - }, - ) - - retries = 0 - while req.status_code != 200 and retries < 10: - retries += 1 - req = get( - f'https://{choice(google_domains)}{temp}', - headers={ - 'User-Agent': useragent(), - 'Content-Type': 'text/html', - }, - ) - - soup = BeautifulSoup(req.text, 'html.parser') - - res1 = soup.find_all('div', attrs={'class': 'g'}) - - out = '' - count = 0 - for res in res1: - try: - result = get_result(res) - if result: - count += 1 - out += f'{count} - {result}\n\n' - except Exception: - print(format_exc()) - print(res) - pass - - return out - - -@sedenify(pattern='^.d(uckduck|d)go') -def ddgo(message): - query = extract_args(message) - if len(query) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - req = get( - f'https://duckduckgo.com/lite?q={query}', - headers={ - 'User-Agent': useragent(), - 'Content-Type': 'text/html', - }, - ) - soup = BeautifulSoup(req.text, 'html.parser') - res1 = soup.findAll('table', {'border': 0}) - res1 = res1[-1].findAll('tr') - - match = do_ddsearch(res1) - edit( - message, - get_translation('googleResult', ['**', '`', query, match]), - preview=False, - ) - send_log(get_translation('ddgoLog', [query])) - - -def do_ddsearch(res1): - def splitter(res): - subs = [] - tlist = None - comp = False - for i in range(len(res)): - item = res[i] - if res3 := item.find('a', {'class': ['result-link']}): - if comp: - subs.append(tlist) - comp = True - tlist = [] - tlist.append(res3) - elif res4 := item.find('td', {'class': ['result-snippet']}): - tlist.append(res4) - subs.append(tlist) - if len(subs) > 9: - break - return subs - - res1 = splitter(res1) - - out = '' - for i in range(len(res1)): - item = res1[i] - link = item[0] - ltxt = link.text.replace('|', '-').replace('...', '').strip() - desc = item[1].text.strip() if len(item) > 1 else get_translation('ddgoDesc') - out += f'{i+1} - [{ltxt}]({link["href"]})\n{desc}\n\n' - - return out - - -@sedenify(pattern='^.ud') -def urbandictionary(message): - query = extract_args(message) - if len(query) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - edit(message, f'`{get_translation("processing")}`') - response = get(f'https://api.urbandictionary.com/v0/define?term={query}') - data = loads(response.text) - if len(data["list"]): - list_size = len(data['list']) - item = data['list'][randrange(list_size)] - meanlen = item['definition'] + item['example'] - if len(meanlen) >= 4096: - edit(message, f'`{get_translation("outputTooLarge")}`') - file = open('urbandictionary.txt', 'w+') - file.write( - 'Query: ' - + query - + '\n\nMeaning: ' - + item['definition'] - + '\n\n' - + 'Örnek: \n' - + item['example'] - ) - file.close() - reply_doc( - message, - 'urbandictionary.txt', - caption=f'`{get_translation("outputTooLarge")}`', - ) - if path.exists('urbandictionary.txt'): - remove('urbandictionary.txt') - message.delete() - return - edit( - message, - get_translation( - 'sedenQueryUd', - ['**', '`', query, item['definition'], item['example']], - ), - ) - else: - edit(message, get_translation('udNoResult', ['**', query])) - - -@sedenify(pattern='^.wiki') -def wiki(message): - args = extract_args(message) - if len(args) < 1: - edit(message, f'`{get_translation("wrongCommand")}`') - return - - try: - result = search_wiki(args) - except BaseException as e: - raise e - - if len(result) > 4096: - with open(f'{args}.txt', 'w', encoding='utf-8') as file: - file.write(result) - return reply_doc( - message, - f'{args}.txt', - caption=f'`{get_translation("outputTooLarge")}`', - delete_after_send=True, - ) - - edit(message, get_translation('sedenQuery', ['**', '`', args, result])) - send_log(get_translation('wikiLog', ['`', args])) - - -def search_wiki(query): - url = f'https://{SEDEN_LANG or "en"}.wikipedia.org/w/api.php' - params = { - 'action': 'query', - 'format': 'json', - 'prop': 'extracts', - 'titles': query, - 'exsectionformat': 'wiki', - 'explaintext': 1, - } - - try: - response = get(url, params=params) - response.raise_for_status() - data = loads(response.text) - pages = data.get('query', {}).get('pages', {}) - result = '' - - for page in pages.values(): - extract = page.get('extract', '') - result += extract - - if not result: - result = get_translation('wikiError') - - return result - - except (RequestException, JSONDecodeError) as e: - print(f'API Error: {e}') - return '' - - -@sedenify(pattern='^.tts') -def text_to_speech(message): - reply = message.reply_to_message - args = extract_args(message) - if args: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("ttsUsage")}`') - args = reply.text - else: - edit(message, f'`{get_translation("ttsUsage")}`') - return - - try: - gTTS(args, lang=TTS_LANG) - except AssertionError: - edit(message, f'`{get_translation("ttsBlank")}`') - return - except ValueError: - edit(message, f'`{get_translation("ttsNoSupport")}`') - return - except RuntimeError: - edit(message, f'`{get_translation("ttsError")}`') - return - tts = gTTS(args, lang=TTS_LANG) - tts.save('h.mp3') - with open('h.mp3', 'rb') as audio: - linelist = list(audio) - linecount = len(linelist) - if linecount == 1: - tts = gTTS(args, lang=TTS_LANG) - tts.save('h.mp3') - with open('h.mp3', 'r'): - reply_voice(reply if reply else message, 'h.mp3', delete_file=True) - - message.delete() - send_log(get_translation('ttsLog')) - - -@sedenify(pattern='^.trt') -def translate(message): - translator = Translator() - reply = message.reply_to_message - args = extract_args(message) - if args: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("trtUsage")}`') - args = reply.text - else: - edit(message, f'`{get_translation("trtUsage")}`') - return - - try: - reply_text = translator.translate(demojize(args), dest=TRT_LANG) - except ValueError: - edit(message, f'`{get_translation("trtError")}`') - return - - source_lan = LANGUAGES[reply_text.src.lower()] - transl_lan = LANGUAGES[reply_text.dest.lower()] - reply_text = '{}\n{}'.format( - get_translation( - 'transHeader', ['**', '`', source_lan.title(), transl_lan.title()] - ), - reply_text.text, - ) - - edit(message, reply_text) - - send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) - - -@sedenify(pattern='^.lang') -def lang(message): - arr = extract_args_split(message) - - if len(arr) != 2: - edit(message, f'`{get_translation("wrongCommand")}`') - return - - util = arr[0].lower() - arg = arr[1].lower() - if util == 'trt': - scraper = get_translation('scraper1') - global TRT_LANG - if arg in LANGUAGES: - TRT_LANG = arg - LANG = LANGUAGES[arg] - else: - edit(message, get_translation('scraperTrt', ['`', LANGUAGES])) - return - elif util == 'tts': - scraper = get_translation('scraper2') - global TTS_LANG - if arg in tts_langs(): - TTS_LANG = arg - LANG = tts_langs()[arg] - else: - edit(message, get_translation('scraperTts', ['`', tts_langs()])) - return - edit(message, get_translation('scraperResult', ['`', scraper, LANG.title()])) - - send_log(get_translation('scraperLog', ['`', scraper, LANG.title()])) - - -@sedenify(pattern='^.d[oö]viz') -def doviz(message): - req = get( - 'https://www.doviz.com/', - headers={'User-Agent': useragent()}, - ) - page = BeautifulSoup(req.content, 'html.parser') - res = page.find_all('div', {'class': 'item'}) - out = '**Güncel döviz kurları:**\n\n' - - for item in res: - name = item.find('span', {'class': 'name'}).text - value = item.find('span', {'class': 'value'}).text - - rate_elem = item.find('div', {'class': ['change-rate status down', 'change-rate status up']}) - rate_class = rate_elem['class'][-1] if rate_elem else None - - changes_emoji = '' - if rate_class == 'down': - changes_emoji = '⬇️' - elif rate_class == 'up': - changes_emoji = '⬆️' - - if changes_emoji: - out += f'{changes_emoji} **{name}:** `{value}`\n' - else: - out += f'**{name}:** `{value}`\n' - - edit(message, out) - - - -@sedenify(pattern='^.imei(|check)') -def imeichecker(message): - imei = extract_args(message) - edit(message, f'`{get_translation("processing")}`') - if len(imei) != 15: - edit(message, f'`{get_translation("wrongCommand")}`') - return - try: - while True: - response = post( - f"https://m.turkiye.gov.tr/api2.php?p=imei-sorgulama&txtImei={imei}" - ).json() - if not response['data']['asyncFinished']: - continue - result = response['data'] - break - _marka = findall(r'Marka:(.+) Model', result['markaModel']) - _model = findall(r'Model Bilgileri:(.+)', result['markaModel']) - _pazaradi = findall(r'Pazar Adı:(.+) Marka', result['markaModel']) - marka = ( - _marka[0].replace(',', '').strip() if _marka else None - ) - model = ( - _model[0].replace(',', '').strip() if _model else None - ) - pazaradi = ( - _pazaradi[0].replace(',', '').strip() if _pazaradi else None - ) - reply_text = f"<b>Sorgu Tarihi:</b> <code>{result['sorguTarihi']}</code>\n\n" - reply_text += f"<b>IMEI:</b> <code>{result['imei'][:-5]+5*'*'}</code>\n" - reply_text += f"<b>Durum:</b> <code>{result['durum']}</code>\n" - reply_text += f"<b>Kaynak:</b> <code>{result['kaynak']}</code>\n" - reply_text += f"<b>Pazar Adı:</b> <code>{pazaradi}</code>\n" if pazaradi is not None else "" - reply_text += f"<b>Marka:</b> <code>{marka}</code>\n" if marka is not None else "" - reply_text += f"<b>Model:</b> <code>{model}</code>\n\n" if model is not None else "" - - edit(message, reply_text, parse=enums.parse_mode.ParseMode.HTML, preview=False) - except Exception as e: - raise e - - -@sedenify(pattern='^.currency') -def currency_convert(message): - input_str = extract_args(message) - input_sgra = input_str.split(' ') - if len(input_sgra) == 3: - try: - number = float(input_sgra[0]) - currency_from = input_sgra[1].upper() - currency_to = input_sgra[2].upper() - request_url = f'https://www.x-rates.com/calculator/?from={currency_from}&to={currency_to}&amount={number}' - current_response = get(request_url, headers={'User-Agent': useragent()}) - if current_response.status_code == 200: - soup = BeautifulSoup(current_response.text, 'html.parser') - rebmun = soup.find('span', {'class': 'ccOutputRslt'}) - result = rebmun.find('span') - result.extract() - edit(message, f'**{number} {currency_from} = {rebmun.text.strip()}**') - else: - edit(message, f'`{get_translation("currencyError")}`') - except Exception as e: - edit(message, str(e)) - else: - edit(message, f'`{get_translation("syntaxError")}`') - return - - -HELP.update({'img': get_translation('imgInfo')}) -HELP.update({'currency': get_translation('currencyInfo')}) -HELP.update({'imeicheck': get_translation('imeiInfo')}) -HELP.update({'carbon': get_translation('carbonInfo')}) -HELP.update({'goolag': get_translation('googleInfo')}) -HELP.update({'duckduckgo': get_translation('ddgoInfo')}) -HELP.update({'wiki': get_translation('wikiInfo')}) -HELP.update({'ud': get_translation('udInfo')}) -HELP.update({'translator': get_translation('translatorInfo')}) diff --git a/sedenbot/modules/android.py b/sedenbot/modules/tools/android.py similarity index 91% rename from sedenbot/modules/android.py rename to sedenbot/modules/tools/android.py index d7b0199..3c43c82 100644 --- a/sedenbot/modules/android.py +++ b/sedenbot/modules/tools/android.py @@ -25,6 +25,26 @@ useragent, ) +@sedenify(pattern='^.k(ernel)?su$') +def kernelsu(message): + kernelsu_url = 'https://api.github.com/repos/tiann/KernelSU/releases' + try: + response = get(kernelsu_url) + data = response.json() + releases = sorted(data, key=lambda x: x['created_at'], reverse=True)[:3] + out = f'**{get_translation("ksuReleases")}**\n' + for release in releases: + tag_name = release['tag_name'] + assets = release['assets'] + apk_assets = [asset for asset in assets if asset['name'].endswith('.apk')] + if apk_assets: + latest_apk = apk_assets[0] + apk_name = latest_apk['name'] + apk_download_url = latest_apk['browser_download_url'] + out += f'`{tag_name}:` [{apk_name}]({apk_download_url})\n' + edit(message, out, preview=False) + except Exception: + pass @sedenify(pattern='^.magisk$') def magisk(message): @@ -76,6 +96,13 @@ def phh(message): edit(message, releases, preview=False) +def format_size(size_in_bytes): + if size_in_bytes >= 1e9: + return '{:.2f} GB'.format(size_in_bytes / 1e9) + else: + return '{:.2f} MB'.format(size_in_bytes / 1e6) + + @sedenify(pattern='^.l(ineage(os)?|os)') def get_lineageos(message): args = extract_args(message).lower() @@ -96,7 +123,7 @@ def get_lineageos(message): build = response[0] time = datetime.utcfromtimestamp(int(build['datetime'])).strftime('%Y-%m-%d') filename = build['filename'] - size = '{:,.2f} MB'.format(int(build['size']) / float(1 << 20)) + size = format_size(int(build['size'])) build_url = build['url'] version = build['version'] else: @@ -345,11 +372,11 @@ def find_device(query, proxy): raw_query = query.lower() def replace_query(query): - return urlencode({'sSearch': query}) + return urlencode({'sQuickSearch': 'yes', 'sName': query}) query = replace_query(raw_query) req = get( - f'https://www.gsmarena.com/res.php3?{query}', + f'https://www.gsmarena.com/results.php3?{query}', headers={'User-Agent': useragent()}, proxies=proxy, ) diff --git a/sedenbot/modules/tools/currency.py b/sedenbot/modules/tools/currency.py new file mode 100644 index 0000000..9c0532f --- /dev/null +++ b/sedenbot/modules/tools/currency.py @@ -0,0 +1,76 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from bs4 import BeautifulSoup +from requests import get + +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify, useragent + + +@sedenify(pattern='^.currency') +def currency_convert(message): + input_str = extract_args(message) + input_sgra = input_str.split(' ') + if len(input_sgra) == 3: + try: + number = float(input_sgra[0]) + currency_from = input_sgra[1].upper() + currency_to = input_sgra[2].upper() + request_url = f'https://www.x-rates.com/calculator/?from={currency_from}&to={currency_to}&amount={number}' + current_response = get(request_url, headers={'User-Agent': useragent()}) + if current_response.status_code == 200: + soup = BeautifulSoup(current_response.text, 'html.parser') + rebmun = soup.find('span', {'class': 'ccOutputRslt'}) + result = rebmun.find('span') + result.extract() + edit(message, f'**{number} {currency_from} = {rebmun.text.strip()}**') + else: + edit(message, f'`{get_translation("currencyError")}`') + except Exception as e: + edit(message, str(e)) + else: + edit(message, f'`{get_translation("syntaxError")}`') + return + + +@sedenify(pattern='^.d[oö]viz') +def doviz(message): + req = get( + 'https://www.doviz.com/', + headers={'User-Agent': useragent()}, + ) + page = BeautifulSoup(req.content, 'html.parser') + res = page.find_all('div', {'class': 'item'}) + out = '**Güncel döviz kurları:**\n\n' + + for item in res: + name = item.find('span', {'class': 'name'}).text + value = item.find('span', {'class': 'value'}).text + + rate_elem = item.find( + 'div', {'class': ['change-rate status down', 'change-rate status up']} + ) + rate_class = rate_elem['class'][-1] if rate_elem else None + + changes_emoji = '' + if rate_class == 'down': + changes_emoji = '⬇️' + elif rate_class == 'up': + changes_emoji = '⬆️' + + if changes_emoji: + out += f'{changes_emoji} **{name}:** `{value}`\n' + else: + out += f'**{name}:** `{value}`\n' + + edit(message, out) + + +HELP.update({'currency': get_translation('currencyInfo')}) diff --git a/sedenbot/modules/tools/ddgo_search.py b/sedenbot/modules/tools/ddgo_search.py new file mode 100644 index 0000000..6e5d0d4 --- /dev/null +++ b/sedenbot/modules/tools/ddgo_search.py @@ -0,0 +1,83 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from bs4 import BeautifulSoup +from requests import get + +from sedenbot import HELP +from sedenecem.core import ( + edit, + extract_args, + get_translation, + sedenify, + send_log, + useragent, +) + + +@sedenify(pattern='^.d(uckduck|d)go') +def ddgo(message): + query = extract_args(message) + if len(query) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + req = get( + f'https://duckduckgo.com/lite?q={query}', + headers={ + 'User-Agent': useragent(), + 'Content-Type': 'text/html', + }, + ) + soup = BeautifulSoup(req.text, 'html.parser') + res1 = soup.findAll('table', {'border': 0}) + res1 = res1[-1].findAll('tr') + + match = do_ddsearch(res1) + edit( + message, + get_translation('googleResult', ['**', '`', query, match]), + preview=False, + ) + send_log(get_translation('ddgoLog', [query])) + + +def do_ddsearch(res1): + def splitter(res): + subs = [] + tlist = None + comp = False + for i in range(len(res)): + item = res[i] + if res3 := item.find('a', {'class': ['result-link']}): + if comp: + subs.append(tlist) + comp = True + tlist = [] + tlist.append(res3) + elif res4 := item.find('td', {'class': ['result-snippet']}): + tlist.append(res4) + subs.append(tlist) + if len(subs) > 9: + break + return subs + + res1 = splitter(res1) + + out = '' + for i in range(len(res1)): + item = res1[i] + link = item[0] + ltxt = link.text.replace('|', '-').replace('...', '').strip() + desc = item[1].text.strip() if len(item) > 1 else get_translation('ddgoDesc') + out += f'{i+1} - [{ltxt}]({link["href"]})\n{desc}\n\n' + + return out + + +HELP.update({'duckduckgo': get_translation('ddgoInfo')}) diff --git a/sedenbot/modules/exif.py b/sedenbot/modules/tools/exif.py similarity index 100% rename from sedenbot/modules/exif.py rename to sedenbot/modules/tools/exif.py diff --git a/sedenbot/modules/gdrive.py b/sedenbot/modules/tools/gdrive.py similarity index 100% rename from sedenbot/modules/gdrive.py rename to sedenbot/modules/tools/gdrive.py diff --git a/sedenbot/modules/git.py b/sedenbot/modules/tools/git_search.py similarity index 100% rename from sedenbot/modules/git.py rename to sedenbot/modules/tools/git_search.py diff --git a/sedenbot/modules/tools/google_search.py b/sedenbot/modules/tools/google_search.py new file mode 100644 index 0000000..0f8fcdf --- /dev/null +++ b/sedenbot/modules/tools/google_search.py @@ -0,0 +1,126 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from random import choice +from re import findall, sub +from traceback import format_exc + +from bs4 import BeautifulSoup +from requests import get + +from sedenbot import HELP, SEDEN_LANG +from sedenecem.core import ( + edit, + extract_args, + get_translation, + google_domains, + sedenify, + send_log, + useragent, +) + + +@sedenify(pattern='^.google') +def google(message): + match = extract_args(message) + if len(match) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + page = findall(r"page=\d+", match) + try: + page = page[0] + page = page.replace('page=', '') + match = match.replace('page=' + page[0], '') + page = int(page) + except BaseException: + page = 1 + msg = do_gsearch(match, page) + edit( + message, get_translation('googleResult', ['**', '`', match, msg]), preview=False + ) + + send_log(get_translation('googleLog', [match])) + + +def do_gsearch(query, page): + def find_page(num): + if num < 1: + num = 1 + return (num - 1) * 10 + + def parse_key(keywords): + return keywords.replace(' ', '+') + + def replacer(st): + return ( + sub(r'[`\*_]', '', st) + .replace('\n', ' ') + .replace('(', '〈') + .replace(')', '〉') + .replace('!', 'ⵑ') + .strip() + ) + + def get_result(res): + link = res.find('a', href=True) + title = res.find('h3') + if title: + title = title.text + desc = res.find( + 'div', attrs={'class': ['VwiC3b', 'yXK7lf', 'MUxGbd', 'yDYNvb', 'lyLwlc']} + ) + if desc: + desc = desc.text + + if link and title and desc: + return f'[{replacer(title)}]({link["href"]})\n{desc or ""}' + + query = parse_key(query) + page = find_page(page) + temp = f'/search?q={query}&start={find_page(page)}&hl={SEDEN_LANG}' + + req = get( + f'https://{choice(google_domains)}{temp}', + headers={ + 'User-Agent': useragent(), + 'Content-Type': 'text/html', + }, + ) + + retries = 0 + while req.status_code != 200 and retries < 10: + retries += 1 + req = get( + f'https://{choice(google_domains)}{temp}', + headers={ + 'User-Agent': useragent(), + 'Content-Type': 'text/html', + }, + ) + + soup = BeautifulSoup(req.text, 'html.parser') + res1 = soup.find_all('div', attrs={'class': 'g'}) + + out = '' + count = 0 + for res in res1: + try: + result = get_result(res) + if result: + count += 1 + out += f'{count} - {result}\n\n' + except Exception: + print(format_exc()) + print(res) + pass + + return out + + +HELP.update({'goolag': get_translation('googleInfo')}) diff --git a/sedenbot/modules/tools/image_search.py b/sedenbot/modules/tools/image_search.py new file mode 100644 index 0000000..c2c1784 --- /dev/null +++ b/sedenbot/modules/tools/image_search.py @@ -0,0 +1,101 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from mimetypes import guess_type +from os import remove +from random import choice +from re import findall +from time import sleep + +from pyrogram.types import InputMediaPhoto +from requests import get +from selenium.webdriver.common.by import By +from sedenbot import HELP + +from sedenecem.core import ( + edit, + extract_args, + get_translation, + get_webdriver, + google_domains, + reply_doc, + sedenify, +) + + +@sedenify(pattern='^.img') +def img(message): + query = extract_args(message) + lim = findall(r'lim=\d+', query) + try: + lim = lim[0] + lim = lim.replace('lim=', '') + query = query.replace('lim=' + lim[0], '') + lim = int(lim) + if lim > 10: + lim = 10 + except IndexError: + lim = 3 + + if len(query) < 1: + edit(message, f'`{get_translation("imgUsage")}`') + return + edit(message, f'`{get_translation("processing")}`') + + url = f'https://{choice(google_domains)}/search?tbm=isch&q={query}&gbv=2&sa=X&biw=1920&bih=1080' + driver = get_webdriver() + driver.get(url) + count = 1 + files = [] + for i in driver.find_elements( + By.XPATH, '//div[contains(@class,"isv-r PNCib MSM1fd BUooTd")]' + ): + i.click() + try_count = 0 + while ( + len( + element := driver.find_elements( + By.XPATH, + '//img[contains(@class,"n3VNCb") and contains(@src,"http")]', + ) + ) + < 1 + and try_count < 20 + ): + try_count += 1 + sleep(0.1) + if len(element) < 1: + continue + link = element[0].get_attribute('src') + filename = f'result_{count}.jpg' + try: + with open(filename, 'wb') as result: + result.write(get(link).content) + ftype = guess_type(filename) + if not ftype[0] or ftype[0].split('/')[1] not in ['png', 'jpg', 'jpeg']: + remove(filename) + continue + except Exception: + continue + files.append(InputMediaPhoto(filename)) + sleep(1) + elements = driver.find_elements(By.XPATH, '//a[contains(@class,"hm60ue")]') + for element in elements: + element.click() + count += 1 + if lim < count: + break + sleep(1) + + driver.quit() + + reply_doc(message, files, delete_orig=True, delete_after_send=True) + + +HELP.update({'img': get_translation('imgInfo')}) diff --git a/sedenbot/modules/tools/imei_check.py b/sedenbot/modules/tools/imei_check.py new file mode 100644 index 0000000..244c30d --- /dev/null +++ b/sedenbot/modules/tools/imei_check.py @@ -0,0 +1,62 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from re import findall + +from pyrogram import enums +from requests import post + +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, sedenify + + +@sedenify(pattern='^.imei(|check)') +def imeichecker(message): + imei = extract_args(message) + edit(message, f'`{get_translation("processing")}`') + if len(imei) != 15: + edit(message, f'`{get_translation("wrongCommand")}`') + return + try: + while True: + response = post( + f"https://m.turkiye.gov.tr/api2.php?p=imei-sorgulama&txtImei={imei}" + ).json() + if not response['data']['asyncFinished']: + continue + result = response['data'] + break + _marka = findall(r'Marka:(.+) Model', result['markaModel']) + _model = findall(r'Model Bilgileri:(.+)', result['markaModel']) + _pazaradi = findall(r'Pazar Adı:(.+) Marka', result['markaModel']) + marka = _marka[0].replace(',', '').strip() if _marka else None + model = _model[0].replace(',', '').strip() if _model else None + pazaradi = _pazaradi[0].replace(',', '').strip() if _pazaradi else None + reply_text = f"<b>Sorgu Tarihi:</b> <code>{result['sorguTarihi']}</code>\n\n" + reply_text += f"<b>IMEI:</b> <code>{result['imei'][:-5]+5*'*'}</code>\n" + reply_text += f"<b>Durum:</b> <code>{result['durum']}</code>\n" + reply_text += f"<b>Kaynak:</b> <code>{result['kaynak']}</code>\n" + reply_text += ( + f"<b>Pazar Adı:</b> <code>{pazaradi}</code>\n" + if pazaradi is not None + else "" + ) + reply_text += ( + f"<b>Marka:</b> <code>{marka}</code>\n" if marka is not None else "" + ) + reply_text += ( + f"<b>Model:</b> <code>{model}</code>\n\n" if model is not None else "" + ) + + edit(message, reply_text, parse=enums.parse_mode.ParseMode.HTML, preview=False) + except Exception as e: + raise e + + +HELP.update({'imeicheck': get_translation('imeiInfo')}) diff --git a/sedenbot/modules/kargotakip.py b/sedenbot/modules/tools/kargo_takip.py similarity index 100% rename from sedenbot/modules/kargotakip.py rename to sedenbot/modules/tools/kargo_takip.py diff --git a/sedenbot/modules/info.py b/sedenbot/modules/tools/profile_info.py similarity index 100% rename from sedenbot/modules/info.py rename to sedenbot/modules/tools/profile_info.py diff --git a/sedenbot/modules/qrcode.py b/sedenbot/modules/tools/qrcode.py similarity index 100% rename from sedenbot/modules/qrcode.py rename to sedenbot/modules/tools/qrcode.py diff --git a/sedenbot/modules/screencapture.py b/sedenbot/modules/tools/screencapture.py similarity index 100% rename from sedenbot/modules/screencapture.py rename to sedenbot/modules/tools/screencapture.py diff --git a/sedenbot/modules/sed.py b/sedenbot/modules/tools/sed.py similarity index 100% rename from sedenbot/modules/sed.py rename to sedenbot/modules/tools/sed.py diff --git a/sedenbot/modules/speedtest.py b/sedenbot/modules/tools/speedtest.py similarity index 100% rename from sedenbot/modules/speedtest.py rename to sedenbot/modules/tools/speedtest.py diff --git a/sedenbot/modules/tools/translate.py b/sedenbot/modules/tools/translate.py new file mode 100644 index 0000000..52890c9 --- /dev/null +++ b/sedenbot/modules/tools/translate.py @@ -0,0 +1,125 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from emoji import demojize +from googletrans import Translator, LANGUAGES +from gtts import gTTS +from gtts.lang import tts_langs +from sedenbot import HELP +from sedenecem.core import sedenify, extract_args, extract_args_split, edit, send_log, reply_voice, get_translation + + +@sedenify(pattern='^.tts') +def text_to_speech(message): + reply = message.reply_to_message + args = extract_args(message) + if args: + pass + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("ttsUsage")}`') + args = reply.text + else: + edit(message, f'`{get_translation("ttsUsage")}`') + return + + try: + gTTS(args, lang=TTS_LANG) + except AssertionError: + edit(message, f'`{get_translation("ttsBlank")}`') + return + except ValueError: + edit(message, f'`{get_translation("ttsNoSupport")}`') + return + except RuntimeError: + edit(message, f'`{get_translation("ttsError")}`') + return + tts = gTTS(args, lang=TTS_LANG) + tts.save('h.mp3') + with open('h.mp3', 'rb') as audio: + linelist = list(audio) + linecount = len(linelist) + if linecount == 1: + tts = gTTS(args, lang=TTS_LANG) + tts.save('h.mp3') + with open('h.mp3', 'r'): + reply_voice(reply if reply else message, 'h.mp3', delete_file=True) + + message.delete() + send_log(get_translation('ttsLog')) + + +@sedenify(pattern='^.trt') +def translate(message): + translator = Translator() + reply = message.reply_to_message + args = extract_args(message) + if args: + pass + elif reply: + if not reply.text: + return edit(message, f'`{get_translation("trtUsage")}`') + args = reply.text + else: + edit(message, f'`{get_translation("trtUsage")}`') + return + + try: + reply_text = translator.translate(demojize(args), dest=TRT_LANG) + except ValueError: + edit(message, f'`{get_translation("trtError")}`') + return + + source_lan = LANGUAGES[reply_text.src.lower()] + transl_lan = LANGUAGES[reply_text.dest.lower()] + reply_text = '{}\n{}'.format( + get_translation( + 'transHeader', ['**', '`', source_lan.title(), transl_lan.title()] + ), + reply_text.text, + ) + + edit(message, reply_text) + + send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) + + +@sedenify(pattern='^.lang') +def lang(message): + arr = extract_args_split(message) + + if len(arr) != 2: + edit(message, f'`{get_translation("wrongCommand")}`') + return + + util = arr[0].lower() + arg = arr[1].lower() + if util == 'trt': + scraper = get_translation('scraper1') + global TRT_LANG + if arg in LANGUAGES: + TRT_LANG = arg + LANG = LANGUAGES[arg] + else: + edit(message, get_translation('scraperTrt', ['`', LANGUAGES])) + return + elif util == 'tts': + scraper = get_translation('scraper2') + global TTS_LANG + if arg in tts_langs(): + TTS_LANG = arg + LANG = tts_langs()[arg] + else: + edit(message, get_translation('scraperTts', ['`', tts_langs()])) + return + edit(message, get_translation('scraperResult', ['`', scraper, LANG.title()])) + + send_log(get_translation('scraperLog', ['`', scraper, LANG.title()])) + +HELP.update({'translator': get_translation('translatorInfo')}) \ No newline at end of file diff --git a/sedenbot/modules/tools/urban_dictionary.py b/sedenbot/modules/tools/urban_dictionary.py new file mode 100644 index 0000000..ac58f28 --- /dev/null +++ b/sedenbot/modules/tools/urban_dictionary.py @@ -0,0 +1,62 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from json import loads +from os import path, remove +from random import randrange + +from requests import get + +from sedenbot import HELP +from sedenecem.core import edit, extract_args, get_translation, reply_doc, sedenify + + +@sedenify(pattern='^.ud') +def urbandictionary(message): + query = extract_args(message) + if len(query) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + edit(message, f'`{get_translation("processing")}`') + response = get(f'https://api.urbandictionary.com/v0/define?term={query}') + data = loads(response.text) + if len(data["list"]): + list_size = len(data['list']) + item = data['list'][randrange(list_size)] + meanlen = item['definition'] + item['example'] + if len(meanlen) >= 4096: + edit(message, f'`{get_translation("outputTooLarge")}`') + file = open('urban_dictionary.txt', 'w+') + file.write( + f"Query: {query}\n\n" + f"Meaning: {item['definition']}\n\n" + f"Example: \n{item['example']}\n" + ) + file.close() + reply_doc( + message, + 'urbandictionary.txt', + caption=f'`{get_translation("outputTooLarge")}`', + ) + if path.exists('urbandictionary.txt'): + remove('urbandictionary.txt') + message.delete() + return + edit( + message, + get_translation( + 'sedenQueryUd', + ['**', '`', query, item['definition'], item['example']], + ), + ) + else: + edit(message, get_translation('udNoResult', ['**', query])) + + +HELP.update({'ud': get_translation('udInfo')}) diff --git a/sedenbot/modules/weather.py b/sedenbot/modules/tools/weather.py similarity index 95% rename from sedenbot/modules/weather.py rename to sedenbot/modules/tools/weather.py index 0213f91..c4a2ae2 100644 --- a/sedenbot/modules/weather.py +++ b/sedenbot/modules/tools/weather.py @@ -36,4 +36,4 @@ def weather(message): edit(message, f'`{get_translation("weatherErrorServer")}`') -HELP.update({'weather': get_translation('infoWeather')}) +HELP.update({'weather': get_translation('infoWeather')}) \ No newline at end of file diff --git a/sedenbot/modules/tools/wiki_search.py b/sedenbot/modules/tools/wiki_search.py new file mode 100644 index 0000000..daeedc3 --- /dev/null +++ b/sedenbot/modules/tools/wiki_search.py @@ -0,0 +1,83 @@ +# Copyright (C) 2020-2024 TeamDerUntergang <https://github.com/TeamDerUntergang> +# +# This file is part of TeamDerUntergang project, +# and licensed under GNU Affero General Public License v3. +# See the GNU Affero General Public License for more details. +# +# All rights reserved. See COPYING, AUTHORS. +# + +from json import JSONDecodeError, loads + +from requests import RequestException, get + +from sedenbot import HELP, SEDEN_LANG +from sedenecem.core import ( + edit, + extract_args, + get_translation, + reply_doc, + sedenify, + send_log, +) + + +@sedenify(pattern='^.wiki') +def wiki(message): + args = extract_args(message) + if len(args) < 1: + edit(message, f'`{get_translation("wrongCommand")}`') + return + + try: + result = search_wiki(args) + except BaseException as e: + raise e + + if len(result) > 4096: + with open(f'{args}.txt', 'w', encoding='utf-8') as file: + file.write(result) + return reply_doc( + message, + f'{args}.txt', + caption=f'`{get_translation("outputTooLarge")}`', + delete_after_send=True, + ) + + edit(message, get_translation('sedenQuery', ['**', '`', args, result])) + send_log(get_translation('wikiLog', ['`', args])) + + +def search_wiki(query): + url = f'https://{SEDEN_LANG or "en"}.wikipedia.org/w/api.php' + params = { + 'action': 'query', + 'format': 'json', + 'prop': 'extracts', + 'titles': query, + 'exsectionformat': 'wiki', + 'explaintext': 1, + } + + try: + response = get(url, params=params) + response.raise_for_status() + data = loads(response.text) + pages = data.get('query', {}).get('pages', {}) + result = '' + + for page in pages.values(): + extract = page.get('extract', '') + result += extract + + if not result: + result = get_translation('wikiError') + + return result + + except (RequestException, JSONDecodeError) as e: + print(f'API Error: {e}') + return '' + + +HELP.update({'wiki': get_translation('wikiInfo')}) diff --git a/sedenbot/modules/youtubedl.py b/sedenbot/modules/tools/youtubedl.py similarity index 92% rename from sedenbot/modules/youtubedl.py rename to sedenbot/modules/tools/youtubedl.py index d0add78..0e8fc5e 100644 --- a/sedenbot/modules/youtubedl.py +++ b/sedenbot/modules/tools/youtubedl.py @@ -13,6 +13,8 @@ from PIL import Image from requests import get +from yt_dlp import YoutubeDL + from sedenbot import HELP from sedenecem.core import ( edit, @@ -23,7 +25,6 @@ reply_video, sedenify, ) -from yt_dlp import YoutubeDL @sedenify(pattern='^.y(outube|tdl)') @@ -40,15 +41,11 @@ def youtubedl(message): if util == 'mp4': ydl_opts = { 'outtmpl': '%(id)s.%(ext)s', - 'format': 'bestvideo[ext=mp4][height<=?1080]+bestaudio[ext=m4a]/best', + 'format': 'bestvideo[height<=?1080]+bestaudio[ext=m4a]/best', 'addmetadata': True, 'prefer_ffmpeg': True, 'geo_bypass': True, 'nocheckcertificate': True, - 'postprocessors': [ - {'key': 'FFmpegMetadata'}, - {'key': 'FFmpegVideoConvertor', 'preferedformat': 'mp4'}, - ], 'quiet': True, 'logtostderr': False, } @@ -84,7 +81,7 @@ def youtubedl(message): edit(message, f'`{get_translation("uploadMedia")}`') reply_video( message, - f'{video_info["id"]}.mp4', + f'{video_info["id"]}.{video_info["ext"]}', thumb=thumb_path, caption=f"**{get_translation('videoTitle')}** `{title}`\n**{get_translation('videoUploader')}** `{uploader}`", duration=duration, @@ -93,8 +90,8 @@ def youtubedl(message): ) try: remove(thumb_path) - except BaseException: - pass + except BaseException as e: + raise e elif util == 'mp3': ydl_opts = { @@ -136,7 +133,7 @@ def youtubedl(message): edit(message, f'`{get_translation("uploadMedia")}`') reply_audio( message, - f'{video_info["id"]}.mp3', + f'{video_info["id"]}.{video_info["audio_ext"]}', caption=f"**{get_translation('videoUploader')}** `{uploader}`", duration=duration, delete_orig=True, diff --git a/sedenecem/core/misc.py b/sedenecem/core/misc.py index ee0013f..ea4f4b3 100644 --- a/sedenecem/core/misc.py +++ b/sedenecem/core/misc.py @@ -8,6 +8,7 @@ # from os import makedirs +from random import choice from re import escape, sub from subprocess import STDOUT, DEVNULL, CalledProcessError, check_output from typing import List @@ -644,10 +645,10 @@ def useragent(): Returns: str: A random user agent string. """ - req = get('https://useragents.io/random') - soup = BeautifulSoup(req.text, 'html.parser') - agent = soup.find_all('td') - for i in agent: - return i.find('a').text - - return 'Googlebot/2.1 (+http://www.google.com/bot.html)' + try: + req = get('https://gist.githubusercontent.com/naytseyd/b4f924774f68cf57e54d646ba600abbc/raw/b8fc2569bb600fa338b26b148736007c133ef026') + user_agents = req.text.split('\n') + return choice(user_agents) + except Exception as e: + print("Error fetching user agent:", e) + return 'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0' diff --git a/sedenecem/core/replier.py b/sedenecem/core/replier.py index cbd2e9e..02f64a0 100644 --- a/sedenecem/core/replier.py +++ b/sedenecem/core/replier.py @@ -83,7 +83,8 @@ def reply_audio( message.delete() if delete_file: remove(audio) - except BaseException: + except BaseException as e: + raise e pass diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index f2d0494..6d0f40d 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -41,7 +41,7 @@ "alwaysOnline2": "Always online mode is already enabled!", "alwaysOnlineOff": "Always online mode disabled!", "alwaysOnlineOff2": "Always online mode is already disabled!", - "androidInfo": ".magisk\nGet latest Magisk releases\n\n.device <codename>\nUsage: Get info about android device codename or model.\n\n.codename <brand> <device>\nUsage: Search for android device codename.\n\n.specs <brand> <device>\nUsage: Get device specifications info.\n\n.twrp <codename>\nUsage: Get latest twrp download for android device.\n\n.orangefox <codename>\nUsage: Get latest OrangeFox download for android device.\n\n.phh <ABI>\nGet latest Phh releases", + "androidInfo": ".ksu\nGet latest KernelSU APK's\n\n.magisk\nGet latest Magisk releases\n\n.device <codename>\nUsage: Get info about android device codename or model.\n\n.codename <brand> <device>\nUsage: Search for android device codename.\n\n.specs <brand> <device>\nUsage: Get device specifications info.\n\n.twrp <codename>\nUsage: Get latest twrp download for android device.\n\n.orangefox <codename>\nUsage: Get latest OrangeFox download for android device.\n\n.phh <ABI>\nGet latest Phh releases", "androidPhhHeader": "%1Latest Phh %2AOSP Releases%1", "answerFromBot": "I didn't get an answer from bot!", "apiHashError": "API HASH Not Set. Please check your config.env file.", @@ -201,6 +201,7 @@ "gdriveUpComplete": "%1Filename:%1 %2%3%2\n%1File Uploaded.%1", "gdriveUsage": ".gauth\nIt needed for authentication.It give you auth url.\n\n.gauth token <URL>\nGive the url you received from .gauth command for saving token\n\n.gauth revoke\nDelete token from db\n\n.gupload <reply_message> or <link>\nReply files or paste link for uploading to google drive.\n\n.gdownload <link>\nPaste link for uploading files to telegram.Limit 2GB", "geniusToken": "Please set the Genius token. Thank you!", + "getPasteOut": "%1Fetched Pastebin URL content successfully!\n\nContent:%1 %2", "gitAccount": "Account Type", "gitBio": "Bio", "gitCompany": "Company", @@ -264,6 +265,7 @@ "kickProcess": "Kicking\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4kicked!%4\n%5", "kickmeResult": "Goodbye.. i go away \ud83e\udd20", + "ksuReleases": "Latest KernelSU APKs:", "langName": "English", "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key missing! Add it to config vars.%2", "lastfmInfo": ".lastfm\nUsage: Shows currently scrobbling track or most recent scrobbles if nothing is playing.", @@ -643,7 +645,6 @@ "wrongFilter": "Filter is wrong!", "wrongMedia": "Wrong media type!", "wrongUrl": "Wrong URL!", - "getPasteOut": "%1Fetched Pastebin URL content successfully!\n\nContent:%1 %2", "yadiskError": "Error: File not found / Download limit exceeded", "youtubedlInfo": ".youtubedl <mp3 or mp4> <url>\nUsage: Downloads video or audio from the link you provided.", "zalUsage": "g\u036b \u0306 i\u031b \u033a v\u0347\u0306 e\u030f\u0345 a\u0322\u0366 s\u0334\u032a c\u0322\u0338 a\u0338\u0308 r\u0369\u0363 y\u0356\u035e t\u0328\u035a e\u0320\u0301 x\u0322\u0356 t\u035b\u0354", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 780cae3..2a8e958 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -41,7 +41,7 @@ "alwaysOnline2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten etkinle\u015ftirildi!", "alwaysOnlineOff": "S\u00fcrekli \u00e7evrimi\u00e7i modu devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", "alwaysOnlineOff2": "S\u00fcrekli \u00e7evrimi\u00e7i modu zaten devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131!", - "androidInfo": ".magisk\nG\u00fcncel Magisk s\u00fcr\u00fcmleri\n\n.device <kod ad\u0131>\nKullan\u0131m: Android cihaz\u0131 hakk\u0131nda bilgi\n\n.codename <marka> <cihaz>\nKullan\u0131m: Android cihaz kod adlar\u0131n\u0131 aray\u0131n.\n\nspecs <marka> <cihaz>\nKullan\u0131m: Cihaz \u00f6zellikleri hakk\u0131nda bilgi al\u0131n.\n\n.twrp <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel TWRP s\u00fcr\u00fcmlerini al\u0131n.\n\n.orangefox <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel OrangeFox Recovery s\u00fcr\u00fcmlerini al\u0131n.\n\n.phh <mimari>\nG\u00fcncel Phh AOSP s\u00fcr\u00fcmlerini al\u0131n.", + "androidInfo": ".ksu\nG\u00fcncel KernelSU s\u00fcr\u00fcmleri\n\n.magisk\nG\u00fcncel Magisk s\u00fcr\u00fcmleri\n\n.device <kod ad\u0131>\nKullan\u0131m: Android cihaz\u0131 hakk\u0131nda bilgi\n\n.codename <marka> <cihaz>\nKullan\u0131m: Android cihaz kod adlar\u0131n\u0131 aray\u0131n.\n\nspecs <marka> <cihaz>\nKullan\u0131m: Cihaz \u00f6zellikleri hakk\u0131nda bilgi al\u0131n.\n\n.twrp <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel TWRP s\u00fcr\u00fcmlerini al\u0131n.\n\n.orangefox <kod ad\u0131>\nKullan\u0131m: Hedeflenen cihaz i\u00e7in resmi olan g\u00fcncel OrangeFox Recovery s\u00fcr\u00fcmlerini al\u0131n.\n\n.phh <mimari>\nG\u00fcncel Phh AOSP s\u00fcr\u00fcmlerini al\u0131n.", "androidPhhHeader": "%1G\u00fcncel PHH %2AOSP S\u00fcr\u00fcmleri%1", "answerFromBot": "Botdan cevap alamad\u0131m!", "apiHashError": "API HASH Ayarlanmad\u0131. L\u00fctfen config.env dosyas\u0131n\u0131z kontrol edin.", @@ -202,6 +202,7 @@ "gdriveUpComplete": "%1Dosya Ad\u0131:%1 %2%3%2\n%1Dosya Y\u00fcklendi.%1", "gdriveUsage": ".gauth \nYetkilendirme i\u00e7in gereklidir.\n\n.gauth token <URL>\n.gauth kullanarak ald\u0131\u011f\u0131n\u0131z url yi girin.Tokeni kaydeder.\n\n.gauth revoke\nGoogle drive tokenini siler.\n\n.gupload <reply_message> yada <link>\nTelegrama at\u0131lm\u0131\u015f bir dosyay\u0131 .gupload ile yan\u0131tlay\u0131n veya link belirtin.Yant\u0131lanan dosyay\u0131 veya linki google drive'a y\u00fckler.\n\n.gdownload <link>\nKi\u015fisel drive'\u0131n\u0131zdan veya google drive linkinden dosyay\u0131 indirir ve telegrama y\u00fckler.S\u0131n\u0131r 2GB", "geniusToken": "L\u00fctfen Genius tokeni ayarlay\u0131n\u0131z. Te\u015fekk\u00fcrler!", + "getPasteOut": "%1Pastebin i\u00e7eri\u011fi ba\u015far\u0131yla getirildi!\n\n\u0130\u00e7erik:%1 %2", "gitAccount": "Kullan\u0131c\u0131 tipi", "gitBio": "Biyografi", "gitCompany": "\u015eirket", @@ -266,6 +267,7 @@ "kickProcess": "\u00c7\u0131kart\u0131l\u0131yor\u2026", "kickResult": "%1[%2](tg://user?id=%3)%1 %4gruptan at\u0131ld\u0131!%4\n%5", "kickmeResult": "G\u00fcle G\u00fcle ben gidiyorum \ud83e\udd20", + "ksuReleases": "G\u00fcncel KernelSU APK:", "langName": "T\u00fcrk\u00e7e", "lastfmApiMissing": "%1[Last.fm](https://www.last.fm/api/account/create)%1 %2API key eksik! L\u00fctfen ekleyin.%2", "lastfmInfo": ".lastfm\nKullan\u0131m: Anl\u0131k oynat\u0131lan par\u00e7a ya da en son oynat\u0131lan par\u00e7a g\u00f6sterilir.", @@ -643,7 +645,6 @@ "wrongFilter": "Filtre hatal\u0131!", "wrongMedia": "Ge\u00e7ersiz medya t\u00fcr\u00fc!", "wrongUrl": "Hatal\u0131 link!", - "getPasteOut": "%1Pastebin içeriği başarıyla getirildi!\n\nİçerik:%1 %2", "yadiskError": "Hata: Dosya bulunamad\u0131 / \u0130ndirme limiti a\u015f\u0131lm\u0131\u015ft\u0131r", "youtubedlInfo": ".youtubedl <mp3 ya da mp4> <link>\nKullan\u0131m: Verdi\u011finiz linki video ya da ses olarak indirir.", "zalUsage": "\uff22\u036c\u033a\uff41\u0351\u0320\uff4e\u0335\u0309\uff41\u032c\u035c \uff42\u0354\u0336\uff49\u033c\u035a\uff52\u0348\u035e \uff4d\u033c\u0358\uff45\u0328\u031d\uff54\u0354\u0359\uff49\u036e\u0322\uff4e\u031c\u0357 \uff56\u0362\u035c\uff45\u0350\u0317\uff52\u036e\u0334", From 6a3224c759afff377c16d2d749198830722bb217 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 8 Mar 2024 21:57:50 +0300 Subject: [PATCH 231/242] Release Seden v1.8.0 --- sedenbot/__init__.py | 2 +- sedenbot/modules/chat/chat.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 6e0b9bc..6fddabf 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -114,7 +114,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.7.9' +BOT_VERSION = '1.8.0' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' diff --git a/sedenbot/modules/chat/chat.py b/sedenbot/modules/chat/chat.py index 60240e3..2212510 100644 --- a/sedenbot/modules/chat/chat.py +++ b/sedenbot/modules/chat/chat.py @@ -81,8 +81,8 @@ def keep_read(message): @sedenify(pattern='^.call') def call_notes(message): try: - from sedenbot.modules.deneme.notes import get_note - from sedenbot.modules.var.snips import get_snip + from sedenbot.modules.chat.notes import get_note + from sedenbot.modules.chat.snips import get_snip except BaseException: edit(message, f'`{get_translation("nonSqlMode")}`') return From 7cacde1b944c9de5db481e17b5b298ee569dd714 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Fri, 8 Mar 2024 22:03:21 +0300 Subject: [PATCH 232/242] fix function duplicate --- sedenbot/modules/fun/carbon_text.py | 39 ----------------------------- 1 file changed, 39 deletions(-) diff --git a/sedenbot/modules/fun/carbon_text.py b/sedenbot/modules/fun/carbon_text.py index 826e183..36767d4 100644 --- a/sedenbot/modules/fun/carbon_text.py +++ b/sedenbot/modules/fun/carbon_text.py @@ -82,43 +82,4 @@ def carbon(message): driver.quit() -@sedenify(pattern='^.tts') -def text_to_speech(message): - reply = message.reply_to_message - args = extract_args(message) - if args: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("ttsUsage")}`') - args = reply.text - else: - edit(message, f'`{get_translation("ttsUsage")}`') - return - - try: - gTTS(args, lang=TTS_LANG) - except AssertionError: - edit(message, f'`{get_translation("ttsBlank")}`') - return - except ValueError: - edit(message, f'`{get_translation("ttsNoSupport")}`') - return - except RuntimeError: - edit(message, f'`{get_translation("ttsError")}`') - return - tts = gTTS(args, lang=TTS_LANG) - tts.save('h.mp3') - with open('h.mp3', 'rb') as audio: - linelist = list(audio) - linecount = len(linelist) - if linecount == 1: - tts = gTTS(args, lang=TTS_LANG) - tts.save('h.mp3') - with open('h.mp3', 'r'): - reply_voice(reply if reply else message, 'h.mp3', delete_file=True) - - message.delete() - send_log(get_translation('ttsLog')) - HELP.update({'carbon': get_translation('carbonInfo')}) From 162d390a8ad36b262fa14d1522aa56ffd716adc6 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 12 Mar 2024 11:44:21 +0300 Subject: [PATCH 233/242] ezanvakti: enable ramazan function --- sedenbot/modules/miscs/ezanvakti.py | 54 ++++++++++++++++------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/sedenbot/modules/miscs/ezanvakti.py b/sedenbot/modules/miscs/ezanvakti.py index 1d36317..7ad17a7 100644 --- a/sedenbot/modules/miscs/ezanvakti.py +++ b/sedenbot/modules/miscs/ezanvakti.py @@ -7,9 +7,9 @@ # All rights reserved. See COPYING, AUTHORS. # +from datetime import datetime, timedelta from functools import reduce from re import DOTALL, sub -from time import localtime from bs4 import BeautifulSoup from requests import get @@ -29,7 +29,7 @@ def ezanvakti(message): except BaseException: return edit(message, f'`{konum} için bir bilgi bulunamadı.`') res1 = result.body.find('div', {'class': 'body-content'}) - res1 = res1.find('script') # type: ignore + res1 = res1.find('script') # type: ignore res1 = sub( r'<script>|</script>|\r|{.*?}|\[.*?\]|\n ', '', str(res1), flags=DOTALL ) @@ -54,7 +54,7 @@ def get_val(st): edit(message, vakitler) -""" + @sedenify(pattern='^.ramazan') def ramazan(message): konum = extract_args(message).lower() @@ -78,32 +78,38 @@ def get_val(st): res2 = get_val(res1[1]) res3 = get_val(res1[2]) - current_time = localtime() - current_hour = current_time.tm_hour - current_minute = current_time.tm_min - sahur_vakti, iftar_vakti, teravih_vakti = res3[0], res3[4], res3[5] - def get_remaining_time(vakt, current_hour, current_minute): - vakt_time = vakt.split(':') - vakt_hour = int(vakt_time[0]) - vakt_minute = int(vakt_time[1]) + def get_remaining_time(vakt): + vakt_time = datetime.strptime(vakt, '%H:%M') + current_time = datetime.now() - if current_hour < vakt_hour or ( - current_hour == vakt_hour and current_minute < vakt_minute - ): - minutes_left = (vakt_hour - current_hour) * 60 + ( - vakt_minute - current_minute + if current_time < vakt_time: + time_left = vakt_time - current_time + else: + tomorrow = current_time + timedelta(days=1) + tomorrow_date = datetime( + tomorrow.year, + tomorrow.month, + tomorrow.day, + vakt_time.hour, + vakt_time.minute, ) - hours_left = minutes_left // 60 - minutes_left = minutes_left % 60 - return f'{vakt} ({hours_left}s {minutes_left}dk kaldı)' + time_left = tomorrow_date - current_time + + hours_left = time_left.seconds // 3600 + minutes_left = (time_left.seconds % 3600) // 60 + + if hours_left == 0: + return f'{vakt} ({minutes_left} dakika kaldı)' + elif minutes_left == 0: + return f'{vakt} ({hours_left} saat kaldı)' else: - return f'{vakt}' + return f'{vakt} ({hours_left} saat {minutes_left} dakika kaldı)' - sahur = get_remaining_time(sahur_vakti, current_hour, current_minute) - iftar = get_remaining_time(iftar_vakti, current_hour, current_minute) - teravih = get_remaining_time(teravih_vakti, current_hour, current_minute) + sahur = get_remaining_time(sahur_vakti) + iftar = get_remaining_time(iftar_vakti) + teravih = get_remaining_time(teravih_vakti) vakitler = ( '**Diyanet Ramazan Vakitleri**\n\n' @@ -115,7 +121,7 @@ def get_remaining_time(vakt, current_hour, current_minute): ) edit(message, vakitler) -""" + def find_loc(konum): if konum.isdigit(): From fd78d057c62b0be16b6fbb7ea616f545d1d8beaf Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 12 Mar 2024 16:11:35 +0300 Subject: [PATCH 234/242] improve kargo_takip module --- sedenbot/modules/tools/kargo_takip.py | 113 +++++++++++++------------- sedenecem/translator/en.json | 2 +- sedenecem/translator/tr.json | 2 +- 3 files changed, 60 insertions(+), 57 deletions(-) diff --git a/sedenbot/modules/tools/kargo_takip.py b/sedenbot/modules/tools/kargo_takip.py index edf4d56..3edc5e1 100644 --- a/sedenbot/modules/tools/kargo_takip.py +++ b/sedenbot/modules/tools/kargo_takip.py @@ -8,9 +8,11 @@ # from json import JSONDecodeError +from re import sub +from typing import Union from pyrogram import enums -from requests import get +from requests import post from sedenbot import HELP from sedenecem.core import ( edit, @@ -21,62 +23,65 @@ ) +def format_datetime(datetime_str): + if datetime_str: + date_part, time_part = datetime_str.split('T') + formatted_datetime = f"{date_part} {time_part.replace(':', ':')}" + return formatted_datetime + else: + return None + + +def format_prefix(text): + if text: + if ":" in text: + formatted_text = sub(r':\s*', ':', text).split(':', 1)[-1].strip() + else: + formatted_text = text + else: + formatted_text = None + return formatted_text + + def parseShipEntity(jsonEntity: dict) -> str: - text = get_translation( - 'shippingResult', - [ - '<b>', - '</b>', - '<code>', - '</code>', - jsonEntity['data']['company'].title(), - jsonEntity['data']['tracking_no'], - jsonEntity['data']['status'], - jsonEntity['data']['sender_name'], - jsonEntity['data']['receiver_name'], - jsonEntity['data']['sender_unit'], - jsonEntity['data']['receiver_unit'], - jsonEntity['data']['sended_date'], - jsonEntity['data']['delivered_date'] or get_translation('notFound'), - ], - ) - if jsonEntity['data']['movements']: - movements = get_translation( - 'shippingMovements', - [ - '<b>', - '</b>', - '<u>', - '</u>', - '<code>', - '</code>', - jsonEntity['data']['movements'][0]['unit'], - jsonEntity['data']['movements'][0]['status'], - jsonEntity['data']['movements'][0]['date'], - jsonEntity['data']['movements'][0]['time'], - jsonEntity['data']['movements'][0]['action'], - ], + if not jsonEntity['value'][0]['success']: + return '<code>Kargo bulunamadı</code>' + else: + text = ( + f"<b>Firma:</b> <code>{jsonEntity['value'][0]['value']['companyName']}</code>\n" + f"<b>Takip No:</b> <code>{jsonEntity['value'][0]['value']['barcode']}</code>\n" + f"<b>Durum:</b> <code>{jsonEntity['value'][0]['value']['statusDescription']}</code>\n" + f"<b>Gönderici:</b> <code>{format_prefix(jsonEntity['value'][0]['value']['sender']) or 'Bulunamadı'}</code>\n" + f"<b>Alıcı:</b> <code>{format_prefix(jsonEntity['value'][0]['value']['receiver']) or 'Bulunamadı'}</code>\n" + f"<b>Gönderim Yeri:</b> <code>{format_prefix(jsonEntity['value'][0]['value']['senderAddress']) or 'Bulunamadı'}</code>\n" + f"<b>Alım Yeri:</b> <code>{format_prefix(jsonEntity['value'][0]['value']['receiverAddress']) or 'Bulunamadı'}</code>\n" + f"<b>Gönderi Tarihi:</b> <code>{format_datetime(jsonEntity['value'][0]['value']['sendDate']) or 'Bulunamadı'}</code>\n" + f"<b>Teslim Tarihi:</b> <code>{format_datetime(jsonEntity['value'][0]['value']['deliveredDate']) or 'Bulunamadı'}</code>" ) + if jsonEntity['value'][0]['value']['movement']: + last_movement = jsonEntity['value'][0]['value']['movement'][-1] + movements = ( + f"\n\n<b><u>Son hareketler</u></b>\n\n" + f"<code>Yer: {format_prefix(last_movement['externalLocation'])}</code>\n" + f"<code>Durum: {last_movement['title']}</code>\n" + f"<code>Tarih: {format_datetime(last_movement['date'])}</code>\n" + ) + text += movements + return text - text += movements - return text +def getShipEntity(company: str, trackId: Union[int, str]): + url = 'https://kargomnerede.com.tr/api/search-codes' + data = {"barcodes": [{"companyId": company, "code": trackId}]} -def getShipEntity(company: str, trackId: int or str) -> dict or None: - headers: dict = { - 'platform': 'Android', - 'public': 'lfJGmU9XpGZcMwyLtZBk', - 'secret': 'sMPMnQuc51nmcBbaeOK1', - 'unique': 'afb612018716663e', - } - response = get(f'https://tapi.kolibu.com/{company}/{trackId}', headers=headers) try: + response = post(url, json=data) return response.json() if response.json()['success'] else None except JSONDecodeError: return None -@sedenify(pattern='^.(hepsijet|trendyol|yurti[cç]i|(s[uü]ra|pt)t|aras|mng|ups)') +@sedenify(pattern='^.(hepsijet|yurti[cç]i|(s[uü]ra|pt)t|aras|mng|ups)') def shippingTrack(message): edit(message, f"`{get_translation('processing')}`") trackId = extract_args_split(message) @@ -86,21 +91,19 @@ def shippingTrack(message): return match comp.replace('ç', 'c').replace('ü', 'u'): case 'yurtici': - kargo_data = getShipEntity(company='yurtici', trackId=trackId[0]) + kargo_data = getShipEntity(company='2', trackId=trackId[0]) case 'aras': - kargo_data = getShipEntity(company='aras', trackId=trackId[0]) + kargo_data = getShipEntity(company='1', trackId=trackId[0]) case 'ptt': - kargo_data = getShipEntity(company='ptt', trackId=trackId[0]) + kargo_data = getShipEntity(company='4', trackId=trackId[0]) case 'mng': - kargo_data = getShipEntity(company='mng', trackId=trackId[0]) + kargo_data = getShipEntity(company='3', trackId=trackId[0]) case 'ups': - kargo_data = getShipEntity(company='ups', trackId=trackId[0]) + kargo_data = getShipEntity(company='5', trackId=trackId[0]) case 'surat': - kargo_data = getShipEntity(company='surat', trackId=trackId[0]) - case 'trendyol': - kargo_data = getShipEntity(company='trendyolexpress', trackId=trackId[0]) + kargo_data = getShipEntity(company='6', trackId=trackId[0]) case 'hepsijet': - kargo_data = getShipEntity(company='hepsijet', trackId=trackId[0]) + kargo_data = getShipEntity(company='10', trackId=trackId[0]) if kargo_data: text = parseShipEntity(kargo_data) edit(message, text, parse=enums.ParseMode.HTML) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 6d0f40d..f7be2e3 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -505,7 +505,7 @@ "shippingMovements": "\n\n%1%3Last movement%4%2\n\n%5Unit: %7\nStatus: %8\nDate: %9\nTime: %10\nAction: %11%6", "shippingNoResult": "Tracking information not found!", "shippingResult": "%1Company:%2 %3%5%4\n%1Track ID:%2 %3%6%4\n%1Status:%2 %3%7%4\n%1Sender:%2 %3%8%4\n%1Receiver:%2 %3%9%4\n%1Sender Unit:%2 %3%10%4\n%1Receiver Unit:%2 %3%11%4\n%1Sent Date:%2 %3%12%4\n%1Delivered Date:%2 %3%13%4", - "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", + "shippingTrack": ".<company> <trackNo>\n\nUsage: Shows shipping information.\n\nExample: `.ups 1234567890`\n\nAllowed companies:\n`mng, ptt, ups, aras, surat, yurtici, hepsijet`", "showWelcome": "I'm currently welcoming new users with this welcome message", "shutdown": "Goodbye *Windows XP shutdown sound\u2026", "shutdownLog": "#SHUTDOWN\nBot shut down", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 2a8e958..7f30a28 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -505,7 +505,7 @@ "shippingMovements": "\n\n%1%3Son hareket%4%2\n\n%5Yer: %7\nDurum: %8\nTarih: %9\nZaman: %10\n\u0130\u015flem: %11%6", "shippingNoResult": "Takip bilgisi bulunamad\u0131!", "shippingResult": "%1Firma:%2 %3%5%4\n%1Takip No:%2 %3%6%4\n%1Durum:%2 %3%7%4\n%1G\u00f6nderici:%2 %3%8%4\n%1Al\u0131c\u0131:%2 %3%9%4\n%1G\u00f6nderim yeri:%2 %3%10%4\n%1Al\u0131m yeri:%2 %3%11%4\n%1G\u00f6nderi tarihi:%2 %3%12%4\n%1Teslim tarihi:%2 %3%13%4", - "shippingTrack": ".<firma> <takipNo>\n\nKullan\u0131m: Kargo bilgilerini g\u00f6sterir.\n\n\u00d6rnek: `.ups 1234567890`\n\nDesteklenen firmalar:\n`mng, ptt, ups, aras, surat, yurtici, trendyol, hepsijet`", + "shippingTrack": ".<firma> <takipNo>\n\nKullan\u0131m: Kargo bilgilerini g\u00f6sterir.\n\n\u00d6rnek: `.ups 1234567890`\n\nDesteklenen firmalar:\n`mng, ptt, ups, aras, surat, yurtici, hepsijet`", "showWelcome": "\u015eu anda a\u015fa\u011f\u0131daki kar\u015f\u0131lama mesaj\u0131 ile yeni kullan\u0131c\u0131lar\u0131 a\u011f\u0131rl\u0131yorum", "shutdown": "Ben kapan\u0131yorum, g\u00f6r\u00fc\u015f\u00fcr\u00fcz\u2026", "shutdownLog": "#SHUTDOWN\nBot kapat\u0131ld\u0131.", From 43bca5e649f749a67e5e6a393e52839d37e9be1b Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Tue, 12 Mar 2024 16:13:06 +0300 Subject: [PATCH 235/242] Release Seden v1.8.1 --- sedenbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index 6fddabf..adb1c62 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -114,7 +114,7 @@ def set_logger(): LOGS.warn(get_translation('apiHashError')) quit(1) -BOT_VERSION = '1.8.0' +BOT_VERSION = '1.8.1' SUPPORT_GROUP = 'SedenUserBotSupport' CHANNEL = 'SedenUserBot' From 771170f46796cf5c7859e16049c62da31cb841cd Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 14 Mar 2024 00:35:26 +0300 Subject: [PATCH 236/242] improve text to speech and translate functions --- sedenbot/modules/tools/translate.py | 128 +++++++++++++++------------- sedenecem/translator/en.json | 8 +- sedenecem/translator/tr.json | 8 +- 3 files changed, 69 insertions(+), 75 deletions(-) diff --git a/sedenbot/modules/tools/translate.py b/sedenbot/modules/tools/translate.py index 52890c9..0593be3 100644 --- a/sedenbot/modules/tools/translate.py +++ b/sedenbot/modules/tools/translate.py @@ -11,26 +11,51 @@ from googletrans import Translator, LANGUAGES from gtts import gTTS from gtts.lang import tts_langs -from sedenbot import HELP -from sedenecem.core import sedenify, extract_args, extract_args_split, edit, send_log, reply_voice, get_translation +from sedenbot import HELP, SEDEN_LANG +from sedenecem.core import ( + sedenify, + extract_args_split, + edit, + send_log, + reply_voice, + get_translation, +) @sedenify(pattern='^.tts') def text_to_speech(message): reply = message.reply_to_message - args = extract_args(message) - if args: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("ttsUsage")}`') - args = reply.text + args = extract_args_split(message) + + if reply and reply.text: + if args: + lang = args[0].lower() + text = reply.text + else: + lang = SEDEN_LANG + text = reply.text + elif args: + if len(args) >= 2: + lang = args[0].lower() + text = ' '.join(args[1:]) + else: + lang = args[0].lower() + text = '' else: edit(message, f'`{get_translation("ttsUsage")}`') return + if lang not in tts_langs(): + lang = SEDEN_LANG + text = ' '.join(args) + + if not text: + edit(message, f'`{get_translation("ttsUsage")}`') + return + try: - gTTS(args, lang=TTS_LANG) + tts = gTTS(text, lang=lang) + tts.save('h.mp3') except AssertionError: edit(message, f'`{get_translation("ttsBlank")}`') return @@ -40,13 +65,12 @@ def text_to_speech(message): except RuntimeError: edit(message, f'`{get_translation("ttsError")}`') return - tts = gTTS(args, lang=TTS_LANG) - tts.save('h.mp3') + with open('h.mp3', 'rb') as audio: linelist = list(audio) linecount = len(linelist) if linecount == 1: - tts = gTTS(args, lang=TTS_LANG) + tts = gTTS(text, lang=lang) tts.save('h.mp3') with open('h.mp3', 'r'): reply_voice(reply if reply else message, 'h.mp3', delete_file=True) @@ -59,67 +83,49 @@ def text_to_speech(message): def translate(message): translator = Translator() reply = message.reply_to_message - args = extract_args(message) - if args: - pass - elif reply: - if not reply.text: - return edit(message, f'`{get_translation("trtUsage")}`') - args = reply.text + args = extract_args_split(message) + + if reply and reply.text: + if args: + dest_lang = args[0].lower() + text = reply.text + else: + dest_lang = SEDEN_LANG + text = reply.text + elif args: + if len(args) == 2: + lang, text = args + if lang.lower() in LANGUAGES: + dest_lang = lang.lower() + text = text + else: + dest_lang = SEDEN_LANG + text = ' '.join(args) + else: + dest_lang = SEDEN_LANG + text = ' '.join(args) else: edit(message, f'`{get_translation("trtUsage")}`') return try: - reply_text = translator.translate(demojize(args), dest=TRT_LANG) + translated_text = translator.translate(demojize(text), dest=dest_lang) except ValueError: edit(message, f'`{get_translation("trtError")}`') return - source_lan = LANGUAGES[reply_text.src.lower()] - transl_lan = LANGUAGES[reply_text.dest.lower()] - reply_text = '{}\n{}'.format( + source_lang = LANGUAGES[translated_text.src.lower()] + transl_lang = LANGUAGES[translated_text.dest.lower()] + translated_text = '{}\n{}'.format( get_translation( - 'transHeader', ['**', '`', source_lan.title(), transl_lan.title()] + 'transHeader', ['**', '`', source_lang.title(), transl_lang.title()] ), - reply_text.text, + translated_text.text, ) - edit(message, reply_text) - - send_log(get_translation('trtLog', [source_lan.title(), transl_lan.title()])) - - -@sedenify(pattern='^.lang') -def lang(message): - arr = extract_args_split(message) + edit(message, translated_text) - if len(arr) != 2: - edit(message, f'`{get_translation("wrongCommand")}`') - return - - util = arr[0].lower() - arg = arr[1].lower() - if util == 'trt': - scraper = get_translation('scraper1') - global TRT_LANG - if arg in LANGUAGES: - TRT_LANG = arg - LANG = LANGUAGES[arg] - else: - edit(message, get_translation('scraperTrt', ['`', LANGUAGES])) - return - elif util == 'tts': - scraper = get_translation('scraper2') - global TTS_LANG - if arg in tts_langs(): - TTS_LANG = arg - LANG = tts_langs()[arg] - else: - edit(message, get_translation('scraperTts', ['`', tts_langs()])) - return - edit(message, get_translation('scraperResult', ['`', scraper, LANG.title()])) + send_log(get_translation('trtLog', [source_lang.title(), transl_lang.title()])) - send_log(get_translation('scraperLog', ['`', scraper, LANG.title()])) -HELP.update({'translator': get_translation('translatorInfo')}) \ No newline at end of file +HELP.update({'translator': get_translation('translatorInfo')}) diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index f7be2e3..962a62e 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -477,12 +477,6 @@ "runstr8": "You run, you die.", "runstr9": "Jokes on you, I'm everywhere", "safeEval": "This may not be a safe eval query.", - "scraper1": "Translator", - "scraper2": "Text to Speech", - "scraperLog": "%1Language for %2 changed to %3.%1", - "scraperResult": "%1Language for %2 changed to %3.%1", - "scraperTrt": "%1Invalid Language code !%1\n%1Available language codes for TRT%1:\n\n%1%2%1", - "scraperTts": "%1Invalid Language code !%1\n%1Available language codes for TTS%1:\n\n%1%2%1", "sedError": "I don't have brains. Well you too don't I guess.", "sedError2": "That's a reply. Don't use sed", "sedInfo": "sed<delimiter><old word(s)><delimiter><new word(s)>\nUsage: Replaces a word or words using sed.\nDelimiters: /, :, |, _", @@ -565,7 +559,7 @@ "testLogId": "Testing LOG_ID value\u2026", "tgUpLimit": "%1File size too big.I cant do that.%1", "transHeader": "%1From:%1 %2%3%2\n%1To:%1 %2%4%2\n", - "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\nUse .lang trt <language code> to set language for trt. (Default is English)\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.\nUse .lang tts <language code> to set language for tts. (Default is English)", + "translatorInfo": ".trt <text> [or reply]\nUsage: Translates text to the language which is set.\n\n.tts <text> [or reply]\nUsage: Translates text to speech for the language which is set.", "trtError": "Invalid destination language.", "trtLog": "Translated some %1 stuff to %2 just now.", "trtUsage": "Give a text or reply to a message to translate!", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 7f30a28..4cd6be8 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -477,12 +477,6 @@ "runstr8": "Ka\u00e7arsan, \u00f6l\u00fcrs\u00fcn.", "runstr9": "\u015eakac\u0131 seni, Ben heryerdeyim.", "safeEval": "Bu g\u00fcvenli bir eval sorgusu olmayabilir.", - "scraper1": "\u00c7eviri", - "scraper2": "Yaz\u0131dan sese", - "scraperLog": "%1%2 mod\u00fcl\u00fc i\u00e7in varsay\u0131lan dil %3 diline \u00e7evirildi.%1", - "scraperResult": "%1%2 mod\u00fcl\u00fc i\u00e7in varsay\u0131lan dil %3 diline \u00e7evirildi.%1", - "scraperTrt": "%1Ge\u00e7ersiz dil kodu!%1\n%1Ge\u00e7erli dil kodlar\u0131:%1\n\n%1%2%1", - "scraperTts": "%1Ge\u00e7ersiz dil kodu!%1\n%1Ge\u00e7erli dil kodlar\u0131:%1\n\n%1%2%1", "sedError": "Bunun i\u00e7in yeterli zek\u00e2ya sahip de\u011filim.", "sedError2": "Bu bir yan\u0131tlama. Sed kullanma", "sedInfo": "sed<s\u0131n\u0131rlay\u0131c\u0131><eski kelime(ler)><s\u0131n\u0131rlay\u0131c\u0131><yeni kelime(ler)>\nKullan\u0131m: Sed kullanarak bir kelimeyi veya kelimeleri de\u011fi\u015ftirir.\nS\u0131n\u0131rlay\u0131c\u0131lar: /, :, |, _", @@ -565,7 +559,7 @@ "testLogId": "LOG_ID de\u011feri test ediliyor\u2026", "tgUpLimit": "%1Dosya boyutu fazla oldu\u011fu i\u00e7in bunu yapamam.%1", "transHeader": "%1Kaynak:%1 %2%3%2\n%1Hedef:%1 %2%4%2\n", - "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n.lang trt komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.\n.lang tts komutuyla varsay\u0131lan dili ayarlayabilirsin. (T\u00fcrk\u00e7e ayarl\u0131 geliyor merak etme)", + "translatorInfo": ".trt <metin>\nKullan\u0131m: Basit bir \u00e7eviri mod\u00fcl\u00fc.\n\n.tts <metin>\nKullan\u0131m: Metni sese d\u00f6n\u00fc\u015ft\u00fcr\u00fcr.", "trtError": "Ayarlanan hedef dil ge\u00e7ersiz.", "trtLog": "Birka\u00e7 %1 kelime az \u00f6nce %2 diline \u00e7evirildi.", "trtUsage": "Bana \u00e7evirilecek bir metin ver!", From e91bc8fe5827205d07478a275de03a08016da704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= <46200293+trkzmn@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:01:00 +0300 Subject: [PATCH 237/242] direct: gdrive: Fix-up "Virus scan warning" and "Quota exceeded" related issues. (#22) * Also missing headers (such as Content-Disposition) are removed. Signed-off-by: trkzmn <trkzmn89@gmail.com> --- cookies.txt | 4 ++-- sedenbot/modules/miscs/direct_link.py | 32 +++++++++++++-------------- sedenecem/translator/en.json | 4 +++- sedenecem/translator/tr.json | 4 +++- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/cookies.txt b/cookies.txt index 029e722..3a2a81d 100644 --- a/cookies.txt +++ b/cookies.txt @@ -1,5 +1,5 @@ # HTTP Cookie File -# Generated by Wget on 2022-09-18 16:35:21. +# Generated by Wget on 2024-03-14 06:02:22. # Edit at your own risk. -.google.com TRUE / TRUE 1726590921 __Secure-3PSID OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XydPm0-knkwsNrazAuGCA7Q. \ No newline at end of file +.google.com TRUE / TRUE 1773457342 __Secure-3PSID g.a000hQgeotAjF0s4Bo1rG0uA-Rs--F4uvWXHlSTgXrmA1YgNa-70WcsTGGbhSM0FtrsKqzuOkAACgYKAVgSAQASFQHGX2MiDObGGYCE-af_tbsVmv15NhoVAUF8yKpYtxp1Xn_8m3ogYEdAJ1QM0076 \ No newline at end of file diff --git a/sedenbot/modules/miscs/direct_link.py b/sedenbot/modules/miscs/direct_link.py index fcbf869..2729804 100644 --- a/sedenbot/modules/miscs/direct_link.py +++ b/sedenbot/modules/miscs/direct_link.py @@ -88,23 +88,23 @@ def check(url, items, starts=False): def gdrive(link: str, message) -> str: reply = '' url_id = link.split('/')[5] - dl_url = f'https://drive.google.com/u/0/uc?id={url_id}&export=download&confirm=t' + dl_url = f'https://drive.usercontent.google.com/download?id={url_id}&export=download&confirm=t' headers = {'user-agent': useragent()} - response = get(url=dl_url, headers=headers, stream=True) - if response.status_code == 429: - reply_doc( - message, 'cookies.txt', caption=get_translation("directGdriveCookieUsage") - ) - reply += get_translation("directGdriveCookie") - cookies = { - '__Secure-1PSID': 'OQhdhyIxvvx3wyIAlCJ3hUqpH3ttB4osdTsnFnpkDWJCtt7XqwPg5PZZKN6KNnoBsXJrQw.' - } - response = get(url=dl_url, headers=headers, cookies=cookies, stream=True) - name = response.headers.get('Content-Disposition').split(';')[1].split('"')[1] - size = f'{int(response.headers.get("Content-Length")) / (1024 * 1024):0.2f}' - alternative_url = response.url - reply += f'[1 - {name} ({size}MB)]({dl_url})\n' - reply += f'[2 - {name} ({size}MB)]({alternative_url})\n' + reply_doc( + message, 'cookies.txt', caption=get_translation("directGdriveCookieUsage") + ) + reply += get_translation("directGdriveCookie") + cookies = { + '__Secure-1PSID': 'g.a000hQgeotAjF0s4Bo1rG0uA-Rs--F4uvWXHlSTgXrmA1YgNa-70LASRphA1f_pHqKS5DVTCuwACgYKA' + 'boSAQASFQHGX2Mi1y83cqTGHara3fsXiu43ZBoVAUF8yKrxezWJON0xdZvCEZj1KgQP0076' + } + response = get(url=dl_url, headers=headers, cookies=cookies, stream=True) + page = BeautifulSoup(response.content, 'html.parser') + info = page.find('span', {'class': 'uc-name-size'}).text + uuid = page.find('input', {'name': 'uuid'}).get('value') + at = page.find('input', {'name': 'at'}).get('value') + dl_url += f'&uuid={uuid}&at={at}' + reply += f'[{info}]({dl_url})\n' return reply diff --git a/sedenecem/translator/en.json b/sedenecem/translator/en.json index 962a62e..21c1c19 100644 --- a/sedenecem/translator/en.json +++ b/sedenecem/translator/en.json @@ -138,7 +138,9 @@ "deviceSearchResultChild": "%1Brand:%1 %2\n%1Name:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Usage: .device <codename> eg: .device raphael", "directError": "Error occurred while processing %1", - "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", + "directGdriveCookie": "This file may require login Google account to access & download.\nIf you face any problem use cookies.txt to download file.\n\n", + "directGdriveCookieUsage": "Usage: `wget --load-cookies cookies.txt` directLink", + "directInfo": ".direct <url>\nUsage: Reply to a link or paste a URL to\ngenerate a direct download link\n\nList of supported URLs:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 not supported", "directUrlNotFound": "URL not found", "directUsage": "Usage: .direct <url>", diff --git a/sedenecem/translator/tr.json b/sedenecem/translator/tr.json index 4cd6be8..c806ba6 100644 --- a/sedenecem/translator/tr.json +++ b/sedenecem/translator/tr.json @@ -139,7 +139,9 @@ "deviceSearchResultChild": "%1Marka:%1 %2\n%1Ad:%1 %3\n%1Model:%1 %4\n\n", "deviceUsage": "Kullan\u0131m: .device <kod ad\u0131> \u00d6rnek: .device raphael", "directError": "%1 i\u015flenirken hata olu\u015ftu", - "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare -\nMediaFire - SourceForge - OSDN - GitHub", + "directGdriveCookie": "Bu dosyay\u0131 indirebilmek i\u00e7in Google hesab\u0131n\u0131za giri\u015f yapman\u0131z gerekebilir.\n\u0130ndirme i\u015flemi esnas\u0131nda herhangi bir hata ile kar\u015f\u0131la\u015f\u0131rsan\u0131z cookies.txt dosyas\u0131n\u0131 kullan\u0131n.\n\n", + "directGdriveCookieUsage": "Kullan\u0131m: `wget --load-cookies cookies.txt` directLink", + "directInfo": ".direct <link>\nKullan\u0131m: Kullan\u0131m: Bir ba\u011flant\u0131y\u0131 yan\u0131tlay\u0131n veya do\u011frudan indirme ba\u011flant\u0131s\u0131\nolu\u015fturmak i\u00e7in bir URL yap\u0131\u015ft\u0131r\u0131n\n\nDesteklenen URL'lerin listesi:\nYandex.Disk - AFH - ZippyShare - GDrive -\nGDocs - MediaFire - SourceForge - OSDN - GitHub", "directNoSupport": "%1 desteklenmiyor", "directUrlNotFound": "Link bulunamad\u0131", "directUsage": "Kullan\u0131m: .direct <link>", From 5a261c854598347c5021077cc061a9533beef4e1 Mon Sep 17 00:00:00 2001 From: NaytSeyd <naytseyd@yandex.com> Date: Thu, 14 Mar 2024 23:50:04 +0300 Subject: [PATCH 238/242] fix youtubedl filename decode, command regex --- sedenbot/modules/tools/youtubedl.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sedenbot/modules/tools/youtubedl.py b/sedenbot/modules/tools/youtubedl.py index 0e8fc5e..380b1a4 100644 --- a/sedenbot/modules/tools/youtubedl.py +++ b/sedenbot/modules/tools/youtubedl.py @@ -27,16 +27,14 @@ ) -@sedenify(pattern='^.y(outube|tdl)') +@sedenify(pattern='^.y(outube|t)dl') def youtubedl(message): args = extract_args_split(message) - if len(args) != 2: edit(message, f'`{get_translation("wrongCommand")}`') return - util = args[0].lower() - url = args[1] + util, url = args[0].lower(), args[1] if util == 'mp4': ydl_opts = { @@ -133,12 +131,11 @@ def youtubedl(message): edit(message, f'`{get_translation("uploadMedia")}`') reply_audio( message, - f'{video_info["id"]}.{video_info["audio_ext"]}', + f'{video_info["id"]}.mp3', caption=f"**{get_translation('videoUploader')}** `{uploader}`", duration=duration, delete_orig=True, delete_file=True, ) - HELP.update({'youtubedl': get_translation('youtubedlInfo')}) From f9150534e20bd9d588812b0790179084a9349289 Mon Sep 17 00:00:00 2001 From: trkzmn <trkzmn89@gmail.com> Date: Mon, 18 Mar 2024 09:41:56 +0300 Subject: [PATCH 239/242] config: Add an alternative database dialect * If PostgreSQL socket is not available (such as port is not open, firewall etc.) some modules (which are need db to work properly) fails (chat.snips, chat.blacklist etc.). - Error(s): 1- connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused (0x0000274D/10061) Is the server running on that host and accepting TCP/IP connections? 2- WARNING - Unable to run mutechat, unmutechat commands, no SQL connections found * That's why stituations like this serverless SQLite should be used. Reference: https://stackoverflow.com/a/35370008 Signed-off-by: trkzmn <trkzmn89@gmail.com> --- sample_config.env | 1 + sedenbot/__init__.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/sample_config.env b/sample_config.env index fb63f20..7d3fd89 100644 --- a/sample_config.env +++ b/sample_config.env @@ -11,6 +11,7 @@ SESSION='' # Leave default if you deploy on local machine otherwise enter the url by following format. # Example: 'postgresql://username:passwd@localhost:5432/db_name' DATABASE_URL='postgresql://sedenuserbot:TeamDerUntergang@localhost:5432/userbotdb' +DATABASE_URL_ALTERNATIVE='sqlite:///userbotdb' # Chat ID for Log Group LOG_ID='' diff --git a/sedenbot/__init__.py b/sedenbot/__init__.py index adb1c62..333d3d9 100644 --- a/sedenbot/__init__.py +++ b/sedenbot/__init__.py @@ -8,6 +8,8 @@ # +import socket +from contextlib import closing from importlib import import_module from logging import CRITICAL, DEBUG, INFO, basicConfig, getLogger from os import environ, listdir, path, remove @@ -163,6 +165,12 @@ def set_logger(): if DATABASE_URL and DATABASE_URL.startswith('postgres://'): DATABASE_URL = DATABASE_URL.replace('postgres://', 'postgresql://', 1) +# If PostgreSQL socket is not available (which is cause issues) use SQLite dialect +host, port = DATABASE_URL.split('@')[-1].split('/')[0].split(':') +with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + if sock.connect_ex((host, int(port))) != 0: + DATABASE_URL = environ.get('DATABASE_URL_ALTERNATIVE', None) + # SedenBot Session SESSION = environ.get('SESSION', 'sedenify') From 619680b9384704085d7280e4068845506859bf96 Mon Sep 17 00:00:00 2001 From: trkzmn <trkzmn89@gmail.com> Date: Mon, 18 Mar 2024 10:30:55 +0300 Subject: [PATCH 240/242] Fix typo in package name Signed-off-by: trkzmn <trkzmn89@gmail.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a08969..323a2a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ googletrans==4.0.0rc1 gtts heroku3 humanize -image_to_ascii +image_to_Ascii lyricsgenius pillow psycopg2-binary From 6a8644edd2429a8d7c0b92421378b258d8b92c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= <46200293+trkzmn@users.noreply.github.com> Date: Mon, 18 Mar 2024 15:03:37 +0300 Subject: [PATCH 241/242] Fix typo in package name (#24) Signed-off-by: trkzmn <trkzmn89@gmail.com> From 19aa68a6261cb32342a93b5f1685d46d9faeadf5 Mon Sep 17 00:00:00 2001 From: ahmet <NaytSeyd@yandex.com> Date: Thu, 4 Apr 2024 15:02:51 +0300 Subject: [PATCH 242/242] update README --- README.md | 55 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3161e4b..f7e2cb5 100755 --- a/README.md +++ b/README.md @@ -1,25 +1,23 @@ # Seden UserBot <sub><sup><sub>_Feel my hands in your hair_</sup></sub></sub> -### Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It is an modular and simple to use bot. +### Telegram Python Bot running on Python3 with a Postgresql Sqlalchemy database. It is a modular and easy-to-use bot. ![GitHub](https://img.shields.io/github/license/TeamDerUntergang/Telegram-SedenUserBot?color=red) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ## Disclaimer -<details> - <summary>Click to expand!</summary> +```python +# -*- coding: utf-8 -*- +""" +DISCLAIMER: +The use of this Telegram bot is subject to the following terms: -```c -#include <std/disclaimer.h> -/** - Your Telegram account may be banned. - I'm not responsible for misuse of bot, responsibility belongs entirely to user. - This bot is maintained for fun as well as managing groups efficiently. - If you think you will have fun by spamming groups, you are wrong. - In case of any spam ban, if you come and write that my account has been banned, - I'll just laugh at you. -/** +- Your Telegram account may be banned due to misuse of the bot. +- The responsibility for any misuse of the bot lies entirely with the user. +- This bot is primarily maintained for facilitating efficient group management and for entertainment purposes. +- Engaging in spam activities within groups is strongly discouraged. +- If your account is banned due to spamming, any requests for assistance will not be entertained. +""" ``` -</details> ## Run Bot <details> @@ -30,32 +28,41 @@ git clone https://github.com/TeamDerUntergang/Telegram-SedenUserBot.git cd Telegram-SedenUserBot -# Install pip dependencies -pip3 install -r requirements.txt +# Create and activate a virtual environment (you can change 'sedenify-venv' to your preferred name) +python3 -m venv sedenify-venv +source sedenify-venv/bin/activate -# Generate session from session.py (skip if there is already) +# Install Python dependencies +pip install -r requirements.txt + +# Generate a session file if it doesn't exist python3 session.py -# Create config.env and fill variables -mv sample_config.env config.env +# Create a configuration file and fill in the required variables +cp sample_config.env config.env +# Then fill in the necessary variables in config.env # Run bot python3 seden.py + ``` ### Nix/NixOS -Just type `nix-shell` command in bot folder. +To set up the bot in a Nix/NixOS environment, navigate to the bot folder and execute the `nix-shell` command. </details> ## Q&A -If you have any requests & complaints & suggestions, you can join our [support group](https://t.me/SedenUserBotSupport) or please contact us through a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues). +If you have any requests, complaints, or suggestions, please feel free to reach out to us. + +- Join our [support group](https://t.me/SedenUserBotSupport) for assistance. +- Submit a [GitHub issue](https://github.com/TeamDerUntergang/Telegram-SedenUserBot/issues) to report any problems or provide feedback. -Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.html) page for installation instructions! Questions asked without reading the instruction will not be answered. +For installation instructions, please visit our [GitHub.io](https://teamderuntergang.github.io/installation.html) page. We kindly request that you read the instructions carefully before asking questions, as questions that can be answered by following the instructions may not receive a response. ## Credits <details> <summary>Click to expand!</summary> -* [@NaytSeyd](https://github.com/NaytSeyd) - Founder +* [@naytseyd](https://github.com/naytseyd) - Founder * [@frknkrc44](https://github.com/frknkrc44) - Operator * [@Sedenogen](https://github.com/ciyanogen) - Co-Founder * [@Delivrance](https://github.com/pyrogram/pyrogram) - Pyrogram Library @@ -65,4 +72,4 @@ Please go to our [GitHub.io](https://teamderuntergang.github.io/installation.htm </details> ## License -This project is licensed under the [AGPL-3](https://www.gnu.org/licenses/agpl-3.0.html). +This project is licensed under the [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html). \ No newline at end of file