__version__ = (1, 1)
# This file is a part of Hikka Userbot
# Code is NOT licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
# 🌐 https://github.com/hikariatama/Hikka
# You CAN edit this file without direct permission from the author.
# You can redistribute this file with any modifications.
# meta developer: @yg_modules
# scope: hikka_only
# scope: hikka_min 1.6.3
# requires: lottie rlottie-python
# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█ █▀▄▀█ █▀█ █▀▄ █▀
# ░█░ █▄█ █░▀░█ █░▀░█ ░█░ █░▀░█ █▄█ █▄▀ ▄█
import os
import io
import json
import gzip
import zipfile
from telethon import types
from telethon.tl.types import Message
from telethon.tl.types import MessageEntityCustomEmoji
from telethon.tl.functions.messages import GetCustomEmojiDocumentsRequest
from lottie.importers.svg import import_svg
from lottie.exporters.core import export_tgs
from lottie.parsers.baseporter import Baseporter
from rlottie_python import LottieAnimation
from .. import loader, utils
@loader.tds
class yg_tgs(loader.Module):
"""Модуль для работы с .tgs (Telegram Animated Sticker)"""
strings = {
"name": "yg_tgs",
"converting": "⌛ Converting...",
"no_reply": "⚠️ Reply to an file, animated sticker or custom emoji!",
"not_svg": "🚫 Attached file is not an SVG!",
"not_json": "🚫 Attached file is not a JSON!",
"not_tgs_or_emoji": "⚠️ Reply to an animated sticker or custom emoji!",
"conversion_error": "🚫 Error during conversion: {}",
"emoji_not_found": "🚫 Custom emoji not found!"
}
strings_ru = {
"name": "yg_tgs",
"converting": "⌛ Конвертирую...",
"no_reply": "⚠️ Ответь на файл, анимированный стикер или кастомный эмодзи!",
"not_svg": "🚫 Прикрепленный файл не является SVG!",
"not_json": "🚫 Прикрепленный файл не является JSON!",
"not_tgs_or_emoji": "⚠️ Ответь на анимированный стикер или кастомный эмодзи!",
"conversion_error": "🚫 Ошибка при конвертации: {}",
"emoji_not_found": "🚫 Кастомный эмодзи не найден!"
}
@loader.command(ru_doc=" - конвертировать svg в tgs")
async def svg2tgscmd(self, msg: Message):
""" - convert svg to tgs"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if not reply.file or not reply.file.name.endswith(".svg"):
await utils.answer(msg, self.strings["not_svg"])
return
try:
tgs_buffer = io.BytesIO()
await reply.download_media(tgs_buffer)
tgs_buffer.seek(0)
svg_buffer = self.svg2tgs(tgs_buffer.getvalue())
await utils.answer(
msg,
svg_buffer,
attributes=[types.DocumentAttributeFilename("sticker.tgs")],
reply_to=reply
)
except Exception as e:
await utils.answer(msg, self.strings["conversion_error"].format(str(e)))
@loader.command(ru_doc=" - конвертировать tgs в lottie json")
async def tgs2jsoncmd(self, msg: Message):
""" - convert tgs to lottie json"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if reply.file and reply.file.name.endswith(".tgs"):
tgs_buffer = io.BytesIO()
await reply.download_media(tgs_buffer)
tgs_buffer.seek(0)
json_buffer = self.tgs2json(tgs_buffer.getvalue())
await utils.answer(
msg,
json_buffer,
attributes=[types.DocumentAttributeFilename("sticker.json")],
reply_to=reply
)
elif reply.entities and isinstance(reply.entities[0], MessageEntityCustomEmoji):
emoji_id = reply.entities[0].document_id
data = await msg.client(GetCustomEmojiDocumentsRequest(document_id=[emoji_id]))
if data:
tgs_buffer = io.BytesIO()
await msg.client.download_media(data[0], tgs_buffer)
tgs_buffer.seek(0)
json_buffer = self.tgs2json(tgs_buffer.getvalue())
await utils.answer(
msg,
json_buffer,
attributes=[types.DocumentAttributeFilename("sticker.json")],
reply_to=reply
)
else:
await utils.answer(msg, self.strings["not_tgs_or_emoji"])
@loader.command(ru_doc=" - разбить tgs на кадры (ZIP с PNG)")
async def tgs2pngscmd(self, msg: Message):
""" - split tgs into frames (ZIP with PNGs)"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if reply.file and reply.file.name.endswith(".tgs"):
tgs_buffer = io.BytesIO()
await reply.download_media(tgs_buffer)
tgs_buffer.seek(0)
pngs_buffer = self.tgs2pngs(tgs_buffer.getvalue())
pngs_buffer.name = "frames.zip"
await utils.answer(
msg,
pngs_buffer,
reply_to=reply
)
elif reply.entities and isinstance(reply.entities[0], MessageEntityCustomEmoji):
emoji_id = reply.entities[0].document_id
data = await msg.client(GetCustomEmojiDocumentsRequest(document_id=[emoji_id]))
if data:
tgs_buffer = io.BytesIO()
await msg.client.download_media(data[0], tgs_buffer)
tgs_buffer.seek(0)
pngs_buffer = self.tgs2pngs(tgs_buffer.getvalue())
pngs_buffer.name = "frames.zip"
await utils.answer(
msg,
pngs_buffer,
reply_to=reply
)
else:
await utils.answer(msg, self.strings["not_tgs_or_emoji"])
@loader.command(ru_doc=" - конвертировать lottie json в tgs")
async def json2tgscmd(self, msg: Message):
""" - convert lottie json to tgs"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if not reply.file or not reply.file.name.endswith(".json"):
await utils.answer(msg, self.strings["not_json"])
return
try:
tgs_buffer = io.BytesIO()
await reply.download_media(tgs_buffer)
tgs_buffer.seek(0)
svg_buffer = self.json2tgs(tgs_buffer.getvalue())
await utils.answer(
msg,
svg_buffer,
attributes=[types.DocumentAttributeFilename("sticker.tgs")],
reply_to=reply
)
except Exception as e:
await utils.answer(msg, self.strings["conversion_error"].format(str(e)))
@loader.command(ru_doc=" - конвертировать tgs в gif")
async def tgs2gifcmd(self, msg: Message):
""" - convert tgs to gif"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if reply.file and reply.file.name.endswith(".tgs"):
tgs_buffer = io.BytesIO()
await reply.download_media(tgs_buffer)
tgs_buffer.seek(0)
gif_buffer = self.tgs2gif(tgs_buffer.getvalue())
gif_buffer.name = "sticker.gif"
await utils.answer(
msg,
gif_buffer,
force_document=False,
reply_to=reply
)
elif reply.entities and isinstance(reply.entities[0], MessageEntityCustomEmoji):
emoji_id = reply.entities[0].document_id
data = await msg.client(GetCustomEmojiDocumentsRequest(document_id=[emoji_id]))
if data:
tgs_buffer = io.BytesIO()
await msg.client.download_media(data[0], tgs_buffer)
tgs_buffer.seek(0)
gif_buffer = self.tgs2gif(tgs_buffer.getvalue())
gif_buffer.name = "sticker.gif"
await utils.answer(
msg,
gif_buffer,
force_document=False,
reply_to=reply.id
)
else:
await utils.answer(msg, self.strings["not_tgs_or_emoji"])
@loader.command(ru_doc=" - конвертировать кастом эмодзи в tgs")
async def emoji2tgscmd(self, msg: Message):
""" - convert custom emoji to tgs"""
if not msg.reply_to_msg_id:
await utils.answer(msg, self.strings["no_reply"])
return
await utils.answer(msg, self.strings["converting"])
reply = await msg.get_reply_message()
if reply.entities and isinstance(reply.entities[0], MessageEntityCustomEmoji):
emoji_id = reply.entities[0].document_id
data = await msg.client(GetCustomEmojiDocumentsRequest(document_id=[emoji_id]))
if data:
tgs_buffer = io.BytesIO()
await msg.client.download_media(data[0], tgs_buffer)
tgs_buffer.seek(0)
await utils.answer(
msg,
tgs_buffer.getvalue(),
attributes=[types.DocumentAttributeFilename("sticker.tgs")],
reply_to=reply
)
else:
await utils.answer(msg, self.strings["emoji_not_found"])
else:
await utils.answer(msg, self.strings["not_tgs_or_emoji"])
def tgs2json(self, inbytes):
data = gzip.decompress(inbytes).decode('utf-8')
return data.encode('utf-8')
def json2tgs(self, inbytes):
data = json.loads(inbytes.decode("utf-8"))
compressed = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
buffer = io.BytesIO()
with gzip.GzipFile(fileobj=buffer, mode="wb", compresslevel=9) as out:
out.write(compressed.encode("utf-8"))
return buffer.getvalue()
def svg2tgs(self, inbytes):
infile = io.BytesIO(inbytes)
importer = Baseporter(extensions=["svg", "svgz"],
callback=import_svg,
name="SVG")
exporter = Baseporter(extensions=["tgs"],
callback=export_tgs,
name="Telegram Animated Sticker")
an = importer.process(infile)
an.frame_rate = 60
an.scale(512, 512)
buffer = io.BytesIO()
exporter.process(an, buffer)
buffer.seek(0)
return buffer
def tgs2gif(self, inbytes):
temp = "/tmp/output.gif"
try:
tgs_stream = io.BytesIO(inbytes)
anim = LottieAnimation.from_tgs(tgs_stream)
anim.save_animation(temp, format="gif")
gif_buffer = io.BytesIO()
with open(temp, "rb") as gif_file:
gif_buffer.write(gif_file.read())
gif_buffer.seek(0)
return gif_buffer
finally:
if os.path.exists(temp):
os.remove(temp)
def tgs2pngs(self, inbytes):
temp = "/tmp/frames.zip"
try:
tgs_stream = io.BytesIO(inbytes)
anim = LottieAnimation.from_tgs(tgs_stream)
total_frames = anim.lottie_animation_get_totalframe()
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
for i in range(total_frames):
frame = anim.render_pillow_frame(i)
frame_buffer = io.BytesIO()
frame.save(frame_buffer, format="PNG")
frame_buffer.seek(0)
zipf.writestr(f"frame_{i}.png", frame_buffer.getvalue())
zip_buffer.seek(0)
return zip_buffer
finally:
if os.path.exists(temp):
os.remove(temp)