mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-17 14:54:18 +02:00
Added and updated repositories 2026-01-11 01:18:34
This commit is contained in:
@@ -168,10 +168,10 @@ class DaysToMyBirthday(loader.Module):
|
||||
await asyncio.sleep(60)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Выставить таймер дней в ник (нестабильно)",
|
||||
en_doc="Set the timer of days in the nickname (unstable)",
|
||||
ru_doc="Включить таймер дней в ник (нестабильно)",
|
||||
en_doc="Enable timer of days in nickname (unstable)",
|
||||
)
|
||||
async def btname(self, message):
|
||||
async def btnameon(self, message):
|
||||
try:
|
||||
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
|
||||
name = user.users[0].last_name or ""
|
||||
@@ -181,25 +181,32 @@ class DaysToMyBirthday(loader.Module):
|
||||
return
|
||||
|
||||
self.db.set(__name__, "last_name", name)
|
||||
self.db.set(__name__, "change_name", True)
|
||||
await utils.answer(message, self.strings("btname_yes"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Выключить таймер дней в ник",
|
||||
en_doc="Disable timer of days in nickname",
|
||||
)
|
||||
async def btnameoff(self, message):
|
||||
change_name = self.db.get(__name__, "change_name", False)
|
||||
|
||||
if change_name:
|
||||
self.db.set(__name__, "change_name", False)
|
||||
if not change_name:
|
||||
await utils.answer(message, self.strings("btname_no"))
|
||||
try:
|
||||
await self.client(
|
||||
UpdateProfileRequest(last_name=self.db.get(__name__, "last_name"))
|
||||
)
|
||||
await utils.answer(message, self.strings("name_not_changed"))
|
||||
except UserPrivacyRestrictedError:
|
||||
await utils.answer(message, self.strings("name_privacy_error"))
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing name: {e}")
|
||||
await utils.answer(message, self.strings("error"))
|
||||
return
|
||||
|
||||
else:
|
||||
self.db.set(__name__, "change_name", True)
|
||||
await utils.answer(message, self.strings("btname_yes"))
|
||||
self.db.set(__name__, "change_name", False)
|
||||
await utils.answer(message, self.strings("btname_no"))
|
||||
try:
|
||||
await self.client(
|
||||
UpdateProfileRequest(last_name=self.db.get(__name__, "last_name"))
|
||||
)
|
||||
await utils.answer(message, self.strings("name_not_changed"))
|
||||
except UserPrivacyRestrictedError:
|
||||
await utils.answer(message, self.strings("name_privacy_error"))
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing name: {e}")
|
||||
await utils.answer(message, self.strings("error"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Вывести таймер",
|
||||
|
||||
@@ -35,6 +35,7 @@ from telethon.types import MessageMediaDocument
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CodeShareMod(loader.Module):
|
||||
"""Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)"""
|
||||
@@ -55,16 +56,16 @@ class CodeShareMod(loader.Module):
|
||||
async def upload_to_kmi(self, content: str) -> str:
|
||||
url = "https://kmi.aeza.net"
|
||||
data = aiohttp.FormData()
|
||||
data.add_field('kmi', content)
|
||||
data.add_field("kmi", content)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, data=data) as response:
|
||||
if response.status == 200:
|
||||
link = await response.text()
|
||||
return link
|
||||
else:
|
||||
logger.error(f"Error occurred! Status code: {response.status}")
|
||||
return
|
||||
async with session.post(url, data=data) as response:
|
||||
if response.status == 200:
|
||||
link = await response.text()
|
||||
return link
|
||||
else:
|
||||
logger.error(f"Error occurred! Status code: {response.status}")
|
||||
return
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Загрузка кода на сайт",
|
||||
@@ -75,20 +76,14 @@ class CodeShareMod(loader.Module):
|
||||
reply = await message.get_reply_message()
|
||||
if args:
|
||||
link = await self.upload_to_kmi(args)
|
||||
await utils.answer(message, self.strings['link_ready'].format(link))
|
||||
await utils.answer(message, self.strings["link_ready"].format(link))
|
||||
return
|
||||
if reply and isinstance(reply.media, MessageMediaDocument):
|
||||
file_name = await reply.download_media()
|
||||
async with aiofiles.open(file_name, mode='r') as f:
|
||||
async with aiofiles.open(file_name, mode="r") as f:
|
||||
content = await f.read()
|
||||
link = await self.upload_to_kmi(content)
|
||||
os.remove(file_name)
|
||||
await utils.answer(message, self.strings['link_ready'].format(link))
|
||||
await os.remove(file_name)
|
||||
await utils.answer(message, self.strings["link_ready"].format(link))
|
||||
return
|
||||
await utils.answer(message, self.strings['invalid_args'])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
|
||||
@@ -31,7 +31,6 @@ import re
|
||||
from typing import Optional, Set
|
||||
|
||||
from telethon.errors import FloodWaitError, MessageDeleteForbiddenError
|
||||
from telethon.tl.functions.messages import DeleteMessagesRequest
|
||||
from telethon.tl.types import Message, MessageMediaDocument
|
||||
|
||||
from .. import loader, utils
|
||||
@@ -82,17 +81,20 @@ class EmojiStickerBlocker(loader.Module):
|
||||
self.blocked_packs: Set[str] = set()
|
||||
self.blocked_stickers: Set[str] = set()
|
||||
self.blocked_emojis: Set[str] = set()
|
||||
self._client = None
|
||||
self._db = None
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
self._load_blocklists()
|
||||
|
||||
def _load_blocklists(self):
|
||||
"""Load blocklists from database"""
|
||||
self.blocked_packs = set(self._db.get(__name__, "blocked_packs", []))
|
||||
self.blocked_stickers = set(self._db.get(__name__, "blocked_stickers", []))
|
||||
self.blocked_emojis = set(self._db.get(__name__, "blocked_emojis", []))
|
||||
|
||||
def _save_blocklists(self):
|
||||
"""Save blocklists to database"""
|
||||
self._db.set(__name__, "blocked_packs", list(self.blocked_packs))
|
||||
self._db.set(__name__, "blocked_stickers", list(self.blocked_stickers))
|
||||
self._db.set(__name__, "blocked_emojis", list(self.blocked_emojis))
|
||||
@@ -107,9 +109,15 @@ class EmojiStickerBlocker(loader.Module):
|
||||
return message.sticker.set_name.lower()
|
||||
|
||||
if isinstance(message.media, MessageMediaDocument):
|
||||
if hasattr(message.media.document, "attributes"):
|
||||
if hasattr(message.media, "document") and hasattr(
|
||||
message.media.document, "attributes"
|
||||
):
|
||||
for attr in message.media.document.attributes:
|
||||
if hasattr(attr, "stickerset") and attr.stickerset:
|
||||
if (
|
||||
hasattr(attr, "stickerset")
|
||||
and hasattr(attr.stickerset, "title")
|
||||
and attr.stickerset.title
|
||||
):
|
||||
return attr.stickerset.title.lower()
|
||||
|
||||
return None
|
||||
@@ -126,19 +134,18 @@ class EmojiStickerBlocker(loader.Module):
|
||||
|
||||
if emojis:
|
||||
return emojis[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
async def _delete_message(self, message: Message) -> bool:
|
||||
"""Delete message with error handling"""
|
||||
try:
|
||||
await self._client(DeleteMessagesRequest([message.id]))
|
||||
await self._client.delete_messages(message.to_id, [message.id])
|
||||
return True
|
||||
except MessageDeleteForbiddenError:
|
||||
await utils.answer(message, self.strings["no_permission"])
|
||||
logger.warning("No permission to delete message")
|
||||
return False
|
||||
except FloodWaitError as e:
|
||||
logger.warning(f"Flood wait when deleting message: {e}")
|
||||
logger.warning(f"Flood wait when deleting message: {e.seconds}s")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting message: {e}")
|
||||
@@ -146,19 +153,23 @@ class EmojiStickerBlocker(loader.Module):
|
||||
|
||||
async def _should_block_message(self, message: Message) -> tuple[bool, str]:
|
||||
"""Check if message should be blocked and return reason"""
|
||||
pack_name = self._extract_pack_name(message)
|
||||
emoji_text = self._extract_emoji_text(message)
|
||||
try:
|
||||
pack_name = self._extract_pack_name(message)
|
||||
emoji_text = self._extract_emoji_text(message)
|
||||
|
||||
if pack_name and pack_name in self.blocked_packs:
|
||||
return True, f"pack: {pack_name}"
|
||||
if pack_name and pack_name in self.blocked_packs:
|
||||
return True, f"pack: {pack_name}"
|
||||
|
||||
if message.sticker:
|
||||
sticker_id = str(message.sticker.id)
|
||||
if sticker_id in self.blocked_stickers:
|
||||
return True, f"sticker: {sticker_id}"
|
||||
if message.sticker:
|
||||
sticker_id = str(message.sticker.id)
|
||||
if sticker_id in self.blocked_stickers:
|
||||
return True, f"sticker: {sticker_id}"
|
||||
|
||||
if emoji_text and emoji_text in self.blocked_emojis:
|
||||
return True, f"emoji: {emoji_text}"
|
||||
if emoji_text and emoji_text in self.blocked_emojis:
|
||||
return True, f"emoji: {emoji_text}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking message: {e}")
|
||||
|
||||
return False, ""
|
||||
|
||||
@@ -174,7 +185,9 @@ class EmojiStickerBlocker(loader.Module):
|
||||
|
||||
pack_name = args.lower().strip()
|
||||
|
||||
# Add to blocked packs
|
||||
if pack_name in self.blocked_packs:
|
||||
return await utils.answer(message, self.strings["not_found"])
|
||||
|
||||
self.blocked_packs.add(pack_name)
|
||||
self._save_blocklists()
|
||||
|
||||
@@ -194,6 +207,10 @@ class EmojiStickerBlocker(loader.Module):
|
||||
return await utils.answer(message, self.strings["no_reply"])
|
||||
|
||||
sticker_id = str(reply_msg.sticker.id)
|
||||
|
||||
if sticker_id in self.blocked_stickers:
|
||||
return await utils.answer(message, self.strings["not_found"])
|
||||
|
||||
self.blocked_stickers.add(sticker_id)
|
||||
self._save_blocklists()
|
||||
|
||||
@@ -206,6 +223,7 @@ class EmojiStickerBlocker(loader.Module):
|
||||
async def emojiblock(self, message: Message):
|
||||
"""Block emoji from reply or input"""
|
||||
args = utils.get_args_raw(message)
|
||||
emoji_text = None
|
||||
|
||||
if args:
|
||||
emoji_text = args.strip()
|
||||
@@ -223,6 +241,9 @@ class EmojiStickerBlocker(loader.Module):
|
||||
if not emoji_text:
|
||||
return await utils.answer(message, self.strings["no_reply"])
|
||||
|
||||
if emoji_text in self.blocked_emojis:
|
||||
return await utils.answer(message, self.strings["not_found"])
|
||||
|
||||
self.blocked_emojis.add(emoji_text)
|
||||
self._save_blocklists()
|
||||
|
||||
@@ -326,6 +347,8 @@ class EmojiStickerBlocker(loader.Module):
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
"""Monitor messages and block unwanted content"""
|
||||
if not self._client or not self._db:
|
||||
return
|
||||
|
||||
if message.is_group or message.is_channel:
|
||||
return
|
||||
|
||||
@@ -1,91 +1,184 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png" alt="hikka_mods" width="600">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png">
|
||||
<img src="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png" alt="H:Mods Logo" width="400">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
<h1 align="center">H:Mods - Hikka Modules</h1>
|
||||
<h1 align="center">
|
||||
<a href="https://github.com/archquise/H.Modules">
|
||||
<img src="https://readme-typing-svg.herokuapp.com/?lines=H:Mods;Hikka+Modules+Collection¢er=true&vCenter=true&width=500&height=80&color=00D9FF¢er=true&vCenter=true">
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Enhance your <a href="https://github.com/hikariatama/Hikka">Hikka</a>/<a href="https://github.com/coddrago/Heroku">Heroku</a> experience with a curated collection of community modules.
|
||||
<a href="https://github.com/hikariatama/Hikka">
|
||||
<img src="https://img.shields.io/badge/Hikka-Userbot-blue?style=for-the-badge&logo=python" alt="Hikka">
|
||||
</a>
|
||||
<a href="https://github.com/coddrago/Heroku">
|
||||
<img src="https://img.shields.io/badge/Heroku-Userbot-purple?style=for-the-badge&logo=heroku" alt="Heroku">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://t.me/hikka_mods">
|
||||
<img src="https://img.shields.io/badge/Join%20the-Telegram%20Channel-blue?style=flat-square&logo=telegram" alt="Telegram Channel">
|
||||
<img src="https://img.shields.io/badge/Telegram%20Channel-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram Channel">
|
||||
</a>
|
||||
<a href="https://github.com/archquise/H.Modules">
|
||||
<img src="https://img.shields.io/badge/GitHub-Repository-181717?style=for-the-badge&logo=github" alt="GitHub">
|
||||
</a>
|
||||
<a href="https://github.com/archquise/H.Modules/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/archquise/H.Modules?style=for-the-badge&logo=github&color=yellow" alt="Stars">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Installation Guide
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Direct Installation
|
||||
|
||||
To install a module directly from the repository, use the following command:
|
||||
|
||||
|
||||
```bash
|
||||
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/<module_name>.py
|
||||
```
|
||||
**Example:** To install example_module.py, use:
|
||||
|
||||
|
||||
```bash
|
||||
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/example_module.py
|
||||
```
|
||||
### 2. Repository Installation (Recommended)
|
||||
|
||||
For easier module management and updates, install the entire repository.
|
||||
|
||||
**Step 1: Add the Repository**
|
||||
### 📦 Repository Installation (Recommended)
|
||||
|
||||
The easiest way to install and manage all modules:
|
||||
|
||||
```bash
|
||||
.addrepo https://github.com/archquise/H.Modules/raw/main
|
||||
```
|
||||
|
||||
**Step 2: Install Modules from the Repository**
|
||||
After adding the repository, install any module:
|
||||
|
||||
```bash
|
||||
.dlm <module_name>
|
||||
```
|
||||
**Example:** After adding the repository, to install example_module, use:
|
||||
|
||||
### 🎯 Direct Installation
|
||||
|
||||
Install a specific module directly:
|
||||
|
||||
```bash
|
||||
.dlm example_module
|
||||
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/<module_name>.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
## 🛠️ Installation Guide
|
||||
|
||||
This project is licensed under a **Proprietary License**. By using this software, you agree to the terms and conditions outlined below.
|
||||
### Step 1: Add Repository
|
||||
|
||||
> **You are granted permission to use the Software for personal and non-commercial purposes, subject to the following conditions:**
|
||||
```bash
|
||||
.addrepo https://github.com/archquise/H.Modules/raw/main
|
||||
```
|
||||
|
||||
1. Modification or alteration of the Software is strictly prohibited without explicit written permission from the author.
|
||||
2. Redistribution of the Software, in original or modified form, is strictly prohibited without explicit written permission from the author.
|
||||
3. The Software is provided "as is", without any warranty, express or implied.
|
||||
4. The copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
|
||||
5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
### Step 2: Install Modules
|
||||
|
||||
**Contact:** For any inquiries or requests for permissions, please contact archquise@gmail.com.
|
||||
```bash
|
||||
# Install specific module
|
||||
.dlm TelegraphComic
|
||||
|
||||
|
||||
# Install from direct URL
|
||||
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/TelegraphComic.py
|
||||
```
|
||||
|
||||
### Step 4: Configure Modules
|
||||
|
||||
Most modules have configurable settings:
|
||||
|
||||
```bash
|
||||
# View module configuration
|
||||
.config TelegraphComic
|
||||
|
||||
# Update configuration
|
||||
.config TelegraphComic upload_service catbox
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
<details>
|
||||
<summary>Show Full License Details</summary>
|
||||
> <i>All files of this repository are under <b>Proprietary License.</b></i><br>
|
||||
> <b>Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:</b>
|
||||
<summary>❌ Module Installation Failed</summary>
|
||||
|
||||
1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
3. The Software is provided "as is", without warranty of any kind, express or implied.
|
||||
4. The copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
|
||||
5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
**Solution:**
|
||||
|
||||
1. Check repository URL is correct
|
||||
2. Ensure you have internet connection
|
||||
3. Try restarting Hikka/Heroku
|
||||
4. Use direct installation method
|
||||
|
||||
```bash
|
||||
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/<module_name>.py
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>⚠️ Module Not Working</summary>
|
||||
|
||||
**Solution:**
|
||||
2. Check configuration: `.cfg <module_name>`
|
||||
3. Check dependencies are installed
|
||||
4. Update the module: `.dlm <module_name>`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>🔧 Configuration Issues</summary>
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Reset to defaults: `.cfg <module_name> reset`
|
||||
2. Check syntax: `.cfg <module_name>`
|
||||
3. View help: `.help <module_name>`
|
||||
|
||||
For any inquiries or requests for permissions, please contact archquise@gmail.com.
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/trinib/trinib/82213791fa9ff58d3ca768ddd6de2489ec23ffca/images/footer.svg" width="100%">
|
||||
</p>
|
||||
## 📜 License
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🔒 Proprietary License**
|
||||
|
||||
This project is licensed under a proprietary license. By using this software, you agree to the following terms:
|
||||
|
||||
</div>
|
||||
|
||||
### 📋 License Terms
|
||||
|
||||
> **✅ What You CAN Do:**
|
||||
|
||||
> - Use the software for personal and non-commercial purposes
|
||||
> - Install and use modules in Hikka/Heroku
|
||||
> - Modify configuration settings
|
||||
> - Report issues and suggest improvements
|
||||
|
||||
> **❌ What You CANNOT Do:**
|
||||
|
||||
> - Modify, alter, or change the software without explicit permission
|
||||
> - Redistribute the software in original or modified form
|
||||
> - Use for commercial purposes without permission
|
||||
> - Remove copyright notices or attribution
|
||||
|
||||
### 📧 Contact
|
||||
|
||||
For inquiries or permission requests:
|
||||
|
||||
- **Email:** `archquise@gmail.com`
|
||||
- **Telegram:** [@hikka_mods](https://t.me/hikka_mods)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<picture>
|
||||
<img src="https://raw.githubusercontent.com/trinib/trinib/82213791fa9ff58d3ca768ddd6de2489ec23ffca/images/footer.svg" alt="Footer" width="100%">
|
||||
</picture>
|
||||
|
||||
<p>
|
||||
<sub>Built with ❤️ for the Hikka/Heroku community</sub>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#top">⬆️ Back to Top</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
536
archquise/H.Modules/TelegraphComic.py
Normal file
536
archquise/H.Modules/TelegraphComic.py
Normal file
@@ -0,0 +1,536 @@
|
||||
# Proprietary License Agreement
|
||||
|
||||
# Copyright (c) 2026-2029 CodWiz
|
||||
|
||||
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
|
||||
|
||||
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
|
||||
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
|
||||
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
|
||||
|
||||
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
|
||||
|
||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: TelegraphComics
|
||||
# Description: Create comics on Telegraph from ZIP/RAR archives
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# requires: aiohttp, zipfile, telegraph
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
from typing import List, Optional
|
||||
import zipfile
|
||||
|
||||
import aiohttp
|
||||
from telethon.types import MessageMediaDocument, Message
|
||||
|
||||
from telegraph import Telegraph
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TelegraphComicMod(loader.Module):
|
||||
"""Create comics on Telegraph from ZIP/CBZ/RAR archives"""
|
||||
|
||||
strings = {
|
||||
"name": "TelegraphComic",
|
||||
"invalid_args": "<emoji document_id=5388785832956016892>❌</emoji> Invalid arguments. Usage: .telegraphcomics <title> | <cover_url> (optional)",
|
||||
"no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Reply to a message with ZIP/CBZ/RAR file",
|
||||
"unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Unsupported file format. Only ZIP/CBZ/RAR files are supported",
|
||||
"processing": "<emoji document_id=5256094480498436162>⏳</emoji> Processing archive...",
|
||||
"uploading": "<emoji document_id=5854762571659218443>⏳</emoji> Uploading images...",
|
||||
"creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Creating Telegraph article...",
|
||||
"archive_extracted": "<emoji document_id=5854762571659218443>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>",
|
||||
"upload_files": "<emoji document_id=5854762571659218443>📦</emoji> Upload image files:",
|
||||
"creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Creating Telegraph article:",
|
||||
"success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph article created!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Upload image files:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Creating Telegraph article:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>',
|
||||
"error": "<emoji document_id=5854929766146118183>❌</emoji> <b>Error:</b> {}",
|
||||
"_cls_doc": "Create comics on Telegraph from ZIP/CBZ/RAR archives",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Создание комиксов на Telegraph из ZIP/CBZ/RAR архивов",
|
||||
"invalid_args": "<emoji document_id=5388785832956016892>❌</emoji> Неверные аргументы. Использование: .telegraphcomics <название> | <ссылка_на_обложку>(необязательно)",
|
||||
"no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Ответьте на сообщение с ZIP/CBZ/RAR файлом",
|
||||
"unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Неподдерживаемый формат. Только ZIP/CBZ/RAR файлы",
|
||||
"processing": "<emoji document_id=5256094480498436162>⏳</emoji> Обработка архива...",
|
||||
"uploading": "<emoji document_id=5256094480498436162>⏳</emoji> Загрузка изображений...",
|
||||
"creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Создание Telegraph статьи...",
|
||||
"archive_extracted": "<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>",
|
||||
"upload_files": "<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:",
|
||||
"creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Создание Telegraph статьи:",
|
||||
"success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph статья создана!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Создание Telegraph статьи:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>',
|
||||
"error": "<emoji document_id=5388785832956016892>❌</emoji> <b>Ошибка:</b> {}",
|
||||
"available_services": "Доступные сервисы: catbox, bashupload, kappa, x0, tmpfiles, pomf",
|
||||
"current_service": "Текущий сервис: {}",
|
||||
"invalid_service": "❌ Неизвестный сервис: {}\n\n{}",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"upload_service",
|
||||
"catbox",
|
||||
"Upload service to use",
|
||||
validator=loader.validators.Choice(
|
||||
["catbox", "bashupload", "kappa", "x0", "tmpfiles", "pomf"]
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"short_name",
|
||||
"HikkaMods",
|
||||
"short name for the article",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"author_name",
|
||||
"HikkaMods",
|
||||
"nickname of the author of the article",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"author_url",
|
||||
"https://t.me/hikka_mods",
|
||||
"link to author",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.telegraph = Telegraph()
|
||||
self.telegraph.create_account(
|
||||
short_name=self.config["short_name"],
|
||||
author_name=self.config["author_name"],
|
||||
author_url=self.config["author_url"],
|
||||
)
|
||||
|
||||
async def _upload_file_to_service(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
url: str,
|
||||
file_path: str,
|
||||
field_name: str,
|
||||
**extra_fields,
|
||||
) -> Optional[str]:
|
||||
"""Generic file upload method"""
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field(field_name, f, filename=os.path.basename(file_path))
|
||||
|
||||
for key, value in extra_fields.items():
|
||||
data.add_field(key, value)
|
||||
|
||||
async with session.post(url, data=data) as response:
|
||||
if response.status == 200:
|
||||
result = await response.text()
|
||||
return result.strip() if result else None
|
||||
else:
|
||||
logger.info(
|
||||
f"Upload failed with status {response.status}: {await response.text()}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to {url}: {e}")
|
||||
return None
|
||||
|
||||
async def upload_to_catbox(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to catbox.moe"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
result = await self._upload_file_to_service(
|
||||
session,
|
||||
"https://catbox.moe/user/api.php",
|
||||
file_path,
|
||||
"fileToUpload",
|
||||
reqtype="fileupload",
|
||||
)
|
||||
return (
|
||||
result
|
||||
if result and result.startswith("https://files.catbox.moe/")
|
||||
else None
|
||||
)
|
||||
|
||||
async def upload_to_bashupload(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to bashupload.com"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field("file", f, filename=os.path.basename(file_path))
|
||||
|
||||
async with session.post(
|
||||
"https://bashupload.com", data=data
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
result = await response.text()
|
||||
|
||||
lines = result.strip().split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("https://"):
|
||||
return line
|
||||
|
||||
if "wget" in result:
|
||||
urls = [
|
||||
line
|
||||
for line in result.split("\n")
|
||||
if "wget" in line
|
||||
]
|
||||
if urls:
|
||||
parts = urls[0].split()
|
||||
for part in parts:
|
||||
if part.startswith("https://"):
|
||||
return part
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to bashupload: {e}")
|
||||
return None
|
||||
|
||||
async def upload_to_kappa(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to kappa.lol"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field("file", f, filename=os.path.basename(file_path))
|
||||
|
||||
async with session.post(
|
||||
"https://kappa.lol/api/upload", data=data
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
if result and "id" in result:
|
||||
return f"https://kappa.lol/{result['id']}"
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to kappa: {e}")
|
||||
return None
|
||||
|
||||
async def upload_to_x0(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to x0.at"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field("file", f, filename=os.path.basename(file_path))
|
||||
|
||||
async with session.post("https://x0.at", data=data) as response:
|
||||
if response.status == 200:
|
||||
result = await response.text()
|
||||
return (
|
||||
result.strip()
|
||||
if result and "https://" in result
|
||||
else None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to x0: {e}")
|
||||
return None
|
||||
|
||||
async def upload_to_tmpfiles(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to tmpfiles.org"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field("file", f, filename=os.path.basename(file_path))
|
||||
|
||||
async with session.post(
|
||||
"https://tmpfiles.org/api/v1/upload", data=data
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
if result and "data" in result and "url" in result["data"]:
|
||||
return result["data"]["url"]
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to tmpfiles: {e}")
|
||||
return None
|
||||
|
||||
async def upload_to_pomf(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to pomf.lain.la"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
data = aiohttp.FormData()
|
||||
data.add_field("files[]", f, filename=os.path.basename(file_path))
|
||||
|
||||
async with session.post(
|
||||
"https://pomf.lain.la/upload.php", data=data
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
if result and "files" in result and result["files"]:
|
||||
return result["files"][0].get("url")
|
||||
except Exception as e:
|
||||
logger.info(f"Error uploading to pomf: {e}")
|
||||
return None
|
||||
|
||||
async def upload_file(self, file_path: str) -> Optional[str]:
|
||||
"""Upload file to selected service"""
|
||||
service_name = self.config["upload_service"]
|
||||
|
||||
service_map = {
|
||||
"catbox": self.upload_to_catbox,
|
||||
"bashupload": self.upload_to_bashupload,
|
||||
"kappa": self.upload_to_kappa,
|
||||
"x0": self.upload_to_x0,
|
||||
"tmpfiles": self.upload_to_tmpfiles,
|
||||
"pomf": self.upload_to_pomf,
|
||||
}
|
||||
|
||||
service_func = service_map.get(service_name)
|
||||
if not service_func:
|
||||
return await self.upload_to_catbox(file_path)
|
||||
|
||||
try:
|
||||
result = await service_func(file_path)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Upload to {service_name} failed: {e}")
|
||||
return None
|
||||
|
||||
async def extract_zip_archive(self, zip_path: str, extract_dir: str) -> List[str]:
|
||||
"""Extract ZIP archive and return sorted list of image files"""
|
||||
image_extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif"}
|
||||
image_files = []
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
|
||||
for root, _, files in os.walk(extract_dir):
|
||||
for file in files:
|
||||
if os.path.splitext(file)[1].lower() in image_extensions:
|
||||
image_files.append(os.path.join(root, file))
|
||||
|
||||
image_files.sort(key=lambda x: os.path.basename(x).lower())
|
||||
|
||||
except Exception as e:
|
||||
logger.info(f"Error extracting ZIP archive: {e}")
|
||||
|
||||
return image_files
|
||||
|
||||
async def create_telegraph_article(
|
||||
self, title: str, image_urls: List[str], cover_url: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
"""Create Telegraph article with images"""
|
||||
try:
|
||||
if cover_url:
|
||||
content = f'<img src="{cover_url}"/><br>'
|
||||
content += "<br>".join(f'<img src="{url}"/>' for url in image_urls)
|
||||
else:
|
||||
content = "<br>".join(f'<img src="{url}"/>' for url in image_urls)
|
||||
|
||||
response = await asyncio.to_thread(
|
||||
lambda: self.telegraph.create_page(
|
||||
title=title,
|
||||
html_content=content,
|
||||
author_name=self.config["author_name"],
|
||||
author_url=self.config["author_url"],
|
||||
)
|
||||
)
|
||||
|
||||
return response["url"]
|
||||
|
||||
except Exception as e:
|
||||
logger.info(f"Error creating Telegraph article: {e}")
|
||||
return None
|
||||
|
||||
async def _process_cover_url(self, cover_url: str) -> Optional[str]:
|
||||
"""Process cover URL - handle Telegram message links and direct URLs"""
|
||||
if not cover_url:
|
||||
return None
|
||||
|
||||
cover_url = cover_url.strip()
|
||||
|
||||
if "t.me/" in cover_url and "/" in cover_url.split("t.me/")[1]:
|
||||
try:
|
||||
parts = cover_url.split("/")
|
||||
if len(parts) >= 4:
|
||||
chat_username = parts[-3]
|
||||
message_id = int(parts[-1])
|
||||
|
||||
message = await self.client.get_messages(
|
||||
chat_username, ids=message_id
|
||||
)
|
||||
if message and message.media:
|
||||
media_path = await message.download_media()
|
||||
if media_path:
|
||||
uploaded_url = await self.upload_file(media_path)
|
||||
os.remove(media_path)
|
||||
return uploaded_url
|
||||
except Exception as e:
|
||||
logger.info(f"Error processing Telegram cover link: {e}")
|
||||
return cover_url
|
||||
|
||||
return cover_url
|
||||
|
||||
async def _process_comics_request(self, message, create_func) -> None:
|
||||
"""Common logic for processing comics requests"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if not args or not reply:
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
return
|
||||
|
||||
if not isinstance(reply.media, MessageMediaDocument):
|
||||
await utils.answer(message, self.strings["no_reply"])
|
||||
return
|
||||
|
||||
if "|" in args:
|
||||
title, cover_url = args.split("|", 1)
|
||||
else:
|
||||
title = args
|
||||
cover_url = None
|
||||
|
||||
title = title.strip()
|
||||
cover_url = (
|
||||
await self._process_cover_url(cover_url.strip()) if cover_url else None
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings["processing"])
|
||||
|
||||
file_path = await reply.download_media()
|
||||
if not file_path:
|
||||
await utils.answer(
|
||||
message, self.strings["error"].format("Failed to download file")
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if not (file_path.lower().endswith((".zip", ".cbz"))):
|
||||
await utils.answer(message, self.strings["unsupported_format"])
|
||||
return
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
archive_path = file_path
|
||||
if file_path.lower().endswith(".cbz"):
|
||||
import shutil
|
||||
|
||||
zip_path = file_path[:-4] + ".zip"
|
||||
shutil.copy2(file_path, zip_path)
|
||||
archive_path = zip_path
|
||||
|
||||
image_files = await self.extract_zip_archive(archive_path, temp_dir)
|
||||
|
||||
if archive_path != file_path and os.path.exists(archive_path):
|
||||
os.remove(archive_path)
|
||||
if not image_files:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["error"].format("No images found in archive"),
|
||||
)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings["archive_extracted"])
|
||||
|
||||
await utils.answer(message, self.strings["uploading"])
|
||||
|
||||
upload_tasks = [self.upload_file(img_file) for img_file in image_files]
|
||||
upload_results = await asyncio.gather(
|
||||
*upload_tasks, return_exceptions=True
|
||||
)
|
||||
image_urls = []
|
||||
failed_uploads = 0
|
||||
upload_errors = []
|
||||
upload_status_lines = []
|
||||
|
||||
for i, (img_file, result) in enumerate(
|
||||
zip(image_files, upload_results)
|
||||
):
|
||||
filename = os.path.basename(img_file)
|
||||
if isinstance(result, Exception):
|
||||
error_str = str(result)
|
||||
logger.info(f"Upload failed: {error_str}")
|
||||
failed_uploads += 1
|
||||
upload_errors.append(error_str)
|
||||
upload_status_lines.append(
|
||||
f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>"
|
||||
)
|
||||
elif result and "https://" in result:
|
||||
image_urls.append(result)
|
||||
upload_status_lines.append(
|
||||
f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>"
|
||||
)
|
||||
else:
|
||||
failed_uploads += 1
|
||||
upload_errors.append("Invalid response from upload service")
|
||||
upload_status_lines.append(
|
||||
f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>"
|
||||
)
|
||||
|
||||
if not image_urls:
|
||||
error_details = []
|
||||
error_details.append(f"Failed uploads: {failed_uploads}")
|
||||
|
||||
if upload_errors:
|
||||
unique_errors = list(set(upload_errors))[:3]
|
||||
error_details.append("Errors: " + "; ".join(unique_errors))
|
||||
|
||||
error_msg = " | ".join(error_details)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["error"].format(error_msg),
|
||||
)
|
||||
return
|
||||
|
||||
upload_status = (
|
||||
self.strings["upload_files"] + "\n" + "\n".join(upload_status_lines)
|
||||
)
|
||||
await utils.answer(message, upload_status)
|
||||
|
||||
await utils.answer(message, self.strings["creating_article"])
|
||||
|
||||
article_url = await create_func(title, image_urls, cover_url)
|
||||
if article_url:
|
||||
article_status_lines = []
|
||||
for i, (img_file, url) in enumerate(zip(image_files, image_urls)):
|
||||
filename = os.path.basename(img_file)
|
||||
article_status_lines.append(
|
||||
f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>"
|
||||
)
|
||||
|
||||
upload_status = "\n".join(upload_status_lines)
|
||||
article_status = "\n".join(article_status_lines)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["success"].format(
|
||||
upload_status=upload_status,
|
||||
article_status=article_status,
|
||||
url=article_url,
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["error"].format("Failed to create article"),
|
||||
)
|
||||
except Exception as e:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["error"].format(f"Processing error: {e}"),
|
||||
)
|
||||
finally:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Создать комикс на Telegraph из ZIP/CBZ/RAR архива\nАргументы: <название> | <ссылка_на_обложку>(необязательно)\nИспользование: .telegraphcomics <title> | <cover_url>(optional)",
|
||||
en_doc="Create Telegraph comic from ZIP/CBZ/RAR archive\nArguments: <title> | <cover_url>(optional)\nUsage: .telegraphcomics <title> | <cover_url>(optional)",
|
||||
)
|
||||
async def telegraphcomicscmd(self, message):
|
||||
await self._process_comics_request(message, self.create_telegraph_article)
|
||||
@@ -185,20 +185,25 @@ class TimedEmojiStatusMod(loader.Module):
|
||||
return "❌"
|
||||
|
||||
if document_id:
|
||||
return f"<emoji document_id={document_id}>📋</emoji>"
|
||||
return f"[Custom Emoji ID: {document_id}]"
|
||||
|
||||
if emoji_str.isdigit():
|
||||
return f"<emoji document_id={emoji_str}>📋</emoji>"
|
||||
return f"[Custom Emoji ID: {emoji_str}]"
|
||||
|
||||
if "<emoji document_id=" in emoji_str:
|
||||
return emoji_str
|
||||
|
||||
import re
|
||||
match = re.search(r'document_id=(\d+)', emoji_str)
|
||||
if match:
|
||||
return f"[Custom Emoji ID: {match.group(1)}]"
|
||||
return "[Custom Emoji]"
|
||||
|
||||
if len(emoji_str) == 1 or (
|
||||
len(emoji_str) <= 4 and all(ord(c) >= 0x1F000 for c in emoji_str)
|
||||
):
|
||||
return emoji_str
|
||||
|
||||
return emoji_str
|
||||
return emoji_str[:10] + "..." if len(emoji_str) > 10 else emoji_str
|
||||
|
||||
async def _set_emoji_status(
|
||||
self, emoji_input: str, until: datetime | None = None, message: Message = None
|
||||
|
||||
@@ -38,6 +38,7 @@ from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VirusTotalMod(loader.Module):
|
||||
"""Professional file scanning with VirusTotal"""
|
||||
|
||||
223
archquise/H.Modules/caliases.py
Normal file
223
archquise/H.Modules/caliases.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# Proprietary License Agreement
|
||||
|
||||
# Copyright (c) 2024-29 CodWiz
|
||||
|
||||
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
|
||||
|
||||
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
|
||||
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
|
||||
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
|
||||
|
||||
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
|
||||
|
||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: CAliases
|
||||
# Description: Module for custom aliases
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# scope: CAliases
|
||||
# scope: CAliases 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import Dict, Optional
|
||||
|
||||
from telethon import types
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CustomAliasesMod(loader.Module):
|
||||
"""Module for custom aliases"""
|
||||
|
||||
strings = {
|
||||
"name": "CAliases",
|
||||
"c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Command <code>{}</code> not found!</b>",
|
||||
"a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Custom alias <code>{}</code> not found!</b>",
|
||||
"no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>You must specify two args: alias name and command</b>",
|
||||
"added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <i>{alias}</i> for command "
|
||||
"<code>{prefix}{cmd}</code> successfully added!</b>\n<b>Use it like:</b> <code>{prefix}{alias}{args}</code>"
|
||||
),
|
||||
"argsopt": " [args (optional)]",
|
||||
"deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <code>{}</code> successfully deleted</b>",
|
||||
"list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Custom aliases ({len}):</b>\n",
|
||||
"no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>You have no custom aliases!</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Команда <code>{}</code> не найдена!</b>",
|
||||
"a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Кастомный алиас <code>{}</code> не найден!</b>",
|
||||
"no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>Вы должны указать как минимум два аргумента: имя алиаса и команду</b>",
|
||||
"added": (
|
||||
"<emoji document_id=5314250708508220914>✅</emoji> <b>Успешно добавил алиас с названием <i>{alias}</i> "
|
||||
"для команды <code>{prefix}{cmd}</code></b>\n<b>Используй его так:</b> <code>{prefix}{alias}{args}</code>"
|
||||
),
|
||||
"argsopt": " [аргументы (необязательно)]",
|
||||
"deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Кастомный алиас <code>{}</code> успешно удалён</b>",
|
||||
"list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Кастомные алиасы (всего {len}):</b>\n",
|
||||
"no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>У вас нет кастомных алиасов!</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._aliases_cache: Optional[Dict[str, Dict[str, str]]] = None
|
||||
self._prefix_cache: Optional[str] = None
|
||||
|
||||
def _get_aliases(self) -> Dict[str, Dict[str, str]]:
|
||||
if self._aliases_cache is None:
|
||||
self._aliases_cache = self.get("aliases", {})
|
||||
return self._aliases_cache
|
||||
|
||||
def _save_aliases(self, aliases: Dict[str, Dict[str, str]]) -> None:
|
||||
self.set("aliases", aliases)
|
||||
self._aliases_cache = aliases
|
||||
|
||||
def _get_prefix(self) -> str:
|
||||
if self._prefix_cache is None:
|
||||
self._prefix_cache = self.get_prefix()
|
||||
return self._prefix_cache
|
||||
|
||||
def _format_alias_list(self) -> str:
|
||||
"""Format aliases list for display"""
|
||||
aliases = self._get_aliases()
|
||||
if not aliases:
|
||||
return self.strings["no_aliases"]
|
||||
|
||||
lines = [self.strings["list"].format(len=len(aliases))]
|
||||
|
||||
for alias_name, alias_data in aliases.items():
|
||||
cmd = alias_data["command"]
|
||||
if alias_data.get("args"):
|
||||
cmd += f" {alias_data['args']}"
|
||||
|
||||
lines.append(
|
||||
f" <emoji document_id=5280726938279749656>▪️</emoji> <code>{alias_name}</code> "
|
||||
f"<emoji document_id=5960671702059848143>👈</emoji> <code>{cmd}</code>"
|
||||
)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _validate_command(self, cmd: str) -> bool:
|
||||
"""Check if command exists"""
|
||||
return cmd in self.allmodules.commands
|
||||
|
||||
def _parse_alias_args(self, message: types.Message) -> tuple:
|
||||
"""Parse alias command arguments"""
|
||||
raw_args = utils.get_args_raw(message)
|
||||
if not raw_args:
|
||||
return None, None, None
|
||||
|
||||
parts = raw_args.split(" ", 2)
|
||||
if len(parts) < 2:
|
||||
return None, None, None
|
||||
|
||||
alias_name = parts[0]
|
||||
command = parts[1]
|
||||
cmd_args = parts[2] if len(parts) > 2 else ""
|
||||
|
||||
return alias_name, command, cmd_args
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Получить список всех алиасов",
|
||||
en_doc=" Get list of all aliases"
|
||||
)
|
||||
async def caliasescmd(self, message: types.Message):
|
||||
"""Get all aliases"""
|
||||
await utils.answer(message, self._format_alias_list())
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<имя> Удалить алиас",
|
||||
en_doc="<name> Remove alias"
|
||||
)
|
||||
async def rmcaliascmd(self, message: types.Message):
|
||||
"""Remove alias"""
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
alias_name = args[0]
|
||||
aliases = self._get_aliases()
|
||||
|
||||
if alias_name not in aliases:
|
||||
return await utils.answer(message, self.strings["a404"].format(alias_name))
|
||||
|
||||
del aliases[alias_name]
|
||||
self._save_aliases(aliases)
|
||||
await utils.answer(message, self.strings["deleted"].format(alias_name))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<имя> <команда> [аргументы] Добавить новый алиас (может содержать ключевое слово {args})",
|
||||
en_doc="<name> <command> [arguments] Add new alias (may contain {args} keyword)",
|
||||
)
|
||||
async def caliascmd(self, message: types.Message):
|
||||
"""Add new alias (may contain {args} keyword)"""
|
||||
alias_name, command, cmd_args = self._parse_alias_args(message)
|
||||
|
||||
if not alias_name or not command:
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
if not self._validate_command(command):
|
||||
return await utils.answer(message, self.strings["c404"].format(command))
|
||||
|
||||
aliases = self._get_aliases()
|
||||
aliases[alias_name] = {"command": command, "args": cmd_args}
|
||||
self._save_aliases(aliases)
|
||||
|
||||
prefix = self._get_prefix()
|
||||
full_cmd = f"{command} {cmd_args}" if cmd_args else command
|
||||
args_display = self.strings["argsopt"] if "{args}" in cmd_args else ""
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["added"].format(
|
||||
alias=alias_name,
|
||||
prefix=prefix,
|
||||
cmd=full_cmd,
|
||||
args=args_display,
|
||||
),
|
||||
)
|
||||
|
||||
@loader.tag(only_messages=True, no_media=True, no_inline=True, out=True)
|
||||
async def watcher(self, message: types.Message):
|
||||
"""Handle alias execution"""
|
||||
if not message.raw_text:
|
||||
return
|
||||
|
||||
aliases = self._get_aliases()
|
||||
prefix = self._get_prefix()
|
||||
text = message.raw_text
|
||||
first_word = text.split()[0].lower()
|
||||
|
||||
if not first_word.startswith(prefix):
|
||||
return
|
||||
|
||||
alias_name = first_word[len(prefix) :]
|
||||
if alias_name not in aliases:
|
||||
return
|
||||
|
||||
alias_data = aliases[alias_name]
|
||||
command = alias_data["command"]
|
||||
template_args = alias_data.get("args", "")
|
||||
|
||||
user_args = utils.get_args_raw(message)
|
||||
if user_args and template_args:
|
||||
final_command = f"{command} {template_args}".format(args=user_args)
|
||||
else:
|
||||
final_command = f"{command} {template_args}" if template_args else command
|
||||
|
||||
try:
|
||||
await self.allmodules.commands[command](
|
||||
await utils.answer(message, f"{prefix}{final_command}")
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing alias '{alias_name}': {e}")
|
||||
@@ -1,7 +1,7 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
#░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
|
||||
#░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
|
||||
#░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
|
||||
# ░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
|
||||
# ░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
|
||||
# ░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
|
||||
# Name: DelMessTools
|
||||
# Description: Module to manage and delete your messages in the current chat
|
||||
# Author: @codrago_m
|
||||
@@ -19,10 +19,11 @@
|
||||
|
||||
__version__ = (1, 1, 0)
|
||||
|
||||
from hikkatl.tl.types import Message, DocumentAttributeFilename
|
||||
from telethon.tl.types import Message, DocumentAttributeFilename
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
class DelMessTools(loader.Module):
|
||||
"""Module to manage and delete your messages in the current chat"""
|
||||
|
||||
@@ -38,7 +39,12 @@ class DelMessTools(loader.Module):
|
||||
"enabled": "It's not operational now anyway.",
|
||||
"disabled": "Operation status changed to disabled.",
|
||||
"interrupted": "The deletion was interrupted because you changed your mind.",
|
||||
"none": "You didn't even intend to delete anything here, but anyway it's disabled now."
|
||||
"none": "You didn't even intend to delete anything here, but anyway it's disabled now.",
|
||||
"no_args": "Please specify arguments for the command.",
|
||||
"no_keyword": "Please specify a keyword to delete messages.",
|
||||
"no_length": "Please specify a valid length.",
|
||||
"invalid_time": "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS",
|
||||
"invalid_length": "Please specify a valid number for length.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
@@ -52,30 +58,58 @@ class DelMessTools(loader.Module):
|
||||
"enabled": "Оно итак сейчас не работает.",
|
||||
"disabled": "Режим работы изменен на выключено.",
|
||||
"interrupted": "Удаление было прервано т.к вы передумали.",
|
||||
"none": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено."
|
||||
"none": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено.",
|
||||
"no_args": "Пожалуйста, укажите аргументы для команды.",
|
||||
"no_keyword": "Пожалуйста, укажите ключевое слово для удаления сообщений.",
|
||||
"no_length": "Пожалуйста, укажите корректную длину.",
|
||||
"invalid_time": "Неверный формат времени. Используйте формат: YYYY-MM-DD HH:MM:SS",
|
||||
"invalid_length": "Пожалуйста, укажите корректное число для длины.",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.client = None
|
||||
self.db = None
|
||||
self.tg_id = None
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.tg_id = (await client.get_me()).id
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[reply] [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в текущем чате или только до сообщения, на которое ответили\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
|
||||
en_doc="[reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
|
||||
)
|
||||
async def purge(self, message: Message):
|
||||
if not self.client or not self.db or not self.tg_id:
|
||||
return
|
||||
|
||||
async def purgecmd(self, message: Message):
|
||||
""" [reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to
|
||||
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||
"""
|
||||
reply = await message.get_reply_message()
|
||||
is_last = False
|
||||
args, types_filter, is_each = self.get_types_filter(message)
|
||||
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||
|
||||
try:
|
||||
entity = await self.client.get_entity(message.chat.id)
|
||||
is_forum = getattr(entity, "forum", False)
|
||||
except Exception:
|
||||
is_forum = False
|
||||
|
||||
status = self.db.get(__name__, "status", {})
|
||||
status[message.chat.id] = True
|
||||
self.db.set(__name__, "status", status)
|
||||
|
||||
deleted_count = 0
|
||||
async for i in self.client.iter_messages(message.peer_id):
|
||||
status = self.db.get(__name__, "status", {})
|
||||
if status.get(message.chat.id, None) is not True:
|
||||
return await utils.answer(message, self.strings["interrupted"])
|
||||
|
||||
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
if is_forum and not is_each:
|
||||
try:
|
||||
if utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if i.from_id == self.tg_id and self.is_valid_type(i, types_filter):
|
||||
if reply:
|
||||
@@ -83,58 +117,86 @@ class DelMessTools(loader.Module):
|
||||
break
|
||||
if i.id == reply.id:
|
||||
is_last = True
|
||||
await message.client.delete_messages(message.peer_id, [i.id])
|
||||
try:
|
||||
await self.client.delete_messages(message.peer_id, [i.id])
|
||||
deleted_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if reply:
|
||||
await utils.answer(message, self.strings["purge_reply_complete"])
|
||||
else:
|
||||
await utils.answer(message, self.strings["purge_complete"])
|
||||
|
||||
async def purgekeywordcmd(self, message: Message):
|
||||
""" <keyword> [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat
|
||||
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||
"""
|
||||
@loader.command(
|
||||
ru_doc="<ключевое слово> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения с указанным ключевым словом в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
|
||||
en_doc="<keyword> [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
|
||||
)
|
||||
async def purgekeyword(self, message: Message):
|
||||
if not self.client or not self.db or not self.tg_id:
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
args, types_filter, is_each = self.get_types_filter(message)
|
||||
if not args:
|
||||
return await utils.answer(message, "Please specify a keyword to delete messages.")
|
||||
return await utils.answer(message, self.strings["no_keyword"])
|
||||
|
||||
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||
try:
|
||||
entity = await self.client.get_entity(message.chat.id)
|
||||
is_forum = getattr(entity, "forum", False)
|
||||
except Exception:
|
||||
is_forum = False
|
||||
|
||||
status = self.db.get(__name__, "status", {})
|
||||
status[message.chat.id] = True
|
||||
self.db.set(__name__, "status", status)
|
||||
|
||||
deleted_count = 0
|
||||
async for i in self.client.iter_messages(message.peer_id):
|
||||
status = self.db.get(__name__, "status", {})
|
||||
if status.get(message.chat.id, None) is not True:
|
||||
return await utils.answer(message, self.strings["interrupted"])
|
||||
|
||||
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
if is_forum and not is_each:
|
||||
try:
|
||||
if utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if i.from_id == self.tg_id and args.lower() in (i.text or '').lower() and self.is_valid_type(i, types_filter):
|
||||
await message.client.delete_messages(message.chat.id, [i.id])
|
||||
if (
|
||||
i.from_id == self.tg_id
|
||||
and args.lower() in (i.text or "").lower()
|
||||
and self.is_valid_type(i, types_filter)
|
||||
):
|
||||
try:
|
||||
await self.client.delete_messages(message.chat.id, [i.id])
|
||||
deleted_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await utils.answer(message, self.strings["purge_keyword_complete"])
|
||||
|
||||
async def purgetimecmd(self, message: Message):
|
||||
""" <start_time> <end_time> [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat
|
||||
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||
Time format: YYYY-MM-DD HH:MM:SS
|
||||
"""
|
||||
@loader.command(
|
||||
ru_doc="<начальное время> <конечное время> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в указанном временном диапазоне в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется\n Формат времени: YYYY-MM-DD HH:MM:SS",
|
||||
en_doc="<start_time> <end_time> [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored\n Time format: YYYY-MM-DD HH:MM:SS",
|
||||
)
|
||||
async def purgetime(self, message: Message):
|
||||
if not self.client or not self.db or not self.tg_id:
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
args, types_filter, is_each = self.get_types_filter(message)
|
||||
args = args.split()
|
||||
|
||||
if not args or len(args) < 2:
|
||||
return await utils.answer(message, "Please specify the start and end time in the format: YYYY-MM-DD HH:MM:SS")
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@@ -142,66 +204,111 @@ class DelMessTools(loader.Module):
|
||||
start_time = datetime.strptime(args[0], "%Y-%m-%d %H:%M:%S")
|
||||
end_time = datetime.strptime(args[1], "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
return await utils.answer(message, "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS")
|
||||
return await utils.answer(message, self.strings["invalid_time"])
|
||||
|
||||
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||
try:
|
||||
entity = await self.client.get_entity(message.chat.id)
|
||||
is_forum = getattr(entity, "forum", False)
|
||||
except Exception:
|
||||
is_forum = False
|
||||
|
||||
status = self.db.get(__name__, "status", {})
|
||||
status[message.chat.id] = True
|
||||
self.db.set(__name__, "status", status)
|
||||
|
||||
deleted_count = 0
|
||||
async for i in self.client.iter_messages(message.peer_id):
|
||||
status = self.db.get(__name__, "status", {})
|
||||
if status.get(message.chat.id, None) is not True:
|
||||
return await utils.answer(message, self.strings["interrupted"])
|
||||
|
||||
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
if is_forum and not is_each:
|
||||
try:
|
||||
if utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if i.from_id == self.tg_id and start_time <= i.date <= end_time and self.is_valid_type(i, types_filter):
|
||||
await message.client.delete_messages(message.peer_id, [i.id])
|
||||
if (
|
||||
i.from_id == self.tg_id
|
||||
and start_time <= i.date <= end_time
|
||||
and self.is_valid_type(i, types_filter)
|
||||
):
|
||||
try:
|
||||
await self.client.delete_messages(message.peer_id, [i.id])
|
||||
deleted_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await utils.answer(message, self.strings["purge_time_complete"])
|
||||
|
||||
async def purgelengthcmd(self, message: Message):
|
||||
""" <length> [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat
|
||||
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||
"""
|
||||
@loader.command(
|
||||
ru_doc="<длина> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения указанной длины в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
|
||||
en_doc="<length> [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
|
||||
)
|
||||
async def purgelength(self, message: Message):
|
||||
if not self.client or not self.db or not self.tg_id:
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
args, types_filter, is_each = self.get_types_filter(message)
|
||||
if not args:
|
||||
return await utils.answer(message, "Please specify a valid length.")
|
||||
return await utils.answer(message, self.strings["no_length"])
|
||||
|
||||
length = int(args)
|
||||
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||
try:
|
||||
length = int(args)
|
||||
except ValueError:
|
||||
return await utils.answer(message, self.strings["invalid_length"])
|
||||
|
||||
try:
|
||||
entity = await self.client.get_entity(message.chat.id)
|
||||
is_forum = getattr(entity, "forum", False)
|
||||
except Exception:
|
||||
is_forum = False
|
||||
|
||||
status = self.db.get(__name__, "status", {})
|
||||
status[message.chat.id] = True
|
||||
self.db.set(__name__, "status", status)
|
||||
|
||||
deleted_count = 0
|
||||
async for i in self.client.iter_messages(message.peer_id):
|
||||
status = self.db.get(__name__, "status", {})
|
||||
if status.get(message.chat.id, None) is not True:
|
||||
return await utils.answer(message, self.strings["interrupted"])
|
||||
|
||||
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
if is_forum and not is_each:
|
||||
try:
|
||||
if utils.get_topic(message) != utils.get_topic(i):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if i.from_id == self.tg_id and len(i.text or '') == length and self.is_valid_type(i, types_filter):
|
||||
await message.client.delete_messages(message.peer_id, [i.id])
|
||||
if (
|
||||
i.from_id == self.tg_id
|
||||
and len(i.text or "") == length
|
||||
and self.is_valid_type(i, types_filter)
|
||||
):
|
||||
try:
|
||||
await self.client.delete_messages(message.peer_id, [i.id])
|
||||
deleted_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await utils.answer(message, self.strings["purge_length_complete"])
|
||||
|
||||
async def nopurgecmd(self, message: Message):
|
||||
"""
|
||||
Interrupt the deletion process
|
||||
Use in the chat where you've previously started deletion
|
||||
"""
|
||||
@loader.command(
|
||||
ru_doc="Прервать процесс удаления\nИспользуйте в чате, где вы ранее начали удаление",
|
||||
en_doc="Interrupt the deletion process\nUse in the chat where you've previously started deletion",
|
||||
)
|
||||
async def nopurge(self, message: Message):
|
||||
if not self.db:
|
||||
return
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
|
||||
|
||||
status = self.db.get(__name__, "status", {})
|
||||
_status = status.get(chat_id, None)
|
||||
status[chat_id] = False
|
||||
@@ -214,20 +321,26 @@ class DelMessTools(loader.Module):
|
||||
else:
|
||||
await utils.answer(message, self.strings["none"])
|
||||
|
||||
|
||||
def get_types_filter(self, message: Message):
|
||||
""" Get the types filter from the command arguments."""
|
||||
args = utils.get_args_raw(message).split()
|
||||
"""Get the types filter from the command arguments."""
|
||||
args_raw = utils.get_args_raw(message)
|
||||
if not args_raw:
|
||||
return "", [], False
|
||||
|
||||
args = args_raw.split()
|
||||
types_filter = []
|
||||
valid_types = ["-img", "-voice", "-file", "-all"]
|
||||
is_each = "-all" in args
|
||||
|
||||
_args = args_raw
|
||||
args_ = ""
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
if arg in valid_types:
|
||||
_args = " ".join(args[:i])
|
||||
args_ = " ".join(args[i:])
|
||||
break
|
||||
|
||||
|
||||
if "-img" in args_:
|
||||
types_filter.append("img")
|
||||
if "-voice" in args_:
|
||||
@@ -240,15 +353,17 @@ class DelMessTools(loader.Module):
|
||||
return _args, types_filter, is_each
|
||||
|
||||
def is_valid_type(self, message: Message, types_filter):
|
||||
""" Check if the message matches the specified types filter. """
|
||||
"""Check if the message matches the specified types filter."""
|
||||
if not types_filter:
|
||||
return True # No filtering means all types are valid
|
||||
|
||||
if "img" in types_filter and message.photo:
|
||||
if "img" in types_filter and hasattr(message, "photo") and message.photo:
|
||||
return True
|
||||
if "voice" in types_filter and message.voice:
|
||||
return True
|
||||
if "file" in types_filter and isinstance(message.document, DocumentAttributeFilename):
|
||||
if "voice" in types_filter and hasattr(message, "voice") and message.voice:
|
||||
return True
|
||||
if "file" in types_filter and hasattr(message, "document") and message.document:
|
||||
for attr in getattr(message.document, "attributes", []):
|
||||
if isinstance(attr, DocumentAttributeFilename):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
350
archquise/H.Modules/privacy.py
Normal file
350
archquise/H.Modules/privacy.py
Normal file
@@ -0,0 +1,350 @@
|
||||
# Proprietary License Agreement
|
||||
|
||||
# Copyright (c) 2024-29 CodWiz
|
||||
|
||||
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
|
||||
|
||||
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
|
||||
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
|
||||
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
|
||||
|
||||
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
|
||||
|
||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: Privacy
|
||||
# Description: Module for fast privacy settings management
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# scope: Privacy
|
||||
# scope: Privacy 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import telethon
|
||||
from telethon import types
|
||||
|
||||
from .. import inline, loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PrivacyMod(loader.Module):
|
||||
"""Module for fast privacy settings management"""
|
||||
|
||||
_PRIVACY_TYPES = {
|
||||
"phone": types.InputPrivacyKeyPhoneNumber,
|
||||
"add_by_phone": types.InputPrivacyKeyAddedByPhone,
|
||||
"online": types.InputPrivacyKeyStatusTimestamp,
|
||||
"photos": types.InputPrivacyKeyProfilePhoto,
|
||||
"forwards": types.InputPrivacyKeyForwards,
|
||||
"calls": types.InputPrivacyKeyPhoneCall,
|
||||
"p2p": types.InputPrivacyKeyPhoneP2P,
|
||||
"voices": types.InputPrivacyKeyVoiceMessages,
|
||||
"bio": getattr(types, "InputPrivacyKeyAbout", None),
|
||||
"invites": types.InputPrivacyKeyChatInvite,
|
||||
}
|
||||
|
||||
_PRIVACY_RULES = {
|
||||
types.PrivacyValueAllowAll: types.InputPrivacyValueAllowAll,
|
||||
types.PrivacyValueAllowChatParticipants: types.InputPrivacyValueAllowChatParticipants,
|
||||
types.PrivacyValueAllowContacts: types.InputPrivacyValueAllowContacts,
|
||||
types.PrivacyValueAllowUsers: types.InputPrivacyValueAllowUsers,
|
||||
types.PrivacyValueDisallowAll: types.InputPrivacyValueDisallowAll,
|
||||
types.PrivacyValueDisallowChatParticipants: types.InputPrivacyValueDisallowChatParticipants,
|
||||
types.PrivacyValueDisallowContacts: types.InputPrivacyValueDisallowContacts,
|
||||
types.PrivacyValueDisallowUsers: types.InputPrivacyValueDisallowUsers,
|
||||
}
|
||||
|
||||
strings = {
|
||||
"name": "Privacy",
|
||||
"privacy_types": (
|
||||
"<emoji document_id=5974492756494519709>🔗</emoji> <b>Available privacy types:</b>\n"
|
||||
),
|
||||
"no_user": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>User not specified</b>",
|
||||
"u_silly": (
|
||||
"<emoji document_id=5449682572223194186>🥺</emoji> <b>You can't set privacy exceptions for yourself</b>"
|
||||
),
|
||||
"choose_type": "🔑 <b>Select privacy type to manage</b>",
|
||||
"not_supported_type": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> <b>Privacy type '{}' not supported</b>"
|
||||
),
|
||||
"allowed": (
|
||||
"<emoji document_id=5298609004551887592>💕</emoji> <b>{user} added to allowed users for [{type}]</b>"
|
||||
),
|
||||
"disallowed": (
|
||||
"<emoji document_id=5224379368242965520>💔</emoji> <b>{user} added to disallowed users for [{type}]</b>"
|
||||
),
|
||||
"privacy": {
|
||||
"phone": "Phone Number",
|
||||
"add_by_phone": "Find by Phone",
|
||||
"p2p": "P2P Calls",
|
||||
"online": "Last Seen",
|
||||
"photos": "Profile Photos",
|
||||
"forwards": "Message Forwards",
|
||||
"calls": "Phone Calls",
|
||||
"voices": "Voice Messages",
|
||||
"bio": "Bio",
|
||||
"invites": "Chat Invites",
|
||||
},
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Модуль для быстрого управления настройками приватности",
|
||||
"privacy_types": (
|
||||
"<emoji document_id=5974492756494519709>🔗</emoji> <b>Доступные типы приватности:</b>\n"
|
||||
),
|
||||
"no_user": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Пользователь не указан</b>",
|
||||
"u_silly": (
|
||||
"<emoji document_id=5449682572223194186>🥺</emoji> <b>Нельзя установить исключения приватности для самого себя</b>"
|
||||
),
|
||||
"choose_type": "🔑 <b>Выберите тип настроек приватности</b>",
|
||||
"not_supported_type": (
|
||||
"<emoji document_id=5312383351217201533>⚠️</emoji> <b>Тип приватности '{}' не поддерживается</b>"
|
||||
),
|
||||
"allowed": (
|
||||
"<emoji document_id=5298609004551887592>💕</emoji> <b>{user} добавлен в разрешённые для [{type}]</b>"
|
||||
),
|
||||
"disallowed": (
|
||||
"<emoji document_id=5224379368242965520>💔</emoji> <b>{user} добавлен в запрещённые для [{type}]</b>"
|
||||
),
|
||||
"privacy": {
|
||||
"phone": "Номер телефона",
|
||||
"add_by_phone": "Поиск по номеру",
|
||||
"p2p": "P2P звонки",
|
||||
"online": "Последний вход",
|
||||
"photos": "Фотографии профиля",
|
||||
"forwards": "Пересылка сообщений",
|
||||
"calls": "Звонки",
|
||||
"voices": "Голосовые сообщения",
|
||||
"bio": "О себе",
|
||||
"invites": "Приглашения в чаты",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._client: Optional[telethon.TelegramClient] = None
|
||||
self._me: Optional[types.User] = None
|
||||
|
||||
async def client_ready(self, client: telethon.TelegramClient, db) -> None:
|
||||
"""Initialize client and get current user"""
|
||||
self._client = client
|
||||
self._me = await client.get_me()
|
||||
|
||||
async def _get_user_id(self, message: types.Message) -> Optional[int]:
|
||||
"""Extract user ID from reply or username"""
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
return reply.sender_id
|
||||
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
return None
|
||||
|
||||
username = args[0]
|
||||
match = re.search(r"(?:t\.me/|@|^(\w+)\.t\.me$)([a-zA-Z0-9_.]+)", username)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
username = match.group(1) or match.group(2)
|
||||
try:
|
||||
entity = await self._client.get_entity(username)
|
||||
return getattr(entity, "user_id", getattr(entity, "id", None))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _format_privacy_list(self) -> str:
|
||||
"""Format privacy types list"""
|
||||
lines = [self.strings["privacy_types"]]
|
||||
|
||||
for key, name in self.strings["privacy"].items():
|
||||
if key in self._PRIVACY_TYPES:
|
||||
lines.append(f" <code>{key}</code> — {name}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _create_privacy_keyboard(
|
||||
self, user: types.User, action: str
|
||||
) -> List[List[Dict]]:
|
||||
"""Create inline keyboard for privacy type selection"""
|
||||
buttons = []
|
||||
|
||||
for key, name in self.strings["privacy"].items():
|
||||
if key not in self._PRIVACY_TYPES:
|
||||
continue
|
||||
|
||||
buttons.append(
|
||||
[
|
||||
{
|
||||
"text": name,
|
||||
"callback": self._privacy_callback_handler,
|
||||
"args": (user, key, action),
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
return [buttons[i : i + 2] for i in range(0, len(buttons), 2)]
|
||||
|
||||
async def _privacy_callback_handler(self, call: inline.types.InlineCall) -> None:
|
||||
"""Handle privacy type selection callback"""
|
||||
user, privacy_key, action = call.data
|
||||
|
||||
if privacy_key not in self._PRIVACY_TYPES:
|
||||
await call.answer(self.strings["not_supported_type"].format(privacy_key))
|
||||
return
|
||||
|
||||
privacy_type = self._PRIVACY_TYPES[privacy_key]
|
||||
await self._update_privacy_settings(user, privacy_type, action)
|
||||
|
||||
action_text = "allowed" if action == "allow" else "disallowed"
|
||||
await call.edit(
|
||||
self.strings[action_text].format(
|
||||
user=telethon.utils.get_display_name(user),
|
||||
type=self.strings["privacy"][privacy_key],
|
||||
)
|
||||
)
|
||||
|
||||
async def _update_privacy_settings(
|
||||
self, user: types.User, privacy_key: types.TypeInputPrivacyKey, action: str
|
||||
) -> None:
|
||||
"""Update privacy settings for specified user"""
|
||||
try:
|
||||
current_rules = await self._client(
|
||||
telethon.functions.account.GetPrivacyRequest(key=privacy_key)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting privacy rules: {e}")
|
||||
return
|
||||
|
||||
new_rules = []
|
||||
existing_user_ids = set()
|
||||
|
||||
for rule in current_rules.rules:
|
||||
rule_type = type(rule)
|
||||
|
||||
if rule_type == types.PrivacyValueAllowUsers:
|
||||
for user_id in rule.users:
|
||||
existing_user_ids.add(user_id)
|
||||
if user_id != user.id:
|
||||
new_rules.append(rule)
|
||||
elif rule_type == types.PrivacyValueDisallowUsers:
|
||||
for user_id in rule.users:
|
||||
existing_user_ids.add(user_id)
|
||||
if user_id != user.id:
|
||||
new_rules.append(rule)
|
||||
|
||||
if action == "allow" and user.id not in existing_user_ids:
|
||||
new_rules.append(
|
||||
types.InputPrivacyValueAllowUsers(
|
||||
[types.InputUser(user.id, user.access_hash)]
|
||||
)
|
||||
)
|
||||
elif action == "disallow" and user.id in existing_user_ids:
|
||||
for rule in new_rules[:]:
|
||||
if type(rule) in [
|
||||
types.PrivacyValueAllowUsers,
|
||||
types.PrivacyValueDisallowUsers,
|
||||
]:
|
||||
user_list = getattr(rule, "users", [])
|
||||
if user.id in user_list:
|
||||
user_list.remove(user.id)
|
||||
if not user_list:
|
||||
new_rules.remove(rule)
|
||||
break
|
||||
|
||||
try:
|
||||
await self._client(
|
||||
telethon.functions.account.SetPrivacyRequest(
|
||||
key=privacy_key, rules=new_rules
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating privacy settings: {e}")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Список типов настроек приватности",
|
||||
en_doc="List of privacy types"
|
||||
)
|
||||
async def privacytypescmd(self, message: types.Message) -> None:
|
||||
"""Show available privacy types"""
|
||||
await utils.answer(message, self._format_privacy_list())
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<user> Добавить в разрешённые",
|
||||
en_doc="<user> Add to allowed list",
|
||||
)
|
||||
async def allowusercmd(self, message: types.Message) -> None:
|
||||
"""Add user to privacy exceptions"""
|
||||
user_id = await self._get_user_id(message)
|
||||
if not user_id:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
if user_id == self._me.id:
|
||||
return await utils.answer(message, self.strings["u_silly"])
|
||||
|
||||
try:
|
||||
user_entity = await self._client.get_entity(user_id)
|
||||
except Exception:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings["choose_type"],
|
||||
reply_markup=self._create_privacy_keyboard(user_entity, "allow"),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="<user> Добавить в запрещённые",
|
||||
en_doc="<user> Add to forbidden list",
|
||||
)
|
||||
async def disallowuser(self, message: types.Message) -> None:
|
||||
"""Add user to privacy restrictions"""
|
||||
user_id = await self._get_user_id(message)
|
||||
if not user_id:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
if user_id == self._me.id:
|
||||
return await utils.answer(message, self.strings["u_silly"])
|
||||
|
||||
try:
|
||||
user_entity = await self._client.get_entity(user_id)
|
||||
except Exception:
|
||||
return await utils.answer(message, self.strings["no_user"])
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings["choose_type"],
|
||||
reply_markup=self._create_privacy_keyboard(user_entity, "disallow"),
|
||||
)
|
||||
|
||||
async def _create_privacy_keyboard(
|
||||
self, user: types.User, action: str
|
||||
) -> List[List[Dict]]:
|
||||
"""Create inline keyboard for privacy type selection"""
|
||||
buttons = []
|
||||
|
||||
for key, name in self.strings["privacy"].items():
|
||||
if key not in self._PRIVACY_TYPES:
|
||||
continue
|
||||
|
||||
buttons.append(
|
||||
[
|
||||
{
|
||||
"text": name,
|
||||
"callback": self._privacy_callback_handler,
|
||||
"args": (user, key, action),
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
return [buttons[i : i + 2] for i in range(0, len(buttons), 2)]
|
||||
116
archquise/H.Modules/sdsaver.py
Normal file
116
archquise/H.Modules/sdsaver.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Proprietary License Agreement
|
||||
|
||||
# Copyright (c) 2024-29 CodWiz
|
||||
|
||||
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
|
||||
|
||||
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
|
||||
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
|
||||
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
|
||||
|
||||
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
|
||||
|
||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: SDSaver
|
||||
# Description: The module for automatically saving self-destructing mediat
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# scope: SDSaver
|
||||
# scope: SDSaver 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
import aiogram
|
||||
import telethon
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SDSaverMod(loader.Module):
|
||||
"""The module for automatically saving self-destructing media"""
|
||||
|
||||
strings = {
|
||||
"name": "SDSaver",
|
||||
"sdmode_on": "<emoji document_id=5769230088960741619>🔥</emoji> <b>Automatic saving self-destructing media is enabled</b>",
|
||||
"sdmode_off": "<emoji document_id=5769230088960741619>🔥</emoji> <b>Automatic saving self-destructing media is disabled</b>",
|
||||
"sd": '🔥 <b><a href="{link}">{name}</a> sent self-destructing media:</b>\n{caption}',
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Модуль для автоматического сохранения самоуничтожающихся медиа",
|
||||
"sdmode_on": "<emoji document_id=5769230088960741619>🔥</emoji> <b>Автоматическое сохранение самоуничтожающихся медиа включено</b>",
|
||||
"sdmode_off": "<emoji document_id=5769230088960741619>🔥</emoji> <b>Автоматическое сохранение самоуничтожающихся медиа выключено</b>",
|
||||
"sd": '🔥 <b><a href="{link}">{name}</a> отправил(а) самоуничтожающееся медиа:</b>\n{caption}',
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
|
||||
channel, _ = await utils.asset_channel(
|
||||
self._client,
|
||||
"heroku-sd",
|
||||
"Self-destruction media will appear there",
|
||||
invite_bot=True,
|
||||
avatar="https://i.pinimg.com/originals/6c/1e/cf/6c1ecf3afca663a9ebc0b18788b337ee.jpg",
|
||||
_folder="heroku",
|
||||
)
|
||||
self._channel = int(f"-100{channel.id}")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Включить/Выключить автоматическое сохранение самоуничтожающихся медиа",
|
||||
en_doc="Enable/Disable automatic saving self-destructing media",
|
||||
)
|
||||
async def sdmodecmd(self, message: telethon.types.Message):
|
||||
need_mode = not self.get("save_sd", True)
|
||||
self.set("save_sd", need_mode)
|
||||
await utils.answer(
|
||||
message, self.strings(f"sdmode_{'on' if need_mode else 'off'}")
|
||||
)
|
||||
|
||||
@loader.watcher("in", only_messages=True)
|
||||
async def watcher(self, message: telethon.types.Message):
|
||||
if (
|
||||
(not self.get("save_sd", True))
|
||||
or (not message.media)
|
||||
or (not getattr(message.media, "ttl_seconds", None))
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
sender = await self.client.get_entity(message.sender_id, exp=0)
|
||||
except Exception:
|
||||
sender = await message.get_sender()
|
||||
|
||||
media = await self.client.download_media(message.media, bytes)
|
||||
args = {
|
||||
"chat_id": self._channel,
|
||||
"caption": self.strings("sd").format(
|
||||
link=utils.get_entity_url(sender),
|
||||
name=utils.escape_html(telethon.utils.get_display_name(sender)),
|
||||
caption=message.text if message.text else "",
|
||||
),
|
||||
}
|
||||
if message.photo:
|
||||
args["photo"] = aiogram.types.BufferedInputFile(media, "sd.png")
|
||||
method = self.inline.bot.send_photo
|
||||
if message.video or message.video_note:
|
||||
args["video"] = aiogram.types.BufferedInputFile(media, "sd.mp4")
|
||||
method = self.inline.bot.send_video
|
||||
if message.voice:
|
||||
args["voice"] = aiogram.types.BufferedInputFile(media, "sd.ogg")
|
||||
method = self.inline.bot.send_voice
|
||||
|
||||
await method(**args)
|
||||
@@ -27,6 +27,7 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
from telethon.types import Message
|
||||
from telethon import events
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
@@ -79,6 +80,7 @@ class SilentTRMod(loader.Module):
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
self._me = await client.get_me()
|
||||
|
||||
self._global_settings = self._db.get(
|
||||
__name__, "global_settings", {"reactions": False, "tags": False}
|
||||
@@ -87,27 +89,22 @@ class SilentTRMod(loader.Module):
|
||||
self._global_ignore = self._db.get(__name__, "global_ignore", [])
|
||||
self._chat_ignore = self._db.get(__name__, "chat_ignore", {})
|
||||
|
||||
client.add_event_handler(self._on_message_reaction_updated)
|
||||
client.add_event_handler(self._on_new_message)
|
||||
client.add_event_handler(
|
||||
self._on_message_reaction_updated, events.MessageReactionsUpdated
|
||||
)
|
||||
client.add_event_handler(self._on_new_message, events.NewMessage)
|
||||
|
||||
async def on_unload(self):
|
||||
self._client.remove_event_handler(self._on_message_reaction_updated)
|
||||
self._client.remove_event_handler(self._on_new_message)
|
||||
|
||||
def _save_global_settings(self):
|
||||
def _save_settings(self):
|
||||
self._db.set(__name__, "global_settings", self._global_settings)
|
||||
|
||||
def _save_chat_settings(self):
|
||||
self._db.set(__name__, "chat_settings", self._chat_settings)
|
||||
|
||||
def _save_global_ignore(self):
|
||||
self._db.set(__name__, "global_ignore", self._global_ignore)
|
||||
|
||||
def _save_chat_ignore(self):
|
||||
self._db.set(__name__, "chat_ignore", self._chat_ignore)
|
||||
|
||||
async def _on_message_reaction_updated(self, event):
|
||||
"""Обработчик обновления реакций"""
|
||||
try:
|
||||
message = await self._client.get_messages(
|
||||
event.chat_id, ids=event.message_id
|
||||
@@ -115,226 +112,180 @@ class SilentTRMod(loader.Module):
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if message.sender_id != (await self._client.get_me()).id:
|
||||
if message.sender_id != self._me.id:
|
||||
return
|
||||
|
||||
chat_id = str(event.chat_id)
|
||||
user_id = event.user_id
|
||||
|
||||
if user_id in self._global_ignore:
|
||||
return
|
||||
|
||||
if chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]:
|
||||
if user_id in self._global_ignore or (
|
||||
chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]
|
||||
):
|
||||
return
|
||||
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
if chat_settings["reactions"] is None:
|
||||
if not self._global_settings["reactions"]:
|
||||
return
|
||||
else:
|
||||
if not chat_settings["reactions"]:
|
||||
return
|
||||
reactions_enabled = (
|
||||
chat_settings["reactions"]
|
||||
if chat_settings["reactions"] is not None
|
||||
else self._global_settings["reactions"]
|
||||
)
|
||||
|
||||
await self._client.read_messages(event.chat_id, event.message_id)
|
||||
if reactions_enabled:
|
||||
await self._client.read_messages(event.chat_id, event.message_id)
|
||||
|
||||
async def _on_new_message(self, event):
|
||||
"""Обработчик новых сообщений для упоминаний"""
|
||||
|
||||
if event.out:
|
||||
return
|
||||
|
||||
me = await self._client.get_me()
|
||||
mentioned = False
|
||||
if event.mentioned:
|
||||
mentioned = True
|
||||
else:
|
||||
if event.entities:
|
||||
for entity in event.entities:
|
||||
if entity.type == "mentionName" and entity.user_id == me.id:
|
||||
mentioned = True
|
||||
break
|
||||
elif entity.type == "textMention" and entity.user_id == me.id:
|
||||
mentioned = True
|
||||
break
|
||||
|
||||
if not mentioned:
|
||||
if event.out or not event.mentioned:
|
||||
return
|
||||
|
||||
chat_id = str(event.chat_id)
|
||||
user_id = event.sender_id
|
||||
|
||||
if user_id in self._global_ignore:
|
||||
return
|
||||
|
||||
if chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]:
|
||||
if user_id in self._global_ignore or (
|
||||
chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]
|
||||
):
|
||||
return
|
||||
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
if chat_settings["tags"] is None:
|
||||
if not self._global_settings["tags"]:
|
||||
tags_enabled = (
|
||||
chat_settings["tags"]
|
||||
if chat_settings["tags"] is not None
|
||||
else self._global_settings["tags"]
|
||||
)
|
||||
|
||||
if tags_enabled:
|
||||
await event.mark_read()
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие реакции во всех чатах",
|
||||
en_doc="[on/off] - silent reactions in all chats",
|
||||
)
|
||||
async def _toggle_setting(
|
||||
self, message: Message, setting_type: str, scope: str = "global"
|
||||
):
|
||||
args = utils.get_args_raw(message).lower()
|
||||
chat_id = str(message.chat_id) if scope == "chat" else None
|
||||
|
||||
if args not in ["on", "off", ""]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["args_error"]
|
||||
if scope == "global"
|
||||
else self.strings["chat_args_error"],
|
||||
)
|
||||
return
|
||||
|
||||
if scope == "global":
|
||||
if args == "on":
|
||||
self._global_settings[setting_type] = True
|
||||
elif args == "off":
|
||||
self._global_settings[setting_type] = False
|
||||
else:
|
||||
status = "on" if self._global_settings[setting_type] else "off"
|
||||
await utils.answer(message, f"Global silent {setting_type}: {status}")
|
||||
return
|
||||
else:
|
||||
if not chat_settings["tags"]:
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
if args == "on":
|
||||
chat_settings[setting_type] = True
|
||||
elif args == "off":
|
||||
chat_settings[setting_type] = False
|
||||
else:
|
||||
status = chat_settings[setting_type]
|
||||
if status is None:
|
||||
status = f"global ({'on' if self._global_settings[setting_type] else 'off'})"
|
||||
else:
|
||||
status = "on" if status else "off"
|
||||
await utils.answer(
|
||||
message, f"Silent {setting_type} in this chat: {status}"
|
||||
)
|
||||
return
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
|
||||
await event.mark_read()
|
||||
self._save_settings()
|
||||
status_key = "on" if args == "on" else "off"
|
||||
if scope == "global":
|
||||
key = f"global_{setting_type}_{status_key}"
|
||||
else:
|
||||
key = f"chat_{setting_type}_{status_key}"
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings.get(
|
||||
key,
|
||||
f"✅ {scope.title()} silent {setting_type} {'enabled' if args == 'on' else 'disabled'}",
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие реакции во всех чатах",
|
||||
en_doc="[on/off] - silent reactions in all chats",
|
||||
)
|
||||
async def sreacts(self, message: Message):
|
||||
"""Toggle global silent reactions"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
if args == "on":
|
||||
self._global_settings["reactions"] = True
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, self.strings["global_reactions_on"])
|
||||
elif args == "off":
|
||||
self._global_settings["reactions"] = False
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, self.strings["global_reactions_off"])
|
||||
elif args == "":
|
||||
status = "on" if self._global_settings["reactions"] else "off"
|
||||
await utils.answer(message, f"Global silent reactions: {status}")
|
||||
else:
|
||||
await utils.answer(message, self.strings["args_error"])
|
||||
await self._toggle_setting(message, "reactions", "global")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие упоминания во всех чатах",
|
||||
en_doc="[on/off] - silent tags in all chats",
|
||||
)
|
||||
async def stags(self, message: Message):
|
||||
"""Toggle global silent tags"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
if args == "on":
|
||||
self._global_settings["tags"] = True
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, self.strings["global_tags_on"])
|
||||
elif args == "off":
|
||||
self._global_settings["tags"] = False
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, self.strings["global_tags_off"])
|
||||
elif args == "":
|
||||
status = "on" if self._global_settings["tags"] else "off"
|
||||
await utils.answer(message, f"Global silent tags: {status}")
|
||||
else:
|
||||
await utils.answer(message, self.strings["args_error"])
|
||||
await self._toggle_setting(message, "tags", "global")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие реакции и упоминания во всех чатах",
|
||||
en_doc="[on/off] - silent reactions and tags in all chats",
|
||||
)
|
||||
async def sall(self, message: Message):
|
||||
"""Toggle global silent reactions and tags"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
if args == "on":
|
||||
self._global_settings["reactions"] = True
|
||||
self._global_settings["tags"] = True
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, "✅ Global silent reactions and tags enabled")
|
||||
elif args == "off":
|
||||
self._global_settings["reactions"] = False
|
||||
self._global_settings["tags"] = False
|
||||
self._save_global_settings()
|
||||
await utils.answer(message, "❌ Global silent reactions and tags disabled")
|
||||
elif args == "":
|
||||
status_r = "on" if self._global_settings["reactions"] else "off"
|
||||
status_t = "on" if self._global_settings["tags"] else "off"
|
||||
await utils.answer(
|
||||
message, f"Global silent reactions: {status_r}, tags: {status_t}"
|
||||
)
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, self.strings["args_error"])
|
||||
return
|
||||
|
||||
self._save_settings()
|
||||
await utils.answer(
|
||||
message,
|
||||
f"{'✅' if args == 'on' else '❌'} Global silent reactions and tags {'enabled' if args == 'on' else 'disabled'}",
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие реакции в этом чате",
|
||||
en_doc="[on/off] - silent reactions in this chat",
|
||||
)
|
||||
async def hsreacts(self, message: Message):
|
||||
"""Toggle silent reactions in this chat"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
chat_id = str(message.chat_id)
|
||||
|
||||
# Получаем настройки чата или создаем новые
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
|
||||
if args == "on":
|
||||
chat_settings["reactions"] = True
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(message, self.strings["chat_reactions_on"])
|
||||
elif args == "off":
|
||||
chat_settings["reactions"] = False
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(message, self.strings["chat_reactions_off"])
|
||||
elif args == "":
|
||||
status = chat_settings["reactions"]
|
||||
if status is None:
|
||||
status = (
|
||||
"global ("
|
||||
+ ("on" if self._global_settings["reactions"] else "off")
|
||||
+ ")"
|
||||
)
|
||||
else:
|
||||
status = "on" if status else "off"
|
||||
await utils.answer(message, f"Silent reactions in this chat: {status}")
|
||||
else:
|
||||
await utils.answer(message, self.strings["chat_args_error"])
|
||||
await self._toggle_setting(message, "reactions", "chat")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие упоминания в этом чате",
|
||||
en_doc="[on/off] - silent tags in this chat",
|
||||
)
|
||||
async def hstags(self, message: Message):
|
||||
"""Toggle silent tags in this chat"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
chat_id = str(message.chat_id)
|
||||
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
|
||||
if args == "on":
|
||||
chat_settings["tags"] = True
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(message, self.strings["chat_tags_on"])
|
||||
elif args == "off":
|
||||
chat_settings["tags"] = False
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(message, self.strings["chat_tags_off"])
|
||||
elif args == "":
|
||||
status = chat_settings["tags"]
|
||||
if status is None:
|
||||
status = (
|
||||
"global ("
|
||||
+ ("on" if self._global_settings["tags"] else "off")
|
||||
+ ")"
|
||||
)
|
||||
else:
|
||||
status = "on" if status else "off"
|
||||
await utils.answer(message, f"Silent tags in this chat: {status}")
|
||||
else:
|
||||
await utils.answer(message, self.strings["chat_args_error"])
|
||||
await self._toggle_setting(message, "tags", "chat")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[on/off] - тихие реакции и упоминания в этом чате",
|
||||
en_doc="[on/off] - silent reactions and tags in this chat",
|
||||
)
|
||||
async def hsall(self, message: Message):
|
||||
"""Toggle silent reactions and tags in this chat"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
chat_id = str(message.chat_id)
|
||||
|
||||
chat_settings = self._chat_settings.get(
|
||||
chat_id, {"reactions": None, "tags": None}
|
||||
)
|
||||
@@ -342,66 +293,58 @@ class SilentTRMod(loader.Module):
|
||||
if args == "on":
|
||||
chat_settings["reactions"] = True
|
||||
chat_settings["tags"] = True
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(
|
||||
message, "✅ Silent reactions and tags enabled in this chat"
|
||||
)
|
||||
elif args == "off":
|
||||
chat_settings["reactions"] = False
|
||||
chat_settings["tags"] = False
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_chat_settings()
|
||||
await utils.answer(
|
||||
message, "❌ Silent reactions and tags disabled in this chat"
|
||||
)
|
||||
elif args == "":
|
||||
status_r = chat_settings["reactions"]
|
||||
if status_r is None:
|
||||
status_r = (
|
||||
"global ("
|
||||
+ ("on" if self._global_settings["reactions"] else "off")
|
||||
+ ")"
|
||||
)
|
||||
else:
|
||||
status_r = "on" if status_r else "off"
|
||||
status_t = chat_settings["tags"]
|
||||
if status_t is None:
|
||||
status_t = (
|
||||
"global ("
|
||||
+ ("on" if self._global_settings["tags"] else "off")
|
||||
+ ")"
|
||||
)
|
||||
else:
|
||||
status_t = "on" if status_t else "off"
|
||||
|
||||
def format_status(status, setting_type):
|
||||
if status is None:
|
||||
return f"global ({'on' if self._global_settings[setting_type] else 'off'})"
|
||||
return "on" if status else "off"
|
||||
|
||||
status_r = format_status(status_r, "reactions")
|
||||
status_t = format_status(status_t, "tags")
|
||||
await utils.answer(
|
||||
message, f"Silent reactions: {status_r}, tags: {status_t} in this chat"
|
||||
)
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, self.strings["chat_args_error"])
|
||||
return
|
||||
|
||||
async def _get_user_id(self, message: Message):
|
||||
"""Получить ID пользователя из аргументов или ответа"""
|
||||
reply = await message.get_reply_message()
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if reply:
|
||||
return reply.sender_id
|
||||
elif args:
|
||||
try:
|
||||
user = await self._client.get_entity(args)
|
||||
return user.id
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
self._chat_settings[chat_id] = chat_settings
|
||||
self._save_settings()
|
||||
await utils.answer(
|
||||
message,
|
||||
f"{'✅' if args == 'on' else '❌'} Silent reactions and tags {'enabled' if args == 'on' else 'disabled'} in this chat",
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[ответ/username] - игнорировать пользователя глобально",
|
||||
en_doc="[reply/username] - ignore user globally",
|
||||
)
|
||||
async def _get_user_id(self, message: Message):
|
||||
reply = await message.get_reply_message()
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if reply:
|
||||
return reply.sender_id
|
||||
if args:
|
||||
try:
|
||||
user = await self._client.get_entity(args)
|
||||
return user.id
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[ответ/username] - игнорировать пользователя в этом чате",
|
||||
en_doc="[reply/username] - ignore user in this chat",
|
||||
)
|
||||
async def ignore(self, message: Message):
|
||||
"""Toggle ignore user globally"""
|
||||
user_id = await self._get_user_id(message)
|
||||
if not user_id:
|
||||
await utils.answer(message, self.strings["no_reply"])
|
||||
@@ -414,21 +357,18 @@ class SilentTRMod(loader.Module):
|
||||
self._global_ignore.append(user_id)
|
||||
await utils.answer(message, self.strings["ignore_added"])
|
||||
|
||||
self._save_global_ignore()
|
||||
self._save_settings()
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[ответ/username] - игнорировать пользователя в этом чате",
|
||||
en_doc="[reply/username] - ignore user in this chat",
|
||||
ru_doc="Показать статус Silent T&R", en_doc="Show Silent T&R status"
|
||||
)
|
||||
async def hignore(self, message: Message):
|
||||
"""Toggle ignore user in this chat"""
|
||||
user_id = await self._get_user_id(message)
|
||||
if not user_id:
|
||||
await utils.answer(message, self.strings["no_reply"])
|
||||
return
|
||||
|
||||
chat_id = str(message.chat_id)
|
||||
|
||||
if chat_id not in self._chat_ignore:
|
||||
self._chat_ignore[chat_id] = []
|
||||
|
||||
@@ -439,13 +379,12 @@ class SilentTRMod(loader.Module):
|
||||
self._chat_ignore[chat_id].append(user_id)
|
||||
await utils.answer(message, self.strings["hignore_added"])
|
||||
|
||||
self._save_chat_ignore()
|
||||
self._save_settings()
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Показать статус Silent T&R", en_doc="Show Silent T&R status"
|
||||
)
|
||||
async def strstatus(self, message: Message):
|
||||
"""Show status"""
|
||||
global_reactions = "on" if self._global_settings["reactions"] else "off"
|
||||
global_tags = "on" if self._global_settings["tags"] else "off"
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class TimeZoneMod(loader.Module):
|
||||
"""Prints current time in selected timezone (UTC+n and tzdata formats supported)"""
|
||||
@@ -43,7 +44,7 @@ class TimeZoneMod(loader.Module):
|
||||
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> There is no arguments or they are invalid",
|
||||
"_cls_doc": "Prints current time in selected timezone (UTC+n and tzdata formats supported)",
|
||||
"time_utc": "<emoji document_id=5276412364458059956>🕓</emoji> Current time by UTC+{}: {}",
|
||||
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Current time in {}: {}"
|
||||
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Current time in {}: {}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
@@ -51,7 +52,7 @@ class TimeZoneMod(loader.Module):
|
||||
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> Нет аргументов или они неверны",
|
||||
"tzdata_error": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка при получении времени по tzdata: {}\n\nУбедитесь, что часовой пояс указан верно",
|
||||
"time_utc": "<emoji document_id=5276412364458059956>🕓</emoji> Текущее время по UTC+{}: {}",
|
||||
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Текущее время в {}: {}"
|
||||
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Текущее время в {}: {}",
|
||||
}
|
||||
|
||||
@loader.command(
|
||||
@@ -61,12 +62,14 @@ class TimeZoneMod(loader.Module):
|
||||
async def utccmd(self, message):
|
||||
args = utils.get_args(message)
|
||||
if not args or not args[0].isdigit() or len(args) > 1:
|
||||
await utils.answer(message, self.strings['invalid_args'])
|
||||
return
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
return
|
||||
offset = timedelta(hours=int(args[0]))
|
||||
tz = timezone(offset)
|
||||
time = datetime.now(tz)
|
||||
await utils.answer(message, self.strings['time_utc'].format(args[0], time.strftime('%H:%M:%S')))
|
||||
await utils.answer(
|
||||
message, self.strings["time_utc"].format(args[0], time.strftime("%H:%M:%S"))
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Выводит время по часовому поясу tzdata | Использование: .tzdata Europe/Moscow",
|
||||
@@ -75,12 +78,15 @@ class TimeZoneMod(loader.Module):
|
||||
async def tzdatacmd(self, message):
|
||||
args = utils.get_args(message)
|
||||
if args[0].isdigit() or not args or len(args) > 1:
|
||||
await utils.answer(message, self.strings['invalid_args'])
|
||||
return
|
||||
try:
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
return
|
||||
try:
|
||||
time = datetime.now(ZoneInfo(args[0]))
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings['tzdata_error'].format(e))
|
||||
logger.error(self.strings['tzdata_error'].format(e))
|
||||
await utils.answer(message, self.strings["tzdata_error"].format(e))
|
||||
logger.error(self.strings["tzdata_error"].format(e))
|
||||
return
|
||||
await utils.answer(message, self.strings['time_tzdata'].format(args[0], time.strftime('%H:%M:%S')))
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["time_tzdata"].format(args[0], time.strftime("%H:%M:%S")),
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class YTDLMod(loader.Module):
|
||||
"""Downloads and sends audio/video from YouTube"""
|
||||
@@ -50,46 +51,47 @@ class YTDLMod(loader.Module):
|
||||
"name": "YTDL",
|
||||
"_cls_doc": "Downloads and sends audio/video from YouTube",
|
||||
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> There is no arguments or they are invalid",
|
||||
"downloading_video": "<emoji document_id=5215484787325676090>🕐</emoji> Video is downloading...",
|
||||
"downloaded_video": "<emoji document_id=5854762571659218443>✅</emoji> Video is downloaded!",
|
||||
"downloading_audio": "<emoji document_id=5215484787325676090>🕐</emoji> Audio is downloading...",
|
||||
"downloaded_audio": "<emoji document_id=5854762571659218443>✅</emoji> Audio is downloaded!",
|
||||
"downloading": "<emoji document_id=5215484787325676090>🕐</emoji> Downloading...",
|
||||
"done": "<emoji document_id=5854762571659218443>✅</emoji> Done!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Скачивает и отправляет аудио/видео с Ютуба",
|
||||
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> Нет аргументов или они неверны",
|
||||
"downloading_video": "<emoji document_id=5215484787325676090>🕐</emoji> Видео скачивается...",
|
||||
"downloaded_video": "<emoji document_id=5854762571659218443>✅</emoji> Видео загружено!",
|
||||
"downloading_audio": "<emoji document_id=5215484787325676090>🕐</emoji> Аудио скачивается...",
|
||||
"downloaded_audio": "<emoji document_id=5854762571659218443>✅</emoji> Аудио загружено!",
|
||||
"downloading": "<emoji document_id=5215484787325676090>🕐</emoji> Скачиваю...",
|
||||
"done": "<emoji document_id=5854762571659218443>✅</emoji> Готово!",
|
||||
}
|
||||
|
||||
|
||||
def _validate_url(self, url: str) -> bool:
|
||||
"""Validate URL format"""
|
||||
if not url:
|
||||
return False
|
||||
|
||||
url_pattern = re.compile(
|
||||
r'^(?:https?://)?(?:www\.|m\.)?(?:youtube\.com|youtu\.be|music\.youtube\.com)/(?:watch\?v=|playlist\?list=|channel/|@|live/|shorts/)?[\w-]+',
|
||||
re.IGNORECASE
|
||||
r"^(?:https?://)?(?:www\.|m\.)?(?:youtube\.com|youtu\.be|music\.youtube\.com)/(?:watch\?v=|playlist\?list=|channel/|@|live/|shorts/)?[\w-]+",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
return url_pattern.match(url) is not None
|
||||
|
||||
async def get_target(self):
|
||||
system = platform.system()
|
||||
machine = platform.machine().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "Windows":
|
||||
return "Windows"
|
||||
|
||||
|
||||
if system == "Darwin":
|
||||
return "aarch64-apple-darwin" if machine == "arm64" else "x86_64-apple-darwin"
|
||||
|
||||
return (
|
||||
"aarch64-apple-darwin" if machine == "arm64" else "x86_64-apple-darwin"
|
||||
)
|
||||
|
||||
if system == "Linux":
|
||||
return "aarch64-unknown-linux-gnu" if machine in ("aarch64", "arm64") else "x86_64-unknown-linux-gnu"
|
||||
return (
|
||||
"aarch64-unknown-linux-gnu"
|
||||
if machine in ("aarch64", "arm64")
|
||||
else "x86_64-unknown-linux-gnu"
|
||||
)
|
||||
|
||||
return "x86_64-unknown-linux-gnu"
|
||||
|
||||
@@ -106,138 +108,131 @@ class YTDLMod(loader.Module):
|
||||
async def client_ready(self, client, db):
|
||||
deno_path = Path("deno")
|
||||
deno_which = shutil.which("deno")
|
||||
|
||||
# Trying to fix previous shitcode...
|
||||
if self.get("deno_source") == "file":
|
||||
self.set("deno_source", str(deno_path.resolve()))
|
||||
|
||||
if not deno_which and not deno_path.is_file():
|
||||
logger.warning("Deno is not installed, attempting installation...")
|
||||
target = await self.get_target()
|
||||
if target == "Windows":
|
||||
logger.critical("Windows platform is unsupported by this module. All future commands will fail. Please, unload the module.")
|
||||
return
|
||||
logger.critical(
|
||||
"Windows platform is unsupported by this module. All future commands will fail. Please, unload the module."
|
||||
)
|
||||
return
|
||||
async with aiohttp.ClientSession() as session:
|
||||
download_link = f"https://github.com/denoland/deno/releases/latest/download/deno-{target}.zip"
|
||||
async with session.get(download_link) as resp:
|
||||
if resp.status == 200:
|
||||
async with aiofiles.open("deno.zip", mode='wb') as f:
|
||||
async for chunk in resp.content.iter_chunked(8192):
|
||||
await f.write(chunk)
|
||||
async with aiofiles.open("deno.zip", mode="wb") as f:
|
||||
async for chunk in resp.content.iter_chunked(8192):
|
||||
await f.write(chunk)
|
||||
else:
|
||||
logger.critical(f"Failed to download Deno: HTTP {resp.status}")
|
||||
self.set("deno_source", "install_failed")
|
||||
return
|
||||
if Path("deno.zip").is_file():
|
||||
with zipfile.ZipFile("deno.zip","r") as zip_ref:
|
||||
with zipfile.ZipFile("deno.zip", "r") as zip_ref:
|
||||
zip_ref.extractall()
|
||||
os.remove("deno.zip")
|
||||
os.chmod(deno_path, 0o755)
|
||||
self.set("deno_source", "file")
|
||||
self.set("deno_source", str(deno_path.resolve()))
|
||||
elif deno_which:
|
||||
self.set("deno_source", deno_which)
|
||||
|
||||
@loader.command(
|
||||
en_doc="Download video",
|
||||
ru_doc="Скачать видео"
|
||||
)
|
||||
|
||||
@loader.command(en_doc="Download video", ru_doc="Скачать видео")
|
||||
async def ytdlvcmd(self, message):
|
||||
args = utils.get_args(message)
|
||||
if not args or not self._validate_url(args[0]) or len(args) > 1:
|
||||
await utils.answer(message, self.strings['invalid_args'])
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
return
|
||||
|
||||
|
||||
source = self.get('deno_source')
|
||||
deno_path = Path('deno').resolve() if source == 'file' else source if source != 'install_failed' else None
|
||||
if not deno_path:
|
||||
logger.critical("Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot.")
|
||||
source = self.get("deno_source")
|
||||
if source == "install_failed" or not Path(source).is_file():
|
||||
logger.critical(
|
||||
"Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot."
|
||||
)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings['downloading_video'])
|
||||
|
||||
|
||||
await utils.answer(message, self.strings["downloading"])
|
||||
|
||||
filename_prefix = f"video_{message.id}"
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'outtmpl': f'{filename_prefix}.%(ext)s',
|
||||
'js_runtimes': {'deno': {'path': str(deno_path)}},
|
||||
|
||||
'extractor_args': {
|
||||
'youtube': {
|
||||
'player_client': ['mweb', 'android'],
|
||||
"quiet": True,
|
||||
"outtmpl": f"{filename_prefix}.%(ext)s",
|
||||
"js_runtimes": {"deno": {"path": source}},
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegVideoConvertor",
|
||||
"preferedformat": "mp4",
|
||||
}
|
||||
},
|
||||
|
||||
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
||||
|
||||
'postprocessors': [{
|
||||
'key': 'FFmpegVideoConvertor',
|
||||
'preferedformat': 'mp4',
|
||||
}],
|
||||
'postprocessor_args': {
|
||||
'video_convertor': [
|
||||
'-c:v', 'libx264',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
'-preset', 'veryfast',
|
||||
'-crf', '23',
|
||||
'-c:a', 'aac'
|
||||
],
|
||||
"postprocessor_args": {
|
||||
"video_convertor": [
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-preset",
|
||||
"veryfast",
|
||||
"-crf",
|
||||
"23",
|
||||
"-c:a",
|
||||
"aac",
|
||||
],
|
||||
'merger': [
|
||||
'-movflags', 'faststart'
|
||||
]
|
||||
"merger": ["-movflags", "faststart"],
|
||||
},
|
||||
}
|
||||
if self.get('youtube_cookie'):
|
||||
ydl_opts['cookiefile'] = self.get('youtube_cookie')
|
||||
if self.get("youtube_cookie"):
|
||||
ydl_opts["cookiefile"] = self.get("youtube_cookie")
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(args[0], download=True)
|
||||
filename = ydl.prepare_filename(info)
|
||||
await self.client.send_file(message.chat_id, filename, caption=self.strings['downloaded_video'])
|
||||
await message.delete()
|
||||
filename = ydl.prepare_filename(info).split(".")[0] + ".mp4"
|
||||
await utils.answer(message, self.strings['done'], file=filename, invert_media=True)
|
||||
os.remove(filename)
|
||||
|
||||
@loader.command(
|
||||
en_doc="Download audio",
|
||||
ru_doc="Скачать аудио"
|
||||
)
|
||||
@loader.command(en_doc="Download audio", ru_doc="Скачать аудио")
|
||||
async def ytdlacmd(self, message):
|
||||
args = utils.get_args(message)
|
||||
if not args or not self._validate_url(args[0]) or len(args) > 1:
|
||||
await utils.answer(message, self.strings['invalid_args'])
|
||||
await utils.answer(message, self.strings["invalid_args"])
|
||||
return
|
||||
|
||||
|
||||
source = self.get('deno_source')
|
||||
deno_path = Path('deno').resolve() if source == 'file' else source if source != 'install_failed' else None
|
||||
if not deno_path:
|
||||
logger.critical("Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot.")
|
||||
source = self.get("deno_source")
|
||||
if source == "install_failed" or not Path(source).is_file():
|
||||
logger.critical(
|
||||
"Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot."
|
||||
)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings['downloading_audio'])
|
||||
|
||||
|
||||
await utils.answer(message, self.strings["downloading"])
|
||||
|
||||
filename_prefix = f"audio_{message.id}"
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'outtmpl': f'{filename_prefix}.%(ext)s',
|
||||
'js_runtimes': {'deno': {'path': str(deno_path)}},
|
||||
|
||||
'postprocessors': [
|
||||
{
|
||||
'key': 'FFmpegExtractAudio',
|
||||
'preferredcodec': 'mp3',
|
||||
'preferredquality': '0',
|
||||
},
|
||||
{
|
||||
'key': 'FFmpegMetadata',
|
||||
'add_metadata': True,
|
||||
},
|
||||
{
|
||||
'key': 'EmbedThumbnail',
|
||||
},
|
||||
],
|
||||
'writethumbnail': True,
|
||||
"quiet": True,
|
||||
"outtmpl": f"{filename_prefix}.%(ext)s",
|
||||
"js_runtimes": {"deno": {"path": source}},
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": "0",
|
||||
},
|
||||
{
|
||||
"key": "FFmpegMetadata",
|
||||
"add_metadata": True,
|
||||
},
|
||||
{
|
||||
"key": "EmbedThumbnail",
|
||||
},
|
||||
],
|
||||
"writethumbnail": True,
|
||||
}
|
||||
if self.get('youtube_cookie'):
|
||||
ydl_opts['cookiefile'] = self.get('youtube_cookie')
|
||||
if self.get("youtube_cookie"):
|
||||
ydl_opts["cookiefile"] = self.get("youtube_cookie")
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(args[0], download=True)
|
||||
filename = ydl.prepare_filename(info).split(".")[0] + ".mp3"
|
||||
await self.client.send_file(message.chat_id, filename, caption=self.strings['downloaded_audio'])
|
||||
await message.delete()
|
||||
|
||||
|
||||
|
||||
await utils.answer(message, self.strings['done'], file=filename)
|
||||
os.remove(filename)
|
||||
|
||||
Reference in New Issue
Block a user