Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
the-blank-x committed Oct 16, 2020
0 parents commit f578c30
Show file tree
Hide file tree
Showing 27 changed files with 1,823 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__pycache__/
sessions/
config.yaml
16 changes: 16 additions & 0 deletions example-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
telegram:
api_id: 0
api_hash: https://my.telegram.org
slave_bot_token: https://t.me/BotFather
config:
prefixes:
- .
sessions:
- blankie
- knees
- nezuko
log_chat: -1001278205033
spamwatch_api: https://t.me/SpamWatchBot
log_user_joins: false
log_user_adds: true
log_reports: true
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pyrogram
tgcrypto
requests
aiohttp
PyYAML
183 changes: 183 additions & 0 deletions sukuinote/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import os
import html
import time
import logging
import asyncio
import traceback
import functools
import yaml
import aiohttp
from datetime import timedelta
from pyrogram import Client, StopPropagation, ContinuePropagation
from pyrogram.types import Chat, User
from pyrogram.parser import parser
from pyrogram.errors.exceptions.bad_request_400 import PeerIdInvalid, ChannelInvalid

logging.basicConfig(level=logging.INFO)
with open('config.yaml') as config:
config = yaml.safe_load(config)
loop = asyncio.get_event_loop()
help_dict = dict()

apps = []
app_user_ids = dict()
# this code here exists because i can't be fucked
class Parser(parser.Parser):
async def parse(self, text, mode):
if mode == 'through':
return text
return await super().parse(text, mode)
for session_name in config['config']['sessions']:
app = Client(session_name, api_id=config['telegram']['api_id'], api_hash=config['telegram']['api_hash'], plugins={'root': os.path.join(__package__, 'plugins')}, parse_mode='html', workdir='sessions')
app.parser = Parser(app)
apps.append(app)
slave = Client('sukuinote-slave', api_id=config['telegram']['api_id'], api_hash=config['telegram']['api_hash'], plugins={'root': os.path.join(__package__, 'slave-plugins')}, parse_mode='html', bot_token=config['telegram']['slave_bot_token'], workdir='sessions')
slave.parser = Parser(slave)
session = aiohttp.ClientSession()

async def get_entity(client, entity):
entity_client = client
if not isinstance(entity, Chat):
try:
entity = int(entity)
except ValueError:
pass
except TypeError:
entity = entity.id
try:
entity = await client.get_chat(entity)
except (PeerIdInvalid, ChannelInvalid):
for app in apps:
if app != client:
try:
entity = await app.get_chat(entity)
except (PeerIdInvalid, ChannelInvalid):
pass
else:
entity_client = app
break
else:
entity = await slave.get_chat(entity)
entity_client = slave
return entity, entity_client

async def get_user(client, entity):
entity_client = client
if not isinstance(entity, User):
try:
entity = int(entity)
except ValueError:
pass
except TypeError:
entity = entity.id
try:
entity = await client.get_users(entity)
except PeerIdInvalid:
for app in apps:
if app != client:
try:
entity = await app.get_users(entity)
except PeerIdInvalid:
pass
else:
entity_client = app
break
else:
entity = await slave.get_users(entity)
entity_client = slave
return entity, entity_client

def log_errors(func):
@functools.wraps(func)
async def wrapper(client, *args):
try:
await func(client, *args)
except (StopPropagation, ContinuePropagation):
raise
except Exception:
tb = traceback.format_exc()
try:
await slave.send_message(config['config']['log_chat'], f'Exception occured in {func.__name__}\n\n{tb}', parse_mode=None)
except Exception:
logging.exception('Failed to log exception for %s as slave', func.__name__)
tb = traceback.format_exc()
for app in apps:
try:
await app.send_message(config['config']['log_chat'], f'Exception occured in {func.__name__}\n\n{tb}', parse_mode=None)
except Exception:
logging.exception('Failed to log exception for %s as app', func.__name__)
tb = traceback.format_exc()
else:
break
raise
raise
return wrapper

def public_log_errors(func):
@functools.wraps(func)
async def wrapper(client, message):
try:
await func(client, message)
except (StopPropagation, ContinuePropagation):
raise
except Exception:
await message.reply_text(traceback.format_exc(), parse_mode=None)
raise
return wrapper

# https://stackoverflow.com/a/49361727
def format_bytes(size):
size = int(size)
# 2**10 = 1024
power = 1000
n = 0
power_labels = {0 : '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
while size > power:
size /= power
n += 1
return f"{size:.2f} {power_labels[n]+'B'}"

# https://stackoverflow.com/a/34325723
def return_progress_string(current, total):
filled_length = int(30 * current // total)
return '[' + '=' * filled_length + ' ' * (30 - filled_length) + ']'

# https://stackoverflow.com/a/852718
# https://stackoverflow.com/a/775095
def calculate_eta(current, total, start_time):
if not current:
return '00:00:00'
end_time = time.time()
elapsed_time = end_time - start_time
seconds = (elapsed_time * (total / current)) - elapsed_time
thing = ''.join(str(timedelta(seconds=seconds)).split('.')[:-1]).split(', ')
thing[-1] = thing[-1].rjust(8, '0')
return ', '.join(thing)

progress_callback_data = dict()
async def progress_callback(current, total, reply, text, upload):
message_identifier = (reply.chat.id, reply.message_id)
last_edit_time, prevtext, start_time = progress_callback_data.get(message_identifier, (0, None, time.time()))
if current == total:
try:
progress_callback_data.pop(message_identifier)
except KeyError:
pass
elif (time.time() - last_edit_time) > 1:
handle = 'Upload' if upload else 'Download'
if last_edit_time:
speed = format_bytes((total - current) / (time.time() - start_time))
else:
speed = '0 B'
text = f'''{text}
<code>{return_progress_string(current, total)}</code>
<b>Total Size:</b> {format_bytes(total)}
<b>{handle}ed Size:</b> {format_bytes(current)}
<b>{handle} Speed:</b> {speed}/s
<b>ETA:</b> {calculate_eta(current, total, start_time)}'''
if prevtext != text:
await reply.edit_text(text)
prevtext = text
last_edit_time = time.time()
progress_callback_data[message_identifier] = last_edit_time, prevtext, start_time
22 changes: 22 additions & 0 deletions sukuinote/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import asyncio
from pyrogram import idle
from . import loop, apps, slave, app_user_ids, session

async def main():
async def _start_app(app):
await app.start()
asyncio.create_task(_get_me_loop(app))
async def _get_me_loop(app):
while True:
try:
me = await app.get_me()
app_user_ids[me.id] = me
except:
pass
await asyncio.sleep(60)
await asyncio.gather(*(_start_app(app) for app in apps), slave.start())
await idle()
await asyncio.gather(*(app.stop() for app in apps), slave.stop())
await session.close()

loop.run_until_complete(main())
54 changes: 54 additions & 0 deletions sukuinote/plugins/admins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import html
from pyrogram import Client, filters
from .. import config, help_dict, log_errors, public_log_errors, get_entity

ZWS = '\u200B'
def _generate_sexy(entity, ping):
text = entity.first_name
if entity.last_name:
text += f' {entity.last_name}'
sexy_text = '<code>[DELETED]</code>' if entity.is_deleted else html.escape(text or 'Empty???')
if not entity.is_deleted:
if ping:
sexy_text = f'<a href="tg://user?id={entity.id}">{sexy_text}</a>'
elif entity.username:
sexy_text = f'<a href="https://t.me/{entity.username}">{sexy_text}</a>'
elif not ping:
sexy_text = sexy_text.replace('@', f'@{ZWS}')
if entity.is_bot:
sexy_text += ' <code>[BOT]</code>'
if entity.is_verified:
sexy_text += ' <code>[VERIFIED]</code>'
if entity.is_support:
sexy_text += ' <code>[SUPPORT]</code>'
if entity.is_scam:
sexy_text += ' <code>[SCAM]</code>'
return sexy_text

@Client.on_message(~filters.sticker & ~filters.via_bot & ~filters.edited & filters.outgoing & filters.command(['admin', 'admins'], prefixes=config['config']['prefixes']))
@log_errors
@public_log_errors
async def admins(client, message):
chat, entity_client = message.chat, client
command = message.command
command.pop(0)
if command:
chat = ' '.join(command)
try:
chat = int(chat)
except ValueError:
pass
chat, entity_client = await get_entity(client, chat)
text_unping = text_ping = ''
async for i in entity_client.iter_chat_members(chat.id, filter='administrators'):
text_unping += f'\n[<code>{i.user.id}</code>] {_generate_sexy(i.user, False)}'
text_ping += f'\n[<code>{i.user.id}</code>] {_generate_sexy(i.user, True)}'
if i.title:
text_unping += f' // {html.escape(i.title.replace("@", "@" + ZWS))}'
text_ping += f' // {html.escape(i.title)}'
reply = await message.reply_text(text_unping, disable_web_page_preview=True)
await reply.edit_text(text_ping, disable_web_page_preview=True)

help_dict['admins'] = ('Admins',
'''{prefix}admins <i>[chat]</i> - Lists the admins in <i>[chat]</i>
Aliases: {prefix}admin''')
48 changes: 48 additions & 0 deletions sukuinote/plugins/anilist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import html
from pyrogram import Client, filters
from pyrogram.types.messages_and_media import Photo
from pyrogram.errors.exceptions.forbidden_403 import Forbidden
from .. import slave, config, help_dict, log_errors, public_log_errors

@Client.on_message(~filters.sticker & ~filters.via_bot & ~filters.edited & filters.outgoing & filters.command(['anilist', 'al', 'alc', 'alchar', 'alcharacter', 'anilistc', 'anilistchar', 'anilistcharacter'], prefixes=config['config']['prefixes']))
@log_errors
@public_log_errors
async def anilist(client, message):
bot = await slave.get_me()
query = message.command
page = 1
character = 'c' in query.pop(0)
if query and query[0].isnumeric():
page = int(query.pop(0))
page -= 1
if page < 0:
page = 0
elif page > 9:
page = 9
query = ' '.join(query)
if not query:
return
results = await client.get_inline_bot_results(bot.username or bot.id, f'al{"c" if character else ""} ' + query)
if not results.results:
await message.reply_text('No results')
return
try:
await message.reply_inline_bot_result(results.query_id, results.results[page].id)
except IndexError:
await message.reply_text(f'There are only {len(results.results)} results')
except Forbidden:
text = {'message': results.results[page].send_message.message, 'entities': results.results[page].send_message.entities}
try:
photo = Photo._parse(client, results.results[page].photo)
await message.reply_cached_media(photo.file_id, photo.file_ref, caption=text, parse_mode='through')
except Forbidden:
await message.reply_text(text, disable_web_page_preview=True, parse_mode='through')

help_dict['anilist'] = ('Anilist',
'''{prefix}anilist <i>&lt;query&gt;</i> - Searches for anime/manga named <i>&lt;query&gt;</i> on Anilist
Aliases: {prefix}al
Can also be activated inline with: @{bot} anilist <i>&lt;query&gt;</i> or @{bot} al <i>&lt;query&gt;</i>
{prefix}anilistc <i>&lt;query&gt;</i> - Searches for characters named <i>&lt;query&gt;</i> on Anilist
Aliases: {prefix}alc, alchar, alcharacter, anilistchar, anilistcharacter
Can also be activated inline with: @{bot} anilistc <i>&lt;query&gt;</i> or @{bot} alc <i>&lt;query&gt;</i>''')
32 changes: 32 additions & 0 deletions sukuinote/plugins/cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import html
import tempfile
from pyrogram import Client, filters
from .. import config, help_dict, log_errors, session, progress_callback, public_log_errors

@Client.on_message(~filters.sticker & ~filters.via_bot & ~filters.edited & filters.outgoing & filters.command('cat', prefixes=config['config']['prefixes']))
@log_errors
@public_log_errors
async def cat(client, message):
media = message.document
if not media and not getattr(message.reply_to_message, 'empty', True):
media = message.reply_to_message.document
if not media:
await message.reply_text('Document required')
return
done = False
with tempfile.NamedTemporaryFile() as file:
reply = await message.reply_text('Downloading...')
await client.download_media(media, file_name=file.name, progress=progress_callback, progress_args=(reply, 'Downloading...', False))
with open(file.name) as nfile:
while True:
chunk = nfile.read(4096)
if not chunk:
break
chunk = f'<code>{html.escape(chunk)}</code>'
if done:
await message.reply_text(chunk, quote=False)
else:
await reply.edit_text(chunk)
done = True

help_dict['cat'] = ('cat', '{prefix}cat <i>(as caption of text file or reply)</i> - Outputs file\'s text to Telegram')
Loading

0 comments on commit f578c30

Please sign in to comment.