mirror of
https://github.com/Xtao-Labs/sticker-captcha-bot.git
synced 2024-11-23 08:20:54 +00:00
init
This commit is contained in:
parent
61b590b087
commit
9e2d53f464
6
.gitignore
vendored
6
.gitignore
vendored
@ -127,3 +127,9 @@ dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# data
|
||||
data/
|
||||
*.session*
|
||||
config.yml
|
||||
.idea/
|
||||
|
16
config.gen.yml
Normal file
16
config.gen.yml
Normal file
@ -0,0 +1,16 @@
|
||||
# API Credentials of your telegram application created at https://my.telegram.org/apps
|
||||
api_id: "ID_HERE"
|
||||
api_hash: "HASH_HERE"
|
||||
|
||||
# bot token
|
||||
bot_token: "TOKEN_HERE"
|
||||
|
||||
# Either debug logging is enabled or not
|
||||
debug: "False"
|
||||
|
||||
# socks5
|
||||
proxy_addr: ""
|
||||
proxy_port: ""
|
||||
|
||||
# ipv6
|
||||
ipv6: "False"
|
19
plugins/invite.py
Normal file
19
plugins/invite.py
Normal file
@ -0,0 +1,19 @@
|
||||
import contextlib
|
||||
|
||||
from pyrogram.types import ChatMemberUpdated
|
||||
|
||||
from sticker.single_utils import Client
|
||||
from sticker import bot
|
||||
|
||||
MSG_PUBLIC = """您好,我发现此群组为公开群组,您需要联系创建者打开 `管理员批准后才能入群` 功能,我才能正常工作。"""
|
||||
MSG_SUCCESS = """验证成功,您已经成为群组的一员了!"""
|
||||
MSG_FAILURE = """验证失败,请重试。"""
|
||||
|
||||
|
||||
@bot.on_chat_member_updated()
|
||||
async def invite(client: Client, chat_member_updated: ChatMemberUpdated):
|
||||
chat = chat_member_updated.chat
|
||||
if user := chat_member_updated.new_chat_member:
|
||||
if user.user.is_self and chat.username:
|
||||
with contextlib.suppress(Exception):
|
||||
await client.send_message(chat.id, MSG_PUBLIC)
|
31
plugins/new_member.py
Normal file
31
plugins/new_member.py
Normal file
@ -0,0 +1,31 @@
|
||||
import contextlib
|
||||
|
||||
from pyrogram.types import ChatJoinRequest
|
||||
from pyrogram import filters
|
||||
|
||||
from sticker.single_utils import Client
|
||||
from sticker import bot
|
||||
|
||||
from pyromod.utils.errors import TimeoutConversationError
|
||||
|
||||
|
||||
MSG = """您好,群组 %s 开启了验证功能。
|
||||
|
||||
您需要在 30 秒内发送任意一个 贴纸 来完成验证。"""
|
||||
MSG_SUCCESS = """验证成功,您已经成为群组的一员了!"""
|
||||
MSG_FAILURE = """验证失败,请重试。"""
|
||||
|
||||
|
||||
@bot.on_chat_join_request()
|
||||
async def new_member(client: Client, chat_join_request: ChatJoinRequest):
|
||||
chat = chat_join_request.chat
|
||||
user = chat_join_request.from_user
|
||||
try:
|
||||
await client.ask(user.id, MSG % chat.title, filters=filters.sticker, timeout=30)
|
||||
with contextlib.suppress(Exception):
|
||||
await client.send_message(user.id, MSG_SUCCESS)
|
||||
await chat_join_request.approve()
|
||||
except TimeoutConversationError:
|
||||
with contextlib.suppress(Exception):
|
||||
await client.send_message(user.id, MSG_FAILURE)
|
||||
await chat_join_request.decline()
|
24
plugins/start.py
Normal file
24
plugins/start.py
Normal file
@ -0,0 +1,24 @@
|
||||
from pyrogram import filters
|
||||
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
from sticker.single_utils import Message, Client
|
||||
from sticker import bot
|
||||
|
||||
|
||||
@bot.on_message(filters=filters.private & filters.command("start"))
|
||||
async def start(client: Client, message: Message):
|
||||
me = await client.get_me()
|
||||
await message.reply(
|
||||
f"""你好,我是 <b>{me.first_name}</b> !
|
||||
|
||||
我可以主动私聊申请加入群组的新成员进行贴纸验证。""",
|
||||
quote=True,
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton(
|
||||
"Github",
|
||||
url="https://github.com/Xtao-Labs/sticker-captcha-bot")],
|
||||
[InlineKeyboardButton(
|
||||
"邀请入群",
|
||||
url=f"https://t.me/{me.username}?startgroup=start&admin=can_invite_users")]
|
||||
]))
|
21
pyromod/__init__.py
Normal file
21
pyromod/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
pyromod - A monkeypatched add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
__version__ = "1.5"
|
21
pyromod/filters/__init__.py
Normal file
21
pyromod/filters/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from .filters import dice
|
28
pyromod/filters/filters.py
Normal file
28
pyromod/filters/filters.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pyrogram
|
||||
|
||||
|
||||
def dice(ctx, message):
|
||||
return hasattr(message, 'dice') and message.dice
|
||||
|
||||
|
||||
pyrogram.filters.dice = dice
|
20
pyromod/helpers/__init__.py
Normal file
20
pyromod/helpers/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply
|
75
pyromod/helpers/helpers.py
Normal file
75
pyromod/helpers/helpers.py
Normal file
@ -0,0 +1,75 @@
|
||||
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ForceReply
|
||||
|
||||
|
||||
def ikb(rows=None):
|
||||
if rows is None:
|
||||
rows = []
|
||||
lines = []
|
||||
for row in rows:
|
||||
line = []
|
||||
for button in row:
|
||||
button = btn(*button) # InlineKeyboardButton
|
||||
line.append(button)
|
||||
lines.append(line)
|
||||
return InlineKeyboardMarkup(inline_keyboard=lines)
|
||||
# return {'inline_keyboard': lines}
|
||||
|
||||
|
||||
def btn(text, value, type='callback_data'):
|
||||
return InlineKeyboardButton(text, **{type: value})
|
||||
# return {'text': text, type: value}
|
||||
|
||||
|
||||
# The inverse of above
|
||||
def bki(keyboard):
|
||||
lines = []
|
||||
for row in keyboard.inline_keyboard:
|
||||
line = []
|
||||
for button in row:
|
||||
button = ntb(button) # btn() format
|
||||
line.append(button)
|
||||
lines.append(line)
|
||||
return lines
|
||||
# return ikb() format
|
||||
|
||||
|
||||
def ntb(button):
|
||||
for btn_type in ['callback_data', 'url', 'switch_inline_query', 'switch_inline_query_current_chat',
|
||||
'callback_game']:
|
||||
value = getattr(button, btn_type)
|
||||
if value:
|
||||
break
|
||||
button = [button.text, value]
|
||||
if btn_type != 'callback_data':
|
||||
button.append(btn_type)
|
||||
return button
|
||||
# return {'text': text, type: value}
|
||||
|
||||
|
||||
def kb(rows=None, **kwargs):
|
||||
if rows is None:
|
||||
rows = []
|
||||
lines = []
|
||||
for row in rows:
|
||||
line = []
|
||||
for button in row:
|
||||
button_type = type(button)
|
||||
if button_type == str:
|
||||
button = KeyboardButton(button)
|
||||
elif button_type == dict:
|
||||
button = KeyboardButton(**button)
|
||||
|
||||
line.append(button)
|
||||
lines.append(line)
|
||||
return ReplyKeyboardMarkup(keyboard=lines, **kwargs)
|
||||
|
||||
|
||||
kbtn = KeyboardButton
|
||||
|
||||
|
||||
def force_reply(selective=True):
|
||||
return ForceReply(selective=selective)
|
||||
|
||||
|
||||
def array_chunk(input_, size):
|
||||
return [input_[i:i + size] for i in range(0, len(input_), size)]
|
21
pyromod/listen/__init__.py
Normal file
21
pyromod/listen/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from .listen import Client, MessageHandler, Chat, User
|
164
pyromod/listen/listen.py
Normal file
164
pyromod/listen/listen.py
Normal file
@ -0,0 +1,164 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
import pyrogram
|
||||
|
||||
from typing import Dict
|
||||
from sticker.scheduler import add_delete_message_job
|
||||
from ..utils import patch, patchable
|
||||
from ..utils.errors import ListenerCanceled, TimeoutConversationError
|
||||
|
||||
pyrogram.errors.ListenerCanceled = ListenerCanceled
|
||||
|
||||
|
||||
@patch(pyrogram.client.Client)
|
||||
class Client:
|
||||
@patchable
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.listening = {}
|
||||
self.using_mod = True
|
||||
|
||||
self.old__init__(*args, **kwargs)
|
||||
|
||||
@patchable
|
||||
async def listen(self, chat_id, filters=None, timeout=None):
|
||||
if type(chat_id) != int:
|
||||
chat = await self.get_chat(chat_id)
|
||||
chat_id = chat.id
|
||||
|
||||
future = self.loop.create_future()
|
||||
future.add_done_callback(
|
||||
functools.partial(self.clear_listener, chat_id)
|
||||
)
|
||||
self.listening.update({
|
||||
chat_id: {"future": future, "filters": filters}
|
||||
})
|
||||
try:
|
||||
return await asyncio.wait_for(future, timeout)
|
||||
except asyncio.exceptions.TimeoutError as e:
|
||||
raise TimeoutConversationError() from e
|
||||
|
||||
@patchable
|
||||
async def ask(self, chat_id, text, filters=None, timeout=None, *args, **kwargs):
|
||||
request = await self.send_message(chat_id, text, *args, **kwargs)
|
||||
response = await self.listen(chat_id, filters, timeout)
|
||||
response.request = request
|
||||
return response
|
||||
|
||||
@patchable
|
||||
def clear_listener(self, chat_id, future):
|
||||
if future == self.listening[chat_id]["future"]:
|
||||
self.listening.pop(chat_id, None)
|
||||
|
||||
@patchable
|
||||
def cancel_listener(self, chat_id):
|
||||
listener = self.listening.get(chat_id)
|
||||
if not listener or listener['future'].done():
|
||||
return
|
||||
|
||||
listener['future'].set_exception(ListenerCanceled())
|
||||
self.clear_listener(chat_id, listener['future'])
|
||||
|
||||
@patchable
|
||||
def cancel_all_listener(self):
|
||||
for chat_id in self.listening:
|
||||
self.cancel_listener(chat_id)
|
||||
|
||||
|
||||
@patch(pyrogram.handlers.message_handler.MessageHandler)
|
||||
class MessageHandler:
|
||||
@patchable
|
||||
def __init__(self, callback: callable, filters=None):
|
||||
self.user_callback = callback
|
||||
self.old__init__(self.resolve_listener, filters)
|
||||
|
||||
@patchable
|
||||
async def resolve_listener(self, client, message, *args):
|
||||
listener = client.listening.get(message.chat.id)
|
||||
if listener and not listener['future'].done():
|
||||
listener['future'].set_result(message)
|
||||
else:
|
||||
if listener and listener['future'].done():
|
||||
client.clear_listener(message.chat.id, listener['future'])
|
||||
await self.user_callback(client, message, *args)
|
||||
|
||||
@patchable
|
||||
async def check(self, client, update):
|
||||
listener = client.listening.get(update.chat.id)
|
||||
|
||||
if listener and not listener['future'].done():
|
||||
return await listener['filters'](client, update) if callable(listener['filters']) else True
|
||||
|
||||
return (
|
||||
await self.filters(client, update)
|
||||
if callable(self.filters)
|
||||
else True
|
||||
)
|
||||
|
||||
|
||||
@patch(pyrogram.types.user_and_chats.chat.Chat)
|
||||
class Chat(pyrogram.types.Chat):
|
||||
@patchable
|
||||
def listen(self, *args, **kwargs):
|
||||
return self._client.listen(self.id, *args, **kwargs)
|
||||
|
||||
@patchable
|
||||
def ask(self, *args, **kwargs):
|
||||
return self._client.ask(self.id, *args, **kwargs)
|
||||
|
||||
@patchable
|
||||
def cancel_listener(self):
|
||||
return self._client.cancel_listener(self.id)
|
||||
|
||||
|
||||
@patch(pyrogram.types.user_and_chats.user.User)
|
||||
class User(pyrogram.types.User):
|
||||
@patchable
|
||||
def listen(self, *args, **kwargs):
|
||||
return self._client.listen(self.id, *args, **kwargs)
|
||||
|
||||
@patchable
|
||||
def ask(self, *args, **kwargs):
|
||||
return self._client.ask(self.id, *args, **kwargs)
|
||||
|
||||
@patchable
|
||||
def cancel_listener(self):
|
||||
return self._client.cancel_listener(self.id)
|
||||
|
||||
|
||||
@patch(pyrogram.types.messages_and_media.Message)
|
||||
class Message(pyrogram.types.Message):
|
||||
@patchable
|
||||
async def safe_delete(self, revoke: bool = True):
|
||||
try:
|
||||
return await self._client.delete_messages(
|
||||
chat_id=self.chat.id,
|
||||
message_ids=self.id,
|
||||
revoke=revoke
|
||||
)
|
||||
except Exception as e: # noqa
|
||||
return False
|
||||
|
||||
@patchable
|
||||
async def delay_delete(self, delay: int = 60):
|
||||
add_delete_message_job(self, delay)
|
20
pyromod/nav/__init__.py
Normal file
20
pyromod/nav/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from .pagination import Pagination
|
91
pyromod/nav/pagination.py
Normal file
91
pyromod/nav/pagination.py
Normal file
@ -0,0 +1,91 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import math
|
||||
from ..helpers import array_chunk
|
||||
|
||||
|
||||
class Pagination:
|
||||
def __init__(self, objects, page_data=None, item_data=None, item_title=None):
|
||||
default_page_callback = (lambda x: str(x))
|
||||
default_item_callback = (lambda i, pg: f'[{pg}] {i}')
|
||||
self.objects = objects
|
||||
self.page_data = page_data or default_page_callback
|
||||
self.item_data = item_data or default_item_callback
|
||||
self.item_title = item_title or default_item_callback
|
||||
|
||||
def create(self, page, lines=5, columns=1):
|
||||
quant_per_page = lines * columns
|
||||
page = 1 if page <= 0 else page
|
||||
offset = (page - 1) * quant_per_page
|
||||
stop = offset + quant_per_page
|
||||
cutted = self.objects[offset:stop]
|
||||
|
||||
total = len(self.objects)
|
||||
pages_range = [*range(1, math.ceil(total / quant_per_page) + 1)] # each item is a page
|
||||
last_page = len(pages_range)
|
||||
|
||||
nav = []
|
||||
if page <= 3:
|
||||
for n in [1, 2, 3]:
|
||||
if n not in pages_range:
|
||||
continue
|
||||
text = f"· {n} ·" if n == page else n
|
||||
nav.append((text, self.page_data(n)))
|
||||
if last_page >= 4:
|
||||
nav.append(
|
||||
('4 ›' if last_page > 5 else 4, self.page_data(4))
|
||||
)
|
||||
if last_page > 4:
|
||||
nav.append(
|
||||
(f'{last_page} »' if last_page > 5 else last_page, self.page_data(last_page))
|
||||
)
|
||||
elif page >= last_page - 2:
|
||||
nav.extend(
|
||||
[
|
||||
('« 1' if last_page > 5 else 1, self.page_data(1)),
|
||||
(
|
||||
f'‹ {last_page - 3}' if last_page > 5 else last_page - 3,
|
||||
self.page_data(last_page - 3),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
for n in range(last_page - 2, last_page + 1):
|
||||
text = f"· {n} ·" if n == page else n
|
||||
nav.append((text, self.page_data(n)))
|
||||
else:
|
||||
nav = [
|
||||
('« 1', self.page_data(1)),
|
||||
(f'‹ {page - 1}', self.page_data(page - 1)),
|
||||
(f'· {page} ·', "noop"),
|
||||
(f'{page + 1} ›', self.page_data(page + 1)),
|
||||
(f'{last_page} »', self.page_data(last_page)),
|
||||
]
|
||||
|
||||
buttons = [
|
||||
(self.item_title(item, page), self.item_data(item, page))
|
||||
for item in cutted
|
||||
]
|
||||
|
||||
kb_lines = array_chunk(buttons, columns)
|
||||
if last_page > 1:
|
||||
kb_lines.append(nav)
|
||||
|
||||
return kb_lines
|
21
pyromod/utils/__init__.py
Normal file
21
pyromod/utils/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from .utils import patch, patchable
|
19
pyromod/utils/errors.py
Normal file
19
pyromod/utils/errors.py
Normal file
@ -0,0 +1,19 @@
|
||||
class TimeoutConversationError(Exception):
|
||||
"""
|
||||
Occurs when the conversation times out.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"Response read timed out"
|
||||
)
|
||||
|
||||
|
||||
class ListenerCanceled(Exception):
|
||||
"""
|
||||
Occurs when the listener is canceled.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"Listener was canceled"
|
||||
)
|
38
pyromod/utils/utils.py
Normal file
38
pyromod/utils/utils.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""
|
||||
pyromod - A monkeypatcher add-on for Pyrogram
|
||||
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
||||
|
||||
This file is part of pyromod.
|
||||
|
||||
pyromod 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.
|
||||
|
||||
pyromod 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 pyromod. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def patch(obj):
|
||||
def is_patchable(item):
|
||||
return getattr(item[1], 'patchable', False)
|
||||
|
||||
def wrapper(container):
|
||||
for name, func in filter(is_patchable, container.__dict__.items()):
|
||||
old = getattr(obj, name, None)
|
||||
setattr(obj, f'old{name}', old)
|
||||
setattr(obj, name, func)
|
||||
return container
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def patchable(func):
|
||||
func.patchable = True
|
||||
return func
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
pyrogram==2.0.30
|
||||
TgCrypto>=1.2.3
|
||||
PyYAML>=6.0
|
||||
coloredlogs>=15.0.1
|
||||
sqlitedict==2.0.0
|
||||
apscheduler==3.9.1
|
||||
pytz
|
48
sticker/__init__.py
Normal file
48
sticker/__init__.py
Normal file
@ -0,0 +1,48 @@
|
||||
import contextlib
|
||||
from coloredlogs import ColoredFormatter
|
||||
from datetime import datetime, timezone
|
||||
from logging import getLogger, StreamHandler, CRITICAL, INFO, basicConfig, DEBUG
|
||||
|
||||
from sticker.config import Config
|
||||
from sticker.scheduler import scheduler
|
||||
import pyromod.listen
|
||||
from pyrogram import Client
|
||||
import sys
|
||||
|
||||
logs = getLogger(__name__)
|
||||
logging_format = "%(levelname)s [%(asctime)s] [%(name)s] %(message)s"
|
||||
logging_handler = StreamHandler()
|
||||
logging_handler.setFormatter(ColoredFormatter(logging_format))
|
||||
root_logger = getLogger()
|
||||
root_logger.setLevel(DEBUG if Config.DEBUG else CRITICAL)
|
||||
root_logger.addHandler(logging_handler)
|
||||
pyro_logger = getLogger("pyrogram")
|
||||
pyro_logger.setLevel(DEBUG)
|
||||
pyro_logger.addHandler(logging_handler)
|
||||
basicConfig(level=DEBUG if Config.DEBUG else INFO)
|
||||
logs.setLevel(DEBUG if Config.DEBUG else INFO)
|
||||
|
||||
# easy check
|
||||
if not Config.API_ID:
|
||||
logs.error("Api-ID Not Found!")
|
||||
sys.exit(1)
|
||||
elif not Config.API_HASH:
|
||||
logs.error("Api-Hash Not Found!")
|
||||
sys.exit(1)
|
||||
|
||||
start_time = datetime.now(timezone.utc)
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
import uvloop # noqa
|
||||
uvloop.install()
|
||||
|
||||
if not scheduler.running:
|
||||
scheduler.start()
|
||||
bot = Client("sticker",
|
||||
bot_token=Config.BOT_TOKEN,
|
||||
session_string=Config.STRING_SESSION,
|
||||
api_id=Config.API_ID,
|
||||
api_hash=Config.API_HASH,
|
||||
ipv6=Config.IPV6,
|
||||
proxy=Config.PROXY,
|
||||
plugins={"root": "plugins"})
|
12
sticker/__main__.py
Normal file
12
sticker/__main__.py
Normal file
@ -0,0 +1,12 @@
|
||||
from pyrogram import idle
|
||||
|
||||
from sticker import bot, logs
|
||||
|
||||
|
||||
async def main():
|
||||
await bot.start()
|
||||
logs.info("bot started.")
|
||||
await idle()
|
||||
await bot.stop()
|
||||
|
||||
bot.run(main())
|
50
sticker/config.py
Normal file
50
sticker/config.py
Normal file
@ -0,0 +1,50 @@
|
||||
import os
|
||||
from json import load as load_json
|
||||
import sys
|
||||
from yaml import load, FullLoader, safe_load
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
def strtobool(val):
|
||||
"""Convert a string representation of truth to true (1) or false (0).
|
||||
|
||||
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
||||
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
|
||||
'val' is anything else.
|
||||
"""
|
||||
val = val.lower()
|
||||
if val in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
return 1
|
||||
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return 0
|
||||
else:
|
||||
raise ValueError("invalid truth value %r" % (val,))
|
||||
|
||||
|
||||
try:
|
||||
config = load(open(r"config.yml"), Loader=FullLoader)
|
||||
except FileNotFoundError:
|
||||
print("The configuration file does not exist, and a new configuration file is being generated.")
|
||||
copyfile(f"{os.getcwd()}{os.sep}config.gen.yml", "config.yml")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Config:
|
||||
try:
|
||||
API_ID = int(os.environ.get("API_ID", config["api_id"]))
|
||||
API_HASH = os.environ.get("API_HASH", config["api_hash"])
|
||||
BOT_TOKEN = os.environ.get("BOT_TOKEN", config["bot_token"])
|
||||
STRING_SESSION = os.environ.get("STRING_SESSION")
|
||||
DEBUG = strtobool(os.environ.get("PGM_DEBUG", config["debug"]))
|
||||
IPV6 = strtobool(os.environ.get("PGM_IPV6", config["ipv6"]))
|
||||
PROXY_ADDRESS = os.environ.get("PGM_PROXY_ADDRESS", config["proxy_addr"])
|
||||
PROXY_PORT = os.environ.get("PGM_PROXY_PORT", config["proxy_port"])
|
||||
PROXY = None
|
||||
if PROXY_ADDRESS and PROXY_PORT:
|
||||
PROXY = dict(
|
||||
hostname=PROXY_ADDRESS,
|
||||
port=PROXY_PORT,
|
||||
)
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
25
sticker/scheduler.py
Normal file
25
sticker/scheduler.py
Normal file
@ -0,0 +1,25 @@
|
||||
import contextlib
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from sticker.single_utils import Message
|
||||
|
||||
scheduler = AsyncIOScheduler(timezone="Asia/ShangHai")
|
||||
|
||||
|
||||
async def delete_message(message: Message) -> bool:
|
||||
with contextlib.suppress(Exception):
|
||||
await message.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_delete_message_job(message: Message, delete_seconds: int = 60):
|
||||
scheduler.add_job(
|
||||
delete_message, "date",
|
||||
id=f"{message.chat.id}|{message.id}|delete_message",
|
||||
name=f"{message.chat.id}|{message.id}|delete_message",
|
||||
args=[message],
|
||||
run_date=datetime.datetime.now(pytz.timezone("Asia/Shanghai")) + datetime.timedelta(seconds=delete_seconds),
|
||||
replace_existing=True)
|
41
sticker/single_utils.py
Normal file
41
sticker/single_utils.py
Normal file
@ -0,0 +1,41 @@
|
||||
import contextlib
|
||||
from os import sep, remove, mkdir
|
||||
from os.path import exists
|
||||
from typing import Optional
|
||||
|
||||
from pyrogram import Client
|
||||
from pyrogram.types import Message
|
||||
|
||||
from pyromod.utils.errors import TimeoutConversationError, ListenerCanceled
|
||||
|
||||
from sqlitedict import SqliteDict
|
||||
|
||||
# init folders
|
||||
if not exists("data"):
|
||||
mkdir("data")
|
||||
sqlite = SqliteDict(f"data{sep}data.sqlite", autocommit=True)
|
||||
|
||||
|
||||
def safe_remove(name: str) -> None:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
remove(name)
|
||||
|
||||
|
||||
class Client(Client): # noqa
|
||||
async def listen(self, chat_id, filters=None, timeout=None) -> Optional[Message]:
|
||||
return
|
||||
|
||||
async def ask(self, chat_id, text, filters=None, timeout=None, *args, **kwargs) -> Optional[Message]:
|
||||
return
|
||||
|
||||
def cancel_listener(self, chat_id):
|
||||
""" Cancel the conversation with the given chat_id. """
|
||||
return
|
||||
|
||||
|
||||
class Message(Message): # noqa
|
||||
async def delay_delete(self, delete_seconds: int = 60) -> Optional[bool]:
|
||||
return
|
||||
|
||||
async def safe_delete(self, revoke: bool = True) -> None:
|
||||
return
|
Loading…
Reference in New Issue
Block a user