├── launch.bat ├── requirements.txt ├── README.md ├── LICENSE ├── .gitignore ├── database.py ├── strings.py └── kazuma.py /launch.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | py kazuma.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python 2 | Pillow 3 | pyffmpeg==2.2.2 4 | python-telegram-bot 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kazuma 2 | Telegram bot which steals stickers and packs and stores them in your own custom packs. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 dedsec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.png 3 | config.json 4 | .vscode/ 5 | *.mp4 6 | *.webm 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | import sqlite3, json, os 2 | 3 | with open('config.json', 'r') as f: 4 | config = json.load(f) 5 | database = config['database'] 6 | 7 | NEW_PACK = """INSERT OR IGNORE INTO packdb (pid, uid, def, name) 8 | VALUES ("{}", {}, {}, "{}");""" 9 | 10 | DELETE_PACK = """DELETE 11 | FROM packdb 12 | WHERE pid = "{}";""" 13 | 14 | LIST_PACKS = """SELECT * 15 | FROM packdb 16 | WHERE uid = {} 17 | ORDER BY name ASC;""" 18 | 19 | GET_PACK_BY_ID = """SELECT * 20 | FROM packdb 21 | WHERE pid = "{}";""" 22 | 23 | GET_PACK_BY_NAME = """SELECT * 24 | FROM packdb 25 | WHERE name = "{}" AND uid = {};""" 26 | 27 | GET_DEFAULT_PACK = """SELECT * 28 | FROM packdb 29 | WHERE uid = {} AND def = 1;""" 30 | 31 | SET_DEFAULT_BY_ID = """UPDATE packdb 32 | SET def = 1 33 | WHERE pid = "{}";""" 34 | 35 | SET_DEFAULT_BY_NAME = """UPDATE packdb 36 | SET def = 1 37 | WHERE name = "{}" AND uid = {};""" 38 | 39 | REMOVE_DEFAULT = """UPDATE packdb 40 | SET def = 0 41 | WHERE uid = {} AND def = 1;""" 42 | 43 | GET_ALL = """SELECT * 44 | FROM packdb;""" 45 | 46 | CREATE_DB = """CREATE TABLE IF NOT EXISTS packdb ( 47 | pid NVARCHAR(128) PRIMARY KEY, 48 | uid INTEGER, 49 | def BOOLEAN, 50 | name NVARCHAR(64) 51 | );""" 52 | 53 | def execute(query): 54 | connection = sqlite3.connect(database) 55 | cursor = connection.cursor() 56 | cursor.execute(query) 57 | r = cursor.fetchall() 58 | connection.commit() 59 | cursor.close() 60 | connection.close() 61 | return(r) 62 | 63 | def new_pack(pid, uid, default, packname): 64 | return(execute(NEW_PACK.format(pid, uid, default, packname))) 65 | 66 | def delete_pack(pid): 67 | return(execute(DELETE_PACK.format(pid))) 68 | 69 | def list_packs(uid): 70 | return(execute(LIST_PACKS.format(uid))) 71 | 72 | def get_pack_by_id(pid): 73 | return(execute(GET_PACK_BY_ID.format(pid))) 74 | 75 | def get_pack_by_name(packname, uid): 76 | return(execute(GET_PACK_BY_NAME.format(packname, uid))) 77 | 78 | def get_default_pack(uid): 79 | return(execute(GET_DEFAULT_PACK.format(uid))) 80 | 81 | def set_default_by_id(pid): 82 | return(execute(SET_DEFAULT_BY_ID.format(pid))) 83 | 84 | def set_default_by_name(packname, uid): 85 | return(execute(SET_DEFAULT_BY_NAME.format(packname, uid))) 86 | 87 | def remove_default(uid): 88 | return(execute(REMOVE_DEFAULT.format(uid))) 89 | 90 | def get_all(): 91 | return(execute(GET_ALL)) 92 | 93 | def create_db(database): 94 | return(execute(CREATE_DB)) 95 | 96 | if not os.path.exists(database): 97 | create_db(database) 98 | -------------------------------------------------------------------------------- /strings.py: -------------------------------------------------------------------------------- 1 | START = "Yes, I'm Kazuma." 2 | RESTART = "Re;Starting Bot in Another Instance from Zero." 3 | GITPULL = "Re;Starting Bot in Another Instance from the Latest Commit." 4 | NOT_SUDO = "This is a developer restricted command.\nYou do not have permissions to run this." 5 | 6 | STEALING = "_STEAL!!_" 7 | STEALING_PACK = "Stolen {} out of {} stickers." 8 | 9 | NEW_PACK = "_STEAL!!_\nCreating a new steal pack." 10 | NEW_PACK_CREATED = "New steal pack created! [Get it here](t.me/addstickers/{})." 11 | 12 | REPLY_NOT_STICKER = "This skill works only when replied to stickers." 13 | REPLY_NOT_MY_STICKER = "This skill only works on stickers of packs that I have stolen." 14 | REPLY_NOT_STICKER_IMAGE = "This skill only works on stickers, images and videos." 15 | REPLY_VID_DURATION_ERROR = "The video is too long ({}s)!\nMax duration is 3 seconds" 16 | REPLY_VID_SIZE_ERROR = "The video is too big ({}KB)!\nMax size is 256KB" 17 | 18 | STEAL_ERROR = "I couldn't steal that sticker. Blame Aqua for being so useless." 19 | STEAL_NOT_REPLY = "Reply to an image or a sticker to steal." 20 | STEAL_SUCESSFUL = "Steal sucessful! Here's your [steal pack](t.me/addstickers/{})." 21 | STEAL_SKIPPED = "Steal sucessful but some stickers might've been skipped. Here's your [steal pack](t.me/addstickers/{})." 22 | STEALPACK_NO_ARGS = "Specify a packname to steal stickers into. Like this:\n/stealpack __" 23 | STEALPACK_NOT_REPLY = "Reply to a sticker and send:\n/stealpack __" 24 | 25 | NEWPACK_ERROR = "I was unable to create that sticker pack. Must be the Demon King's magic." 26 | RESIZE_ERROR = "Unable to resize image to the correct dimensions." 27 | INVALID_EMOJI = "Some of the emojis you specified are not supported." 28 | INVALID_PACKNAME = "The pack name or emojis you specified contain unsupported characters." 29 | INVALID_PEER_ID = "Freshly isekai-d? Click the button to join my guild!" 30 | PACK_DOESNT_EXIST = "What you're trying to steal doesn't exist.\n( ͡° ͜ʖ ͡°)" 31 | PACK_LIMIT_EXCEEDED = "This pack has reached maximum capacity. You can /switch to a different pack or make a new one." 32 | PACK_ALREADY_EXISTS = "I think you're looking for [this pack](t.me/addstickers/{})." 33 | NO_STOLEN_PACKS = "You haven't stolen any packs yet newb." 34 | UNANIMATED_IN_ANIMATED = "You can't add normal stickers in an animated sticker pack. Try stealing it in a normal pack." 35 | ANIMATED_IN_UNANIMATED = "You can't add animated stickers in a normal sticker pack. Try stealing it in an animated pack." 36 | NOT_YOUR_PACK = "Hah! Nice try but you can't mess with others' stickers." 37 | 38 | DELETE_PACK = "This is beyond my powers. Use @stickers to delete sticker packs." 39 | DELETE_ERROR = "I couldn't delete that sticker. Looks like those Arch-Devils are at it again." 40 | DELETE_SUCESSFUL = "Poof! The sticker is gone." 41 | DELETE_NOT_REPLY = "This skill only works on stickers of packs that I have stolen." 42 | 43 | SETPOSITION_INVALID_INPUT = "That's not how this skill works. Reply to a sticker and try:\n/setposition __" 44 | SETPOSITION_NOT_REPLY = "Reply to the sticker whose position you wanna change." 45 | SETPOSITION_ERROR = "I couldn't change sticker positions. Maybe the undead are interfering with my magic." 46 | 47 | SWITCH_INVALID_INPUT = "Specify the pack you want to be set as default by:\n/switch __\n/switch __" 48 | SWITCH_PACK_DOESNT_EXIST = "I don't think this pack exists. Use /mypacks to get a list of packs that you've stolen." 49 | SWITCH_ALREADY_DEFAULT = "*{}* is already set as your default pack." 50 | SWITCH_CHANGED_DEFAULT = "*{}* is now set as your default pack." 51 | SWITCH_INDEX_ERROR = "I couldn't switch default packs. Maybe those Axis Cult members are interfering with my magic." 52 | SWITCH_PACKNAME_ERROR = "I couldn't switch default packs. Maybe those Eris Cult members are interfering with my magic." 53 | 54 | HELP = """ 55 | Hi, I'm Kazuma. 56 | Here's a list of skills that I can use: 57 | 58 | /steal - Steal a sticker 59 | /stealpack - Steal the whole pack 60 | /mypacks - List your steal packs 61 | /switch - Change your default pack 62 | /delsticker - Delete sticker from pack 63 | /setposition - Change sticker postiton 64 | """ 65 | STATS = "Stealers: {}\nStolen Packs: {}" 66 | GIST = "https://gist.github.com/notdedsec/2c4aa0359aef072b0e3025d55eaba858" -------------------------------------------------------------------------------- /kazuma.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | import math 4 | import json 5 | import time 6 | import logging 7 | import hashlib 8 | import datetime 9 | from PIL import Image 10 | from pyffmpeg import FFmpeg, FFprobe 11 | from telegram.ext import Updater, CommandHandler 12 | from telegram import Bot, TelegramError, ParseMode, InlineKeyboardMarkup, InlineKeyboardButton 13 | 14 | import strings as s 15 | import database as sql 16 | 17 | def steal(update, context): 18 | msg = update.effective_message 19 | user = update.effective_user 20 | tempsticker = f"{str(user.id)}.png" 21 | if not msg.reply_to_message: 22 | reply(msg, s.STEAL_NOT_REPLY) 23 | return 24 | 25 | emoji = "🤔" 26 | defpack = sql.get_default_pack(user.id) 27 | if defpack: packname = defpack[0][3] 28 | else: packname = user.first_name[:35]+"'s Stolen Pack" 29 | if context.args: 30 | if len(context.args[-1][-1].encode('utf-8')) == 1: 31 | packname = ' '.join(context.args) 32 | if msg.reply_to_message.sticker: 33 | emoji = msg.reply_to_message.sticker.emoji 34 | else: 35 | emoji = str(context.args[-1]) 36 | if len(context.args) > 1: 37 | packname = ' '.join(context.args[:-1]) 38 | 39 | useridhash = hashlib.sha1(bytearray(user.id)).hexdigest() 40 | packnamehash = hashlib.sha1(bytearray(packname.lower().encode('utf-8'))).hexdigest() 41 | packid = f'K{packnamehash[:10]}{useridhash[:10]}_by_{context.bot.username}' 42 | replymsg = msg.reply_text(s.STEALING, parse_mode=ParseMode.MARKDOWN) 43 | 44 | try: 45 | if msg.reply_to_message.sticker: 46 | if msg.reply_to_message.sticker.is_animated: 47 | tempsticker = tempsticker[:-3]+'tgs' 48 | if msg.reply_to_message.sticker.is_video: 49 | tempsticker = tempsticker[:-3] + 'webm' 50 | file_id = msg.reply_to_message.sticker.file_id 51 | elif msg.reply_to_message.photo: 52 | file_id = msg.reply_to_message.photo[-1].file_id 53 | elif msg.reply_to_message.animation: 54 | extension = msg.reply_to_message.animation.mime_type.split('/')[1] 55 | tempsticker = tempsticker[:-3] + extension 56 | file_id = msg.reply_to_message.animation.file_id 57 | elif msg.reply_to_message.video: 58 | extension = msg.reply_to_message.video.mime_type.split('/')[1] 59 | tempsticker = tempsticker[:-3] + extension 60 | file_id = msg.reply_to_message.video.file_id 61 | elif msg.reply_to_message.document: 62 | file_id = msg.reply_to_message.document.file_id 63 | context.bot.get_file(file_id).download(tempsticker) 64 | 65 | if not tempsticker.endswith(('webm', 'tgs')): 66 | if not process_file(replymsg, tempsticker): 67 | return 68 | 69 | # Renaming tempsticker to the processed webm file 70 | if tempsticker.endswith('mp4'): 71 | os.remove(tempsticker) 72 | tempsticker = tempsticker[:-3] + 'webm' 73 | 74 | stickerfile = open(tempsticker, 'rb') 75 | 76 | if tempsticker.endswith('png'): 77 | context.bot.addStickerToSet(user_id=user.id, name=packid, png_sticker=stickerfile, emojis=emoji) 78 | elif tempsticker.endswith('webm'): 79 | context.bot.addStickerToSet(user_id=user.id, name=packid, webm_sticker=stickerfile, emojis=emoji) 80 | else: 81 | context.bot.addStickerToSet(user_id=user.id, name=packid, tgs_sticker=stickerfile, emojis=emoji) 82 | replymsg.edit_text(s.STEAL_SUCESSFUL.format(packid), parse_mode=ParseMode.MARKDOWN) 83 | except OSError as e: 84 | replymsg.edit_text(s.REPLY_NOT_STICKER_IMAGE) 85 | except TelegramError as e: 86 | if e.message == "Stickerset_invalid": 87 | newpack(msg, user, tempsticker, emoji, packname, packid, True, replymsg, context.bot) 88 | elif e.message == "Sticker_tgs_notgs": 89 | replymsg.edit_text(s.UNANIMATED_IN_ANIMATED) 90 | elif e.message == "Sticker_png_nopng": 91 | replymsg.edit_text(s.ANIMATED_IN_UNANIMATED) 92 | elif e.message == "Invalid sticker emojis": 93 | replymsg.edit_text(s.INVALID_EMOJI) 94 | elif e.message == "Sticker set name invalid": 95 | replymsg.edit_text(s.INVALID_PACKNAME) 96 | elif e.message == "Stickers_too_much": 97 | replymsg.edit_text(s.PACK_LIMIT_EXCEEDED) 98 | elif e.message == "Internal Server Error: sticker set not found (500)": 99 | replymsg.edit_text(s.STEAL_SUCESSFUL.format(packid), parse_mode=ParseMode.MARKDOWN) 100 | else: 101 | replymsg.edit_text(s.STEAL_ERROR) 102 | print(e.message) 103 | finally: 104 | try: 105 | stickerfile.close() 106 | except UnboundLocalError: # to deal with undefined stickerfile variable 107 | pass 108 | os.remove(tempsticker) 109 | reply(msg, None, replymsg) 110 | 111 | def stealpack(update, context): 112 | msg = update.effective_message 113 | user = update.effective_user 114 | if not context.args: 115 | reply(msg, s.STEALPACK_NO_context.args, parse_mode=ParseMode.MARKDOWN) 116 | return 117 | if not msg.reply_to_message: 118 | reply(msg, s.STEALPACK_NOT_REPLY) 119 | return 120 | try: 121 | sticker = msg.reply_to_message.sticker 122 | except: 123 | reply(msg, s.REPLY_NOT_STICKER_IMAGE) 124 | return 125 | packname = ' '.join(context.args) 126 | replymsg = msg.reply_text(s.STEALING, parse_mode=ParseMode.MARKDOWN) 127 | try: 128 | oldpack = context.bot.getStickerSet(sticker.set_name) 129 | except TelegramError as e: 130 | if e.message == "Stickerset_invalid": 131 | replymsg.edit_text(s.PACK_DOESNT_EXIST, parse_mode=ParseMode.MARKDOWN) 132 | reply(msg, None, replymsg) 133 | return 134 | 135 | useridhash = hashlib.sha1(bytearray(user.id)).hexdigest() 136 | packnamehash = hashlib.sha1(bytearray(packname.lower().encode('utf-8'))).hexdigest() 137 | packid = f'K{packnamehash[:10]}{useridhash[:10]}_by_{context.bot.username}' 138 | 139 | if msg.reply_to_message.sticker.is_animated: 140 | ext = 'tgs' 141 | elif msg.reply_to_message.sticker.is_video: 142 | ext = 'webm' 143 | else: 144 | ext = 'png' 145 | 146 | skipped = False 147 | for sticker in oldpack.stickers: 148 | try: 149 | tempsticker = f"{str(sticker.file_id) + str(user.id)}.{ext}" 150 | context.bot.get_file(sticker.file_id).download(tempsticker) 151 | if not tempsticker.endswith(('webm', 'tgs')): 152 | if not process_file(replymsg, tempsticker): 153 | return 154 | stickerfile = open(tempsticker, 'rb') 155 | if tempsticker.endswith('png'): 156 | context.bot.addStickerToSet(user_id=user.id, name=packid, png_sticker=stickerfile, emojis=sticker.emoji) 157 | elif tempsticker.endswith('webm'): 158 | context.bot.addStickerToSet(user_id=user.id, name=packid, webm_sticker=stickerfile, emojis=sticker.emoji) 159 | else: 160 | context.bot.addStickerToSet(user_id=user.id, name=packid, tgs_sticker=stickerfile, emojis=sticker.emoji) 161 | except OSError as e: 162 | replymsg.edit_text(s.REPLY_NOT_STICKER_IMAGE) 163 | except Exception as e: 164 | if e.message == "Stickerset_invalid": 165 | newpack(msg, user, tempsticker, sticker.emoji, packname, packid, False, replymsg, context.bot) 166 | else: 167 | skipped = True 168 | pass 169 | finally: 170 | stickerfile.close() 171 | os.remove(tempsticker) 172 | try: 173 | replymsg.edit_text(s.STEALING_PACK.format(oldpack.stickers.index(sticker), len(oldpack.stickers)), parse_mode=ParseMode.MARKDOWN) 174 | except: 175 | pass 176 | if skipped: 177 | replymsg.edit_text(s.STEAL_SKIPPED.format(packid), parse_mode=ParseMode.MARKDOWN) 178 | else: 179 | replymsg.edit_text(s.STEAL_SUCESSFUL.format(packid), parse_mode=ParseMode.MARKDOWN) 180 | reply(msg, None, replymsg) 181 | 182 | def newpack(msg, user, tempsticker, emoji, packname, packid, sendreply, replymsg, bot): 183 | try: 184 | stickerfile = open(tempsticker, 'rb') 185 | replymsg.edit_text(s.NEW_PACK, parse_mode=ParseMode.MARKDOWN) 186 | if tempsticker.endswith('png'): 187 | bot.createNewStickerSet(user.id, packid, packname, png_sticker=stickerfile, emojis=emoji, timeout=9999) 188 | elif tempsticker.endswith('webm'): 189 | bot.createNewStickerSet(user.id, packid, packname, webm_sticker=stickerfile, emojis=emoji, timeout=9999) 190 | else: 191 | bot.createNewStickerSet(user.id, packid, packname, tgs_sticker=stickerfile, emojis=emoji, timeout=9999) 192 | default = 0 if sql.get_default_pack(user.id) else 1 193 | sql.new_pack(packid, user.id, default, packname) 194 | stickerfile.close() 195 | except TelegramError as e: 196 | if e.message == "Sticker set name is already occupied": 197 | replymsg.edit_text(s.PACK_ALREADY_EXISTS.format(packid), parse_mode=ParseMode.MARKDOWN) 198 | if e.message == "Internal Server Error: created sticker set not found (500)": # throws this error but pack gets created anyway. idk. 199 | replymsg.edit_text(s.NEW_PACK_CREATED.format(packid), parse_mode=ParseMode.MARKDOWN) 200 | default = 0 if sql.get_default_pack(user.id) else 1 201 | sql.new_pack(packid, user.id, default, packname) 202 | elif e.message == "Sticker set name invalid" and sendreply: 203 | replymsg.edit_text(s.INVALID_PACKNAME) 204 | elif e.message == "Peer_id_invalid": 205 | kb = [[InlineKeyboardButton(text="Start", url=f"t.me/{bot.username}")]] 206 | replymsg.edit_text(s.INVALID_PEER_ID, reply_markup=InlineKeyboardMarkup(kb)) 207 | else: 208 | replymsg.edit_text(s.NEWPACK_ERROR) 209 | print(e) 210 | else: 211 | if sendreply: 212 | replymsg.edit_text(s.NEW_PACK_CREATED.format(packid), parse_mode=ParseMode.MARKDOWN) 213 | finally: 214 | reply(msg, None, replymsg) 215 | 216 | def reply(msg, text=None, replymsg=None, delete=True): 217 | if text: 218 | replymsg = msg.reply_markdown(text) 219 | if msg.chat.type == msg.chat.PRIVATE: 220 | return 221 | time.sleep(15) 222 | try: 223 | replymsg.delete() 224 | msg.delete() 225 | except: pass 226 | 227 | def processimage(tempsticker): 228 | size = (512, 512) 229 | im = Image.open(tempsticker) 230 | if (im.width and im.height) < size[0]: 231 | if im.width > im.height: 232 | wnew = size[0] 233 | hnew = size[0]/im.width*im.height 234 | else: 235 | wnew = size[0]/im.height*im.width 236 | hnew = size[0] 237 | im = im.resize((math.floor(wnew), math.floor(hnew))) 238 | else: 239 | im.thumbnail(size) 240 | im.save(tempsticker, "PNG") 241 | im.close() 242 | 243 | def process_vid(frame_rate, tempsticker): 244 | """Change file to webm format with proper dimensions (atleast one side 512px)""" 245 | 246 | ff = FFmpeg() 247 | vid = cv2.VideoCapture(tempsticker) 248 | 249 | if tempsticker.endswith('webm'): 250 | webm_tempsticker = tempsticker 251 | else: 252 | webm_tempsticker = os.path.splitext(tempsticker)[0] + '.webm' 253 | 254 | h = vid.get(cv2.CAP_PROP_FRAME_HEIGHT) 255 | w = vid.get(cv2.CAP_PROP_FRAME_WIDTH) 256 | 257 | if h >= w: 258 | h = 512 259 | w = -1 260 | else: 261 | w = 512 262 | h = -1 263 | 264 | frame_rate_option = "-r 30" if frame_rate > 30 else " " 265 | 266 | ff.options(f"-i {tempsticker} -filter:v scale={w}:{h} -c:a copy -an {frame_rate_option}{webm_tempsticker}") 267 | 268 | def check_vid(replymsg, tempsticker): 269 | """Check if the file fulfill the requirements specified in https://core.telegram.org/stickers#video-stickers""" 270 | 271 | fp = FFprobe(tempsticker) 272 | 273 | # Convert duration to seconds. 00:01:02.120 -> 62.12s 274 | dur, milliseconds = fp.duration.split('.') 275 | x = time.strptime(dur,'%H:%M:%S') 276 | duration = datetime.timedelta(hours=x.tm_hour, minutes=x.tm_min, seconds=x.tm_sec, milliseconds=int(milliseconds)).total_seconds() 277 | 278 | frame_rate = float(fp.fps) 279 | size = os.path.getsize(tempsticker) 280 | 281 | # Duration max 3s 282 | if duration > 3: 283 | replymsg.edit_text(s.REPLY_VID_DURATION_ERROR.format(duration)) 284 | return False 285 | 286 | # Size max 256KB 287 | if size > 256000: 288 | replymsg.edit_text(s.REPLY_VID_SIZE_ERROR.format(size/1000)) 289 | return False 290 | 291 | process_vid(frame_rate, tempsticker) 292 | return True 293 | 294 | 295 | def process_file(replymsg, tempsticker): 296 | if tempsticker.endswith(('.mp4', '.webm')): 297 | if not check_vid(replymsg, tempsticker): 298 | return False 299 | else: 300 | processimage(tempsticker) 301 | return True 302 | 303 | def delsticker(update, context): 304 | msg = update.effective_message 305 | if not msg.reply_to_message.sticker: 306 | reply(msg, s.DELETE_NOT_REPLY) 307 | return 308 | if not msg.reply_to_message.sticker.set_name in str(sql.list_packs(update.effective_user.id)): 309 | reply(msg, s.NOT_YOUR_PACK) 310 | return 311 | try: 312 | context.bot.delete_sticker_from_set(msg.reply_to_message.sticker.file_id) 313 | except: 314 | replymsg = msg.reply_text(s.DELETE_ERROR) 315 | else: 316 | replymsg = msg.reply_text(s.DELETE_SUCESSFUL) 317 | reply(msg, None, replymsg) 318 | 319 | def delpack(update, context): 320 | msg = update.effective_message 321 | replymsg = msg.reply_text(s.DELETE_PACK) 322 | reply(msg, None, replymsg) 323 | 324 | def setposition(update, context): 325 | msg = update.effective_message 326 | if not msg.reply_to_message.sticker.set_name in str(sql.list_packs(update.effective_user.id)): 327 | reply(msg, s.NOT_YOUR_PACK) 328 | return 329 | try: 330 | position = int(context.args[-1]) 331 | except: 332 | replymsg = msg.reply_markdown(s.SETPOSITION_INVALID_INPUT) 333 | return 334 | if not msg.reply_to_message: 335 | replymsg = msg.reply_text(s.STEAL_NOT_REPLY) 336 | return 337 | if msg.reply_to_message.sticker: 338 | try: 339 | context.bot.set_sticker_position_in_set(msg.reply_to_message.sticker.file_id, position) 340 | replymsg = msg.reply_text("Sticker position changed.") 341 | except: 342 | replymsg = msg.reply_markdown(s.SETPOSITION_ERROR) 343 | else: 344 | replymsg = msg.reply_text(s.REPLY_NOT_MY_STICKER) 345 | reply(msg, None, replymsg) 346 | 347 | def checkpacks(bot, packs): 348 | response = False 349 | for pack in packs: 350 | try: bot.getStickerSet(pack[0]) 351 | except TelegramError as e: 352 | if e.message == "Stickerset_invalid": 353 | sql.delete_pack(pack[0]) 354 | response = True 355 | continue 356 | return(response) 357 | # checks if the pack actually exists or has been deleted 358 | # if a pack is deleted via @stickers, its packid will be deleted forever even if you try to re-create it with the same packname 359 | 360 | def mypacks(update, context): 361 | msg = update.effective_message 362 | user = update.effective_user 363 | packs = sql.list_packs(user.id) 364 | defpack = sql.get_default_pack(user.id) 365 | packlist = f"{user.first_name}'s steal pack list :\n" 366 | if checkpacks(context.bot, packs): 367 | packs = sql.list_packs(user.id) 368 | blank = packlist 369 | count = 0 370 | for pack in packs: 371 | count += 1 372 | if pack == defpack[0]: 373 | packlist += f"\n*{count}.* [{pack[3]}](t.me/addstickers/{pack[0]}) ✓" 374 | else: 375 | packlist += f"\n*{count}.* [{pack[3]}](t.me/addstickers/{pack[0]})" 376 | if packlist == blank: 377 | replymsg = msg.reply_text(s.NO_STOLEN_PACKS) 378 | else: 379 | replymsg = msg.reply_markdown(packlist) 380 | reply(msg, None, replymsg) 381 | 382 | def switch(update, context): 383 | user = update.effective_user 384 | msg = update.effective_message 385 | if not context.args: 386 | reply(msg, s.SWITCH_INVALID_INPUT) 387 | return 388 | packs = sql.list_packs(user.id) 389 | if not packs: 390 | reply(msg, s.NO_STOLEN_PACKS) 391 | return 392 | if checkpacks(context.bot, packs): 393 | packs = sql.list_packs(user.id) 394 | if context.args[-1].isdigit(): 395 | try: 396 | newdefpack = packs[int(context.args[-1])-1] 397 | defpack = sql.get_default_pack(user.id) 398 | except: 399 | reply(msg, s.SWITCH_PACK_DOESNT_EXIST) 400 | return 401 | if defpack == newdefpack: 402 | reply(msg, s.SWITCH_ALREADY_DEFAULT.format(newdefpack[3])) 403 | else: 404 | try: 405 | sql.remove_default(user.id) 406 | sql.set_default_by_id(newdefpack[0]) 407 | reply(msg, s.SWITCH_CHANGED_DEFAULT.format(newdefpack[3])) 408 | except: 409 | reply(msg, s.SWITCH_INDEX_ERROR) 410 | else: 411 | arg = ' '.join(context.args) 412 | if not sql.get_pack_by_name(arg.lower(), user.id): 413 | reply(msg, s.SWITCH_PACK_DOESNT_EXIST) 414 | return 415 | try: 416 | sql.remove_default(user.id) 417 | sql.set_default_by_name(arg.lower(), user.id) 418 | reply(msg, s.SWITCH_CHANGED_DEFAULT.format(arg)) 419 | except: 420 | reply(msg, s.SWITCH_PACKNAME_ERROR) 421 | 422 | def kstats(update, context): 423 | if update.message.from_user.id not in sudoList: 424 | reply(update.effective_message, s.NOT_SUDO) 425 | return 426 | ulist = [] 427 | db = sql.get_all() 428 | for i in db: 429 | if i[1] not in ulist: 430 | ulist.append(i[1]) 431 | ucount = len(ulist) 432 | pcount = len(db) 433 | update.effective_message.reply_text(s.STATS.format(ucount, pcount)) 434 | 435 | def start(update, context): 436 | update.effective_message.reply_text(s.START) 437 | 438 | def help(update, context): 439 | button = [InlineKeyboardButton(text="More Information", url=s.GIST)] 440 | update.effective_message.reply_text(s.HELP, reply_markup=InlineKeyboardMarkup([button])) 441 | 442 | if __name__ == "__main__": 443 | try: 444 | with open('config.json', 'r') as f: config = json.load(f) 445 | sudoList = config['sudoList'] 446 | botToken = config['botToken'] 447 | except: 448 | config ={"database": "database-name.db", "botToken": "bot-token-here", "sudoList": [12345678, 87654321]} 449 | with open('config.json', 'w') as f: json.dump(config, f, indent=4) 450 | print('Edit the config.json and add all necessary information.') 451 | 452 | updater = Updater(botToken, use_context=True) 453 | os.system("title "+ Bot(botToken).first_name) 454 | logging.basicConfig(format='\n\n%(levelname)s\n%(asctime)s\n%(name)s\n%(message)s', level=logging.ERROR) 455 | 456 | updater.dispatcher.add_handler(CommandHandler('steal', steal, pass_args=True, run_async=True)) 457 | updater.dispatcher.add_handler(CommandHandler('stealpack', stealpack, pass_args=True, run_async=True)) 458 | updater.dispatcher.add_handler(CommandHandler('delsticker', delsticker)) 459 | updater.dispatcher.add_handler(CommandHandler('delpack', delpack)) 460 | updater.dispatcher.add_handler(CommandHandler('setposition', setposition, pass_args=True)) 461 | updater.dispatcher.add_handler(CommandHandler('switch', switch, pass_args=True)) 462 | updater.dispatcher.add_handler(CommandHandler('mypacks', mypacks, run_async=True)) 463 | updater.dispatcher.add_handler(CommandHandler('kstats', kstats)) 464 | updater.dispatcher.add_handler(CommandHandler('start', start)) 465 | updater.dispatcher.add_handler(CommandHandler('help', help)) 466 | 467 | logging.info('Bot Started.') 468 | updater.start_polling() 469 | updater.idle() 470 | --------------------------------------------------------------------------------