ring23 commited on
Commit
91a3592
1 Parent(s): 79e7b01

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +359 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import requests
4
+ import sys
5
+ import aiogram
6
+ import random
7
+ import asyncio
8
+ from typing import List
9
+ from requests.exceptions import ConnectionError
10
+
11
+ from aiogram import Bot, Dispatcher, F, types
12
+ from aiogram.fsm.storage.memory import MemoryStorage
13
+ from aiogram.types import ContentType, Message
14
+ from aiogram_media_group import MediaGroupFilter, media_group_handler
15
+ from dotenv import load_dotenv
16
+ from vk_api import VkApi
17
+ from vk_api.upload import VkUpload
18
+ from vk_api.utils import get_random_id
19
+ from PIL import Image, UnidentifiedImageError
20
+ from rlottie_python import LottieAnimation
21
+ from moviepy.editor import VideoFileClip
22
+
23
+ load_dotenv()
24
+ logging.basicConfig(level=logging.INFO)
25
+
26
+ TELEGRAM_API_TOKEN = os.getenv('TELEGRAM_API_TOKEN')
27
+ TELEGRAM_CHANNEL_USERNAME = os.getenv('TELEGRAM_CHANNEL_USERNAME')
28
+ VK_API_TOKEN = os.getenv('VK_API_TOKEN')
29
+ VK_GROUP_ID = os.getenv('VK_GROUP_ID')
30
+
31
+ vk_session = VkApi(token=VK_API_TOKEN)
32
+ vk = vk_session.get_api()
33
+ uploader = VkUpload(vk)
34
+ PROXY_URL = os.getenv('PROXY_URL')
35
+ proxy = PROXY_URL
36
+ proxies = {"http": proxy, "https": proxy}
37
+
38
+ bot = Bot(token=TELEGRAM_API_TOKEN, proxy=proxies)
39
+ storage = MemoryStorage()
40
+
41
+ dp = Dispatcher(storage=storage, proxy=proxies)
42
+
43
+ def add_entry(message_id, post_id):
44
+ with open('data.txt', 'a') as f:
45
+ f.write(f'{message_id}:{post_id}\n')
46
+
47
+
48
+ def get_entry(message_id) -> int:
49
+ with open('data.txt', 'r') as f:
50
+ for line in f.readlines():
51
+ if int(line.split(':')[0]) == message_id:
52
+ return int(line.split(':')[1])
53
+ raise KeyError(f'{message_id} is not in the file!')
54
+
55
+
56
+ def create_vk_post(text: str, message_id, photo_list=None, video_list=None, doc_list=None, audio_list=None, gif_list=None):
57
+ photos, videos, docs, audios, gifs = [], [], [], [], []
58
+
59
+ try:
60
+ if photo_list:
61
+ photos = uploader.photo_wall(photos=photo_list, group_id=VK_GROUP_ID)
62
+ if video_list:
63
+ videos = [uploader.video(video_file=path, group_id=int(VK_GROUP_ID)) for path in video_list]
64
+ if doc_list:
65
+ for doc in doc_list:
66
+ if os.path.exists(doc):
67
+ with open(doc, 'rb') as f:
68
+ doc_info = uploader.document(doc=f, title=os.path.basename(doc))
69
+ docs.append(doc_info)
70
+ if audio_list:
71
+ for audio in audio_list:
72
+ if os.path.exists(audio):
73
+ with open(audio, 'rb') as f:
74
+ audio_info = uploader.audio(audio=f, artist="Unknown Artist", title=os.path.basename(audio))
75
+ audios.append(audio_info)
76
+ if gif_list:
77
+ for gif in gif_list:
78
+ if os.path.exists(gif):
79
+ with open(gif, 'rb') as f:
80
+ gif_info = uploader.document(doc=f, title=os.path.basename(gif))
81
+ gifs.append(gif_info)
82
+
83
+ attachments = [f'photo{photo.get("owner_id")}_{photo["id"]}' for photo in photos if "owner_id" in photo and "id" in photo]
84
+ attachments += [f'video{video["owner_id"]}_{video["video_id"]}' for video in videos if "owner_id" in video and "video_id" in video]
85
+ attachments += [f'doc{doc["doc"]["owner_id"]}_{doc["doc"]["id"]}' for doc in docs if "doc" in doc and "owner_id" in doc["doc"] and "id" in doc["doc"]]
86
+ attachments += [f'audio{audio["owner_id"]}_{audio["id"]}' for audio in audios if "owner_id" in audio and "id" in audio]
87
+ attachments += [f'doc{gif["doc"]["owner_id"]}_{gif["doc"]["id"]}' for gif in gifs if "doc" in gif and "owner_id" in gif["doc"] and "id" in gif["doc"]]
88
+
89
+ source_link = f'https://t.me/{TELEGRAM_CHANNEL_USERNAME}/{message_id}'
90
+ post_text = f'{text}\n\nИсточник: {source_link}'
91
+
92
+ response = vk.wall.post(
93
+ owner_id=f'-{VK_GROUP_ID}',
94
+ message=post_text,
95
+ attachments=','.join(attachments),
96
+ from_group=1,
97
+ copyright=source_link,
98
+ random_id=get_random_id()
99
+ )
100
+ post_id = response['post_id']
101
+ add_entry(message_id, post_id)
102
+
103
+ except Exception as e:
104
+ logging.error(f'Error while creating VK post: {e}')
105
+
106
+
107
+ def edit_vk_post(post_id, new_text, message_id):
108
+ old_post = vk.wall.getById(posts=f'-{VK_GROUP_ID}_{post_id}')[0]
109
+ attachments = [f'{attachment["type"]}{attachment[attachment["type"]]["owner_id"]}_{attachment[attachment["type"]]["id"]}' for attachment in old_post.get('attachments', [])]
110
+
111
+ source_link = f'https://t.me/{TELEGRAM_CHANNEL_USERNAME}/{message_id}'
112
+ edited_text = f'{new_text}\n\nИсточник: {source_link}'
113
+
114
+ vk.wall.edit(
115
+ message=edited_text,
116
+ post_id=post_id,
117
+ from_group=1,
118
+ owner_id=f'-{VK_GROUP_ID}',
119
+ copyright=source_link,
120
+ attachments=attachments
121
+ )
122
+
123
+
124
+ async def download_file_with_retries(file_id: str, destination: str, retries: int = 3, delay: int = 5):
125
+ os.makedirs(os.path.dirname(destination), exist_ok=True)
126
+ for attempt in range(retries):
127
+ try:
128
+ file = await bot.get_file(file_id)
129
+ await bot.download_file(file.file_path, destination)
130
+ return True
131
+ except Exception as e:
132
+ logging.error(f'Error while downloading file (attempt {attempt + 1}/{retries}): {e}')
133
+ if attempt < retries - 1:
134
+ await asyncio.sleep(delay)
135
+ return False
136
+
137
+
138
+ @media_group_handler
139
+ async def album_handler(messages: List[Message]):
140
+ random_number = random.randint(1000000, 9999999)
141
+ c = 0
142
+
143
+ photo_list = []
144
+ video_list = []
145
+ doc_list = []
146
+ audio_list = []
147
+ gif_list = []
148
+ text = None
149
+
150
+ for message in messages:
151
+ if message.caption and not text:
152
+ text = message.caption
153
+ if message.photo:
154
+ path = f'./files/photo_{random_number}_{c}.jpg'
155
+ if await download_file_with_retries(message.photo[-1].file_id, path):
156
+ photo_list.append(path)
157
+ elif message.video:
158
+ path = f'./files/video_{random_number}_{c}.mp4'
159
+ if await download_file_with_retries(message.video.file_id, path):
160
+ video_list.append(path)
161
+ elif message.document:
162
+ path = f'./files/doc_{random_number}_{c}_{message.document.file_name}'
163
+ if await download_file_with_retries(message.document.file_id, path):
164
+ doc_list.append(path)
165
+ elif message.audio:
166
+ path = f'./files/audio_{random_number}_{c}_{message.audio.file_name}'
167
+ if await download_file_with_retries(message.audio.file_id, path):
168
+ audio_list.append(path)
169
+ elif message.animation:
170
+ path = f'./files/gif_{random_number}_{c}.mp4'
171
+ if await download_file_with_retries(message.animation.file_id, path):
172
+ gif_list.append(path)
173
+ c += 1
174
+
175
+ message_id = messages[0].message_id
176
+ post_text = text if text else ''
177
+ await asyncio.to_thread(create_vk_post, post_text, message_id, photo_list, video_list, doc_list, audio_list, gif_list)
178
+
179
+ for path in photo_list + video_list + doc_list + audio_list + gif_list:
180
+ os.remove(path)
181
+
182
+
183
+ async def photo_video_handler(message: Message):
184
+ text = message.caption or ''
185
+ random_number = random.randint(1000000, 9999999)
186
+ if message.photo:
187
+ path = f'./files/photo_{random_number}.jpg'
188
+ if await download_file_with_retries(message.photo[-1].file_id, path):
189
+ await asyncio.to_thread(create_vk_post, text, message.message_id, [path])
190
+ os.remove(path)
191
+ elif message.video:
192
+ path = f'./files/video_{random_number}.mp4'
193
+ if await download_file_with_retries(message.video.file_id, path):
194
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, [path])
195
+ os.remove(path)
196
+
197
+
198
+ async def document_handler(message: Message):
199
+ text = message.caption or ''
200
+ random_number = random.randint(1000000, 9999999)
201
+ path = f'./files/doc_{random_number}_{message.document.file_name}'
202
+ if await download_file_with_retries(message.document.file_id, path):
203
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, None, [path])
204
+ os.remove(path)
205
+
206
+
207
+ async def audio_handler(message: Message):
208
+ text = message.caption or ''
209
+ random_number = random.randint(1000000, 9999999)
210
+ audio_paths = []
211
+ for i, audio in enumerate(message.audio):
212
+ path = f'./files/audio_{random_number}_{i}_{audio.file_name}'
213
+ if await download_file_with_retries(audio.file_id, path):
214
+ if os.path.exists(path):
215
+ audio_paths.append(path)
216
+ if audio_paths:
217
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, None, None, audio_paths)
218
+ for path in audio_paths:
219
+ os.remove(path)
220
+
221
+
222
+ async def video_handler(message: Message):
223
+ text = message.caption or ''
224
+ random_number = random.randint(1000000, 9999999)
225
+ path = f'./files/video_{random_number}_{message.video.file_name}'
226
+ if await download_file_with_retries(message.video.file_id, path):
227
+ if os.path.exists(path):
228
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, [path])
229
+ os.remove(path)
230
+
231
+
232
+ async def text_handler(message: Message):
233
+ await asyncio.to_thread(create_vk_post, message.text, message.message_id)
234
+
235
+
236
+ async def edited_handler(message: Message):
237
+ if message.message_id is None:
238
+ return
239
+
240
+ try:
241
+ post_id = get_entry(message.message_id)
242
+ except KeyError:
243
+ return
244
+
245
+ text = message.text or message.caption or ''
246
+ await asyncio.to_thread(edit_vk_post, post_id, text, message.message_id)
247
+
248
+
249
+ async def sticker_handler(message: Message):
250
+ random_number = random.randint(1000000, 9999999)
251
+ path = f'./files/sticker_{random_number}.{"tgs" if message.sticker.is_animated else "webp"}'
252
+ if await download_file_with_retries(message.sticker.file_id, path):
253
+ if message.sticker.is_animated or message.sticker.is_video:
254
+ try:
255
+ gif_path = f'./files/sticker_{random_number}.gif'
256
+
257
+ if message.sticker.is_animated:
258
+ animation = LottieAnimation.from_tgs(path)
259
+ frames = [animation.render_pillow_frame(frame_num=i) for i in range(animation.lottie_animation_get_totalframe())]
260
+ frames[0].save(gif_path, save_all=True, append_images=frames[1:], loop=0, duration=1000 // animation.lottie_animation_get_framerate())
261
+ else:
262
+ clip = VideoFileClip(path)
263
+ clip.write_gif(gif_path)
264
+ clip.close()
265
+
266
+ os.remove(path)
267
+ await asyncio.to_thread(create_vk_post, '', message.message_id, None, None, None, None, [gif_path])
268
+ os.remove(gif_path)
269
+ except Exception as e:
270
+ logging.error(f'Error while converting animated sticker: {e}')
271
+ try:
272
+ os.remove(path)
273
+ except PermissionError:
274
+ pass
275
+ else:
276
+ try:
277
+ png_path = f'./files/sticker_{random_number}.png'
278
+ try:
279
+ with Image.open(path) as im:
280
+ width, height = im.size
281
+ scale = 1.0
282
+ width_new, height_new = int(width * scale), int(height * scale)
283
+ im_resized = im.resize((width_new, height_new), Image.LANCZOS)
284
+ im_resized.save(png_path, 'PNG')
285
+ except UnidentifiedImageError as e:
286
+ raise
287
+ os.remove(path)
288
+ await asyncio.to_thread(create_vk_post, '', message.message_id, [png_path], None, None, None, None)
289
+ os.remove(png_path)
290
+ except Exception as e:
291
+ logging.error(f'Error while converting static sticker: {e}')
292
+ if os.path.exists(path):
293
+ os.remove(path)
294
+
295
+
296
+ async def voice_handler(message: Message):
297
+ text = message.caption or ''
298
+ random_number = random.randint(1000000, 9999999)
299
+ path = f'./files/voice_{random_number}.ogg'
300
+
301
+ download_task = asyncio.create_task(download_file_with_retries(message.voice.file_id, path, retries=5, delay=10))
302
+
303
+ try:
304
+ await download_task
305
+ if not os.path.exists(path):
306
+ return
307
+ except Exception as e:
308
+ return
309
+
310
+ file_size = os.path.getsize(path)
311
+ max_size = 10 * 1024 * 1024
312
+ if file_size > max_size:
313
+ os.remove(path)
314
+ return
315
+
316
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, None, None, [path])
317
+ os.remove(path)
318
+
319
+
320
+ async def animation_handler(message: Message):
321
+ text = message.caption or ''
322
+ random_number = random.randint(1000000, 9999999)
323
+ path = f'./files/animation_{random_number}.mp4'
324
+ retries = 3
325
+ delay = 5
326
+ for attempt in range(retries):
327
+ try:
328
+ if await download_file_with_retries(message.animation.file_id, path):
329
+ gif_path = f'./files/animation_{random_number}.gif'
330
+ clip = VideoFileClip(path)
331
+ clip.write_gif(gif_path)
332
+ clip.close()
333
+ os.remove(path)
334
+ await asyncio.to_thread(create_vk_post, text, message.message_id, None, None, None, None, [gif_path])
335
+ os.remove(gif_path)
336
+ break
337
+ except Exception as e:
338
+ if attempt < retries - 1:
339
+ await asyncio.sleep(delay)
340
+ else:
341
+ logging.error(f'Failed to download animation {message.animation.file_id} after {retries} attempts')
342
+
343
+ async def main():
344
+ dp.channel_post.register(album_handler, MediaGroupFilter())
345
+ dp.channel_post.register(photo_video_handler, F.content_type.in_([ContentType.PHOTO, ContentType.VIDEO]))
346
+ dp.channel_post.register(document_handler, F.content_type == ContentType.DOCUMENT)
347
+ dp.channel_post.register(audio_handler, F.content_type == ContentType.AUDIO)
348
+ dp.channel_post.register(video_handler, F.content_type == ContentType.VIDEO)
349
+ dp.channel_post.register(text_handler, F.content_type == ContentType.TEXT)
350
+ dp.channel_post.register(sticker_handler, F.content_type == ContentType.STICKER)
351
+ dp.channel_post.register(animation_handler, F.content_type == ContentType.ANIMATION)
352
+ dp.channel_post.register(voice_handler, F.content_type == ContentType.VOICE)
353
+ dp.edited_channel_post.register(edited_handler)
354
+
355
+ await dp.start_polling(bot)
356
+
357
+ if __name__ == '__main__':
358
+ logging.info('Starting bot')
359
+ asyncio.run(main())
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ aiogram==3.6.0
2
+ aiogram-media-group==0.5.1
3
+ aiohttp==3.9.0
4
+ python-dotenv==1.0.0
5
+ vk-api==11.9.9
6
+ rlottie-python==1.3.4
7
+ moviepy
8
+ requests==2.31.0