This commit is contained in:
Denis 2024-09-14 23:27:11 +03:00
parent a3275e79c1
commit d581ecadc2
23 changed files with 401 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

2
.env Normal file
View File

@ -0,0 +1,2 @@
API_ID_1 = 24012189
API_HASH_1 = 5a04d4beafc73f2b3a9d5a64ccb53ae5

13
Dockerfile Normal file
View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

72
bot/album_handler.py Normal file
View 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
View 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

Binary file not shown.

Binary file not shown.

74
bot/handlers.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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