1
This commit is contained in:
parent
a3275e79c1
commit
d581ecadc2
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
API_ID_1 = 24012189
|
||||
API_HASH_1 = 5a04d4beafc73f2b3a9d5a64ccb53ae5
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM python:3.11
|
||||
|
||||
WORKDIR /forwardspam_nedvizhka_Ciprus
|
||||
COPY ./ ./
|
||||
|
||||
RUN rm -rf /etc/localtime
|
||||
RUN ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime
|
||||
RUN echo "Europe/Moscow" > /etc/timezone
|
||||
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
CMD ["python","-u", "main.py"]
|
0
bot/__init__.py
Normal file
0
bot/__init__.py
Normal file
BIN
bot/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
bot/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/album_handler.cpython-310.pyc
Normal file
BIN
bot/__pycache__/album_handler.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/config.cpython-310.pyc
Normal file
BIN
bot/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/handlers.cpython-310.pyc
Normal file
BIN
bot/__pycache__/handlers.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/loader.cpython-310.pyc
Normal file
BIN
bot/__pycache__/loader.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/scheduler.cpython-310.pyc
Normal file
BIN
bot/__pycache__/scheduler.cpython-310.pyc
Normal file
Binary file not shown.
BIN
bot/__pycache__/sending.cpython-310.pyc
Normal file
BIN
bot/__pycache__/sending.cpython-310.pyc
Normal file
Binary file not shown.
72
bot/album_handler.py
Normal file
72
bot/album_handler.py
Normal file
@ -0,0 +1,72 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
from typing import Coroutine, TypeVar
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from bot import config
|
||||
from bot.loader import app1
|
||||
from bot.sending import forward_post
|
||||
|
||||
_tasks = set()
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def background(coro: Coroutine[None, None, T]) -> asyncio.Task[T]:
|
||||
loop = asyncio.get_event_loop()
|
||||
task = loop.create_task(coro)
|
||||
_tasks.add(task)
|
||||
task.add_done_callback(_tasks.remove)
|
||||
return task
|
||||
|
||||
|
||||
@dataclass
|
||||
class Album:
|
||||
media_group_id: str
|
||||
messages: list[Message]
|
||||
|
||||
|
||||
# chat_id: group_id: album
|
||||
_albums: defaultdict[int, dict[str, Album]] = defaultdict(dict)
|
||||
|
||||
|
||||
@app1.on_message(filters.media_group)
|
||||
async def on_media_group(client: Client, message: Message):
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
media_group_id = message.media_group_id
|
||||
if media_group_id is None:
|
||||
return
|
||||
|
||||
if media_group_id not in _albums[chat_id]:
|
||||
album = Album(messages=[message], media_group_id=media_group_id)
|
||||
_albums[chat_id][media_group_id] = album
|
||||
|
||||
async def task():
|
||||
await asyncio.sleep(1)
|
||||
_albums[chat_id].pop(media_group_id, None)
|
||||
try:
|
||||
album.messages.sort(key=lambda m: m.id)
|
||||
await on_album(client, album)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
background(task())
|
||||
else:
|
||||
album = _albums[chat_id][media_group_id]
|
||||
album.messages.append(message)
|
||||
finally:
|
||||
message.continue_propagation()
|
||||
|
||||
|
||||
async def on_album(client: Client, album: Album):
|
||||
"""
|
||||
Обрабатывает новые альбомы
|
||||
"""
|
||||
if album.messages[0].chat.id == config.rent_channel_id:
|
||||
await forward_post(client, album.messages[0], config.groups_for_rent)
|
||||
if album.messages[0].chat.id == config.sell_channel_id:
|
||||
await forward_post(client, album.messages[0], config.groups_for_sell)
|
26
bot/config.py
Normal file
26
bot/config.py
Normal file
@ -0,0 +1,26 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
API_ID_1 = os.getenv("API_ID_1")
|
||||
API_HASH_1 = os.getenv("API_HASH_1")
|
||||
|
||||
admins: list[int] = [5899041406, 800530092, 1046931046]
|
||||
|
||||
# Интвервал спама постов (в секундах)
|
||||
spam_interval = 200
|
||||
|
||||
|
||||
# айди канала/группы берем из веб версии тг
|
||||
# или с помощью @username_to_id_bot
|
||||
|
||||
# Канал по аренде
|
||||
rent_channel_id = -1002054380269
|
||||
# Канал по продаже
|
||||
sell_channel_id = -1002123908331
|
||||
|
||||
# Группы для перессылки
|
||||
groups_for_rent = ["-1002227589874_13"]
|
||||
groups_for_sell = ["-1002227589874_13"]
|
BIN
bot/cyprus_off.session
Normal file
BIN
bot/cyprus_off.session
Normal file
Binary file not shown.
BIN
bot/cyprus_off.session-journal
Normal file
BIN
bot/cyprus_off.session-journal
Normal file
Binary file not shown.
74
bot/handlers.py
Normal file
74
bot/handlers.py
Normal file
@ -0,0 +1,74 @@
|
||||
import asyncio
|
||||
from pprint import pprint
|
||||
|
||||
from pyrogram.enums import ParseMode
|
||||
from pyrogram.errors import PeerIdInvalid
|
||||
from pyrogram.types import Message
|
||||
|
||||
from bot import config
|
||||
from bot.loader import app1, scheduler
|
||||
from pyrogram import filters
|
||||
|
||||
from bot.sending import is_valid_time_format, sending, check_stop_sign, forward_post, get_number_posts, set_number_posts
|
||||
|
||||
|
||||
class Sending:
|
||||
SEND = True
|
||||
|
||||
|
||||
recently_media_groups = set()
|
||||
|
||||
|
||||
@app1.on_message(filters.chat(config.admins) & filters.command('current'))
|
||||
async def current_tasks(client, message: Message):
|
||||
"""Присылает текущие таймслоты на пересылку"""
|
||||
text = "Текущие расписание постов:\n" + '\n'.join([time for _, time, _ in scheduler.tasks])
|
||||
if len(scheduler.tasks) == 0: text += "Пусто"
|
||||
# print(message.text)
|
||||
await message.reply(text)
|
||||
|
||||
|
||||
@app1.on_message(filters.chat(config.admins) & filters.command('add'))
|
||||
async def add(client, message: Message):
|
||||
"""Добавление нового времени"""
|
||||
if len(message.command) < 2 or not is_valid_time_format(message.command[1]):
|
||||
await message.reply("Введите /add HH:MM")
|
||||
return
|
||||
time = message.command[1]
|
||||
await scheduler.add_task(task=sending, run_time=time)
|
||||
await current_tasks(app1, message)
|
||||
|
||||
|
||||
@app1.on_message(filters.chat(config.admins) & filters.command('delete'))
|
||||
async def delete(client, message: Message):
|
||||
"""Удаление нового времени"""
|
||||
if len(message.command) < 2 or not is_valid_time_format(message.command[1]):
|
||||
await message.reply("Введите /add HH:MM")
|
||||
return
|
||||
time = message.command[1]
|
||||
if await scheduler.remove_task(sending, time):
|
||||
await message.reply(f"Успешно удалено время {time}")
|
||||
await current_tasks(client, message)
|
||||
|
||||
|
||||
@app1.on_message(filters.chat(config.admins) & filters.command('switch'))
|
||||
async def switch(client, message: Message):
|
||||
""" вкл/выкл пересылки """
|
||||
if Sending.SEND:
|
||||
Sending.SEND = False
|
||||
else:
|
||||
Sending.SEND = True
|
||||
await message.reply(f"моментальная пересылка {'вкл' if Sending.SEND else 'выкл'}")
|
||||
|
||||
|
||||
@app1.on_message(filters.chat(config.admins) & filters.command('set_posts'))
|
||||
async def set_posts(client, message: Message):
|
||||
""" установить нужное количество постов для пересылки """
|
||||
global g
|
||||
try:
|
||||
set_number_posts(int(message.command[1]))
|
||||
await message.reply(f"Количество постов {get_number_posts()} установлено")
|
||||
except:
|
||||
await message.reply("Введите /set_posts число")
|
||||
|
||||
|
14
bot/loader.py
Normal file
14
bot/loader.py
Normal file
@ -0,0 +1,14 @@
|
||||
from pyrogram import Client
|
||||
|
||||
from bot import config
|
||||
from bot.scheduler import AsyncScheduler
|
||||
|
||||
api_id_1 = config.API_ID_1
|
||||
api_hash_1 = config.API_HASH_1
|
||||
|
||||
app1 = Client("cyprus_off", api_id=api_id_1, api_hash=api_hash_1, workdir='bot')
|
||||
|
||||
print(f'client is wait ...')
|
||||
print(app1)
|
||||
|
||||
scheduler = AsyncScheduler()
|
11
bot/loader.py.save
Normal file
11
bot/loader.py.save
Normal file
@ -0,0 +1,11 @@
|
||||
from pyrogram import Client
|
||||
|
||||
from bot import config
|
||||
from bot.scheduler import AsyncScheduler
|
||||
|
||||
api_id_1 = config.API_ID_1
|
||||
api_hash_1 = config.API_HASH_1
|
||||
|
||||
app1 = Client("cyprus_off", api_id=api_id_1, api_hash=api_hash_1, workdir='bot')
|
||||
|
||||
scheduler = AsyncScheduler()
|
37
bot/scheduler.py
Normal file
37
bot/scheduler.py
Normal file
@ -0,0 +1,37 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Callable, Awaitable, Coroutine
|
||||
|
||||
|
||||
class AsyncScheduler:
|
||||
"""
|
||||
Запускает, останавливает ассинхронные задачаи в заданное время
|
||||
"""
|
||||
def __init__(self):
|
||||
self.tasks = []
|
||||
|
||||
async def task_runner(self, task: Callable, run_time: str):
|
||||
"""
|
||||
Запускает задачу, которая выполняет каждый день в заданный run_time
|
||||
:param task: await Функция
|
||||
:param run_time: строка "HH:MM"
|
||||
"""
|
||||
while True:
|
||||
current_time = time.strftime("%H:%M", time.localtime())
|
||||
if current_time == run_time:
|
||||
await task()
|
||||
await asyncio.sleep(60)
|
||||
|
||||
async def add_task(self, task, run_time):
|
||||
"""Добавление новой задачи в определенное время"""
|
||||
task_instance = asyncio.create_task(self.task_runner(task, run_time))
|
||||
self.tasks.append((task, run_time, task_instance))
|
||||
|
||||
async def remove_task(self, task, run_time) -> bool:
|
||||
"""Удаление сущесвтующей задачи по функции и времени"""
|
||||
for t, r_t, task_instance in self.tasks:
|
||||
if t == task and r_t == run_time:
|
||||
task_instance.cancel()
|
||||
self.tasks.remove((t, r_t, task_instance))
|
||||
return True
|
||||
return False
|
108
bot/sending.py
Normal file
108
bot/sending.py
Normal file
@ -0,0 +1,108 @@
|
||||
import asyncio
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from pyrogram import Client
|
||||
from pyrogram.types import Message, InputMedia, InputMediaPhoto
|
||||
|
||||
from bot import config
|
||||
from bot.loader import app1
|
||||
|
||||
from random import randint
|
||||
|
||||
|
||||
NUMBER_POSTS = 20
|
||||
|
||||
|
||||
def get_number_posts():
|
||||
global NUMBER_POSTS
|
||||
return NUMBER_POSTS
|
||||
|
||||
|
||||
def set_number_posts(x: int):
|
||||
global NUMBER_POSTS
|
||||
NUMBER_POSTS = x
|
||||
|
||||
|
||||
async def sending():
|
||||
"""
|
||||
Функция для пересылки по расписанию.
|
||||
"""
|
||||
await forwards_to_chats(config.rent_channel_id, config.sell_channel_id, config.groups_for_rent, get_number_posts())
|
||||
# await forwards_to_chats(config.sell_channel_id, config.groups_for_sell, get_number_posts())
|
||||
|
||||
|
||||
# contains date: client: media groups
|
||||
class DailyAlbums:
|
||||
todays_albums = set()
|
||||
|
||||
@classmethod
|
||||
def add_album(cls, client: Client, media_group_id: int):
|
||||
album_key = (client.api_id, datetime.now().strftime('%d.%m.%Y'), media_group_id)
|
||||
cls.todays_albums.add(album_key)
|
||||
|
||||
@classmethod
|
||||
def contains_album(cls, client: Client, media_group_id: int) -> bool:
|
||||
album_key = (client.api_id, datetime.now().strftime('%d.%m.%Y'), media_group_id)
|
||||
return album_key in cls.todays_albums
|
||||
|
||||
|
||||
async def fetch_posts(chat_id: int, daily_albums: DailyAlbums, limit: int):
|
||||
"""
|
||||
Получает посты из чата, проверяя, что они ещё не находятся в daily_albums.
|
||||
"""
|
||||
posts = []
|
||||
async for post in app1.get_chat_history(chat_id=chat_id, limit=200):
|
||||
if post.media_group_id and post.caption and not daily_albums.contains_album(app1, post.media_group_id):
|
||||
posts.append(post)
|
||||
daily_albums.add_album(app1, post.media_group_id)
|
||||
if len(posts) >= limit:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
return posts
|
||||
|
||||
|
||||
async def forwards_to_chats(rent_channel_id: int, sell_channel_id: int, groups: list[str], limit: int):
|
||||
"""
|
||||
Пересылает посты из rent_channel_id и sell_channel_id в группы.
|
||||
"""
|
||||
if not groups:
|
||||
return
|
||||
|
||||
daily_albums = DailyAlbums()
|
||||
messages = await fetch_posts(rent_channel_id, daily_albums, limit)
|
||||
messages2 = await fetch_posts(sell_channel_id, daily_albums, limit)
|
||||
|
||||
all_messages = messages + messages2
|
||||
for i in range(min(limit, len(all_messages))):
|
||||
await forward_post(app1, all_messages[i], groups)
|
||||
await asyncio.sleep(config.spam_interval)
|
||||
|
||||
|
||||
def is_valid_time_format(time_str):
|
||||
"""Проверяют строку на формат HH:MM"""
|
||||
time_pattern = re.compile(r'^[0-2][0-9]:[0-5][0-9]$')
|
||||
return bool(time_pattern.match(time_str))
|
||||
|
||||
|
||||
def check_stop_sign(message: Message):
|
||||
return "⛔️" in str(message.caption) or "⛔️" in str(message.text)
|
||||
|
||||
|
||||
async def forward_post(client: Client, message: Message, groups: list[int, str]):
|
||||
# === СТОП ЗНАК ===
|
||||
if check_stop_sign(message) or not (message.caption or message.text):
|
||||
return
|
||||
# ===============================
|
||||
for spam_group in groups:
|
||||
group_id, reply_id = (spam_group, None) if '_' not in str(spam_group) else list(
|
||||
map(int, str(spam_group).split('_')))
|
||||
try:
|
||||
if message.media_group_id:
|
||||
await client.copy_media_group(chat_id=group_id, from_chat_id=message.chat.id, message_id=message.id,
|
||||
reply_to_message_id=reply_id)
|
||||
else:
|
||||
await message.copy(group_id, reply_to_message_id=reply_id)
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}\n\nГруппа: {group_id}\n\n-----------------------\n\n")
|
||||
await asyncio.sleep(2)
|
11
bot/test.py
Normal file
11
bot/test.py
Normal file
@ -0,0 +1,11 @@
|
||||
from pyrogram import Client
|
||||
|
||||
api_id = 29370778
|
||||
api_hash = '9beeb54652c5ce4f323d67f57dc233be'
|
||||
|
||||
with Client("my_account", api_id, api_hash) as app:
|
||||
# Идентификатор чата, из которого вы хотите получить сообщения
|
||||
chat_id = "your_chat_id"
|
||||
# Количество сообщений, которые вы хотите получить (максимальное значение - 100)
|
||||
me = app.get_me()
|
||||
print(me)
|
22
main.py
Normal file
22
main.py
Normal file
@ -0,0 +1,22 @@
|
||||
import asyncio
|
||||
|
||||
from pyrogram import compose, idle
|
||||
|
||||
from bot import config
|
||||
from bot.loader import app1
|
||||
|
||||
from bot import handlers
|
||||
from bot import album_handler
|
||||
|
||||
def main():
|
||||
print("Bot started.")
|
||||
# compose([app1])
|
||||
|
||||
print('test')
|
||||
|
||||
app1.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# main()
|
||||
main()
|
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@ -0,0 +1,11 @@
|
||||
croniter==2.0.1
|
||||
pyaes==1.6.1
|
||||
https://github.com/KurimuzonAkuma/pyrogram/archive/dev.zip
|
||||
PySocks==1.7.1
|
||||
python-dateutil==2.8.2
|
||||
python-dotenv==1.0.1
|
||||
pytz==2024.1
|
||||
six==1.16.0
|
||||
TgCrypto==1.2.5
|
||||
tzdata==2023.4
|
||||
tzlocal==5.2
|
Loading…
Reference in New Issue
Block a user