mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Commited backup
This commit is contained in:
8
anon97945/hikka-mods/.deepsource.toml
Normal file
8
anon97945/hikka-mods/.deepsource.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
130
anon97945/hikka-mods/.gitignore
vendored
Normal file
130
anon97945/hikka-mods/.gitignore
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
snippet.py
|
||||
500
anon97945/hikka-mods/CHANGELOG.md
Normal file
500
anon97945/hikka-mods/CHANGELOG.md
Normal file
@@ -0,0 +1,500 @@
|
||||
# 📝 Apodiktum Modules Changelog:
|
||||
|
||||
## 🆕 Version 2.0.64
|
||||
### 📃 module updates:
|
||||
- no_ttl | fixed for forums
|
||||
|
||||
## 🆕 Version 2.0.63
|
||||
### 📕 new modules:
|
||||
- no_ttl | Send messages without ttl by removing ttl before and set it again after.
|
||||
|
||||
## 🆕 Version 2.0.62
|
||||
### ℹ️ General:
|
||||
- updated addons copyright and media link
|
||||
|
||||
### 📃 module updates:
|
||||
- save_message | fixed for channels
|
||||
|
||||
## 🆕 Version 2.0.61
|
||||
### 📃 module updates:
|
||||
- linked_chat | added new module
|
||||
- quotes | added new module
|
||||
|
||||
## 🆕 Version 2.0.60
|
||||
### 📃 module updates:
|
||||
- admintools | fixed missing await
|
||||
|
||||
## 🆕 Version 2.0.59
|
||||
### 📃 module updates:
|
||||
- general | replaced get_entity with get_chat and get_sender
|
||||
|
||||
## 🆕 Version 2.0.58
|
||||
### 📃 module updates:
|
||||
- pmlog | mark log as read (opt-out)
|
||||
|
||||
## 🆕 Version 2.0.57
|
||||
### 📃 module updates:
|
||||
- mark_read | updated for forums
|
||||
|
||||
## 🆕 Version 2.0.56
|
||||
### 📃 module updates:
|
||||
- mark_read | wrong cfg string
|
||||
|
||||
## 🆕 Version 2.0.55
|
||||
### 📃 module updates:
|
||||
- mark_read | typo
|
||||
|
||||
## 🆕 Version 2.0.54
|
||||
### 📃 module updates:
|
||||
- _skeleton | rework q_watcher
|
||||
- mark_read | new module
|
||||
- msg_merger | changed default whitelist to false
|
||||
|
||||
## 🆕 Version 2.0.53
|
||||
### 📃 module updates:
|
||||
- apo_python | removed, library is adding itself to eval now
|
||||
- auto_delerror | changed validators
|
||||
|
||||
## 🆕 Version 2.0.52
|
||||
### 📃 module updates:
|
||||
- _skeleton | added q_watcher example
|
||||
- admintools | fix bnc, removed try except
|
||||
- msg_merger | removed try except
|
||||
- pmlog | removed try except
|
||||
|
||||
## 🆕 Version 2.0.51
|
||||
### 📃 module updates:
|
||||
- pmlog | create new topic if deleted
|
||||
|
||||
## 🆕 Version 2.0.50
|
||||
### 📃 module updates:
|
||||
- admintools | changed crash handler
|
||||
- msg_merger | changed crash handler
|
||||
- pmlog | added realtime_names for topics
|
||||
|
||||
## 🆕 Version 2.0.49
|
||||
### 📃 module updates:
|
||||
- pmlog | users are now logged now in seperate topics, fixed whielist
|
||||
|
||||
## 🆕 Version 2.0.48
|
||||
### 📃 module updates:
|
||||
- admintools | added some debug msgs and fixed restricted logger
|
||||
|
||||
## 🆕 Version 2.0.47
|
||||
### 📃 module updates:
|
||||
- all modules | removed stats collect
|
||||
|
||||
## 🆕 Version 2.0.46
|
||||
### 📃 module updates:
|
||||
- apolib_controller | changed apodiktum_library url to master
|
||||
|
||||
## 🆕 Version 2.0.45
|
||||
### 📃 module updates:
|
||||
- auto_delerror | fix module
|
||||
|
||||
## 🆕 Version 2.0.44
|
||||
### 📃 module updates:
|
||||
- apolib_controller | typo
|
||||
|
||||
## 🆕 Version 2.0.43
|
||||
### 📃 module updates:
|
||||
- auto_delerror | added new module
|
||||
|
||||
## 🆕 Version 2.0.42
|
||||
### 📃 module updates:
|
||||
- apolib_controller | added unloadapocontroller
|
||||
|
||||
## 🆕 Version 2.0.41
|
||||
### 📃 module updates:
|
||||
- msg_merger | added try except q_watcher
|
||||
- pmlog | added try except q_watcher
|
||||
|
||||
## 🆕 Version 2.0.40
|
||||
### 📃 module updates:
|
||||
- autoreact | Added all emoji reactions to the regex
|
||||
|
||||
## 🆕 Version 2.0.39
|
||||
### 📃 module updates:
|
||||
- admintools | removed get_entity(x.sender_id)
|
||||
|
||||
## 🆕 Version 2.0.38
|
||||
### 📃 module updates:
|
||||
- admintools | fix bnd for non-creators
|
||||
|
||||
## 🆕 Version 2.0.37
|
||||
### 📃 module updates:
|
||||
- admintools | fix admin tags whitelist
|
||||
|
||||
## 🆕 Version 2.0.36
|
||||
### 📃 module updates:
|
||||
- dnd | fixed afk reply
|
||||
|
||||
## 🆕 Version 2.0.35
|
||||
### 📃 module updates:
|
||||
- admintools | fixed logger message
|
||||
|
||||
## 🆕 Version 2.0.35
|
||||
### 📃 module updates:
|
||||
- apolib_controller | code improvement
|
||||
- msg_merger | fix for reply merge bug
|
||||
|
||||
## 🆕 Version 2.0.34
|
||||
### 📃 module updates:
|
||||
- admintools | fixed crash of queue
|
||||
- dnd | fixed afk reply
|
||||
- msg_merger | fix attempt for threads
|
||||
|
||||
## 🆕 Version 2.0.33
|
||||
### 📕 new modules:
|
||||
- auto_delerror | Remove inline bot error messages in log channel including defined phrases
|
||||
|
||||
### 📃 module updates:
|
||||
- admintools | added `.bnc` -> BlockNonComment will block all "non comment" messages.
|
||||
- dnd | fixed `.denypm <id>`
|
||||
- langreplier | fixed watcher
|
||||
- msg_merger | fix attempt for threads
|
||||
- pypng | fixed reply to file
|
||||
- voicetools | fixed watcher
|
||||
|
||||
|
||||
## 🆕 Version 2.0.32
|
||||
### 📃 module updates:
|
||||
- msg_merger | new config var `skip_reactions`
|
||||
|
||||
## 🆕 Version 2.0.31
|
||||
### 📃 module updates:
|
||||
- dnd | fix `custom emoji bio`
|
||||
|
||||
## 🆕 Version 2.0.30
|
||||
### 📃 module updates:
|
||||
- dnd | now supports `custom emojis`
|
||||
|
||||
## 🆕 Version 2.0.29
|
||||
### 📃 module updates:
|
||||
- dnd | fix `duration` display
|
||||
|
||||
## 🆕 Version 2.0.28
|
||||
### 📃 module updates:
|
||||
- msg_merger | added `.merge` to merge manual
|
||||
- msg_merger | reduced `get_messages` requests
|
||||
|
||||
## 🆕 Version 2.0.27
|
||||
### 📃 module updates:
|
||||
- dnd | added multitime string support e.g. `7h30m`
|
||||
- dnd | changed command `report` to `reportpm`
|
||||
|
||||
## 🆕 Version 2.0.26
|
||||
### 📃 module updates:
|
||||
- apolib_controller | added `qapolib` to see the current q_watcher queue and tasks
|
||||
|
||||
## 🆕 Version 2.0.25
|
||||
### 📃 module updates:
|
||||
- admintools | using `apo_lib.watcher_q`
|
||||
- apo_python | updated to upstream
|
||||
- pmlog | using `apo_lib.watcher_q`
|
||||
|
||||
## 🆕 Version 2.0.24
|
||||
### 📃 module updates:
|
||||
- msg_merger | using `q_watcher`
|
||||
|
||||
## 🆕 Version 2.0.23
|
||||
### 📃 module updates:
|
||||
- admintools | fix `whitelist` doc and `behavior`
|
||||
- dnd | fix `whitelist` doc and `behavior`
|
||||
|
||||
## 🆕 Version 2.0.22
|
||||
### 📃 module updates:
|
||||
- admintools | fixed `BlockDoubleLink`
|
||||
- admintools | fixed `TypeError: '<' not supported between instances of 'list' and 'float'`
|
||||
- msg_merger | `skip_prefix` work in messages with links
|
||||
- msg_merger | ignore messages with `prefix`
|
||||
|
||||
## 🆕 Version 2.0.21
|
||||
### 📃 module updates:
|
||||
- admintools | added `BlockCustomEmojis`
|
||||
- admintools | rework of `punish handler`, now also supports notify for each
|
||||
- dnd | now can black/whitelist chats for afk response. check `config`
|
||||
- dnd | now supports `custom emojis`
|
||||
- dnd | now supports `premium` further `bio length`
|
||||
- dnd | removed config `afk_no_group` use `afk_group_list` and `afk_tag_whitelist`
|
||||
|
||||
## 🆕 Version 2.0.20
|
||||
### 📃 module updates:
|
||||
- auto_update | fix watcher
|
||||
|
||||
## 🆕 Version 2.0.19
|
||||
### 📃 module updates:
|
||||
- dnd | now also supports further in `bio`
|
||||
|
||||
## 🆕 Version 2.0.18
|
||||
### 📃 module updates:
|
||||
- apolib_controller | added `.vapolib` to get the version of the last loaded apodiktum_library
|
||||
- dnd | added `use_bio` config (`Default: True`). This will set the afk message also as bio and will replace it with the old bio after `unstatus`
|
||||
|
||||
## 🆕 Version 2.0.17
|
||||
### 📃 module updates:
|
||||
- admintools | added `.bf` (blockflood) which will count messages of a user until limit (this is still `beta`)
|
||||
- admintools | anonymous chat admin will be ignored
|
||||
- admintools | changed `int` in db to `str`. json cant use `int keys`
|
||||
- admintools | next update may have a command rework, also punishment rework
|
||||
|
||||
## 🆕 Version 2.0.16
|
||||
### 📃 module updates:
|
||||
- admintools | added `GetFullChannelRequest` cache
|
||||
- admintools | added `BlockGifSpam` which will only accept one sticker per x seconds per user
|
||||
|
||||
## 🆕 Version 2.0.15
|
||||
### 📃 module updates:
|
||||
- admintools | added `BlockDoubleLinks` which will block each duplicated link for x seconds
|
||||
- admintools | added `BlockStickerSpam` which will only accept one sticker per x seconds per user
|
||||
- admintools | added `get_permissions` cache
|
||||
- admintools | added ratelimit to notify messages
|
||||
- admintools | reduced api requests
|
||||
|
||||
## 🆕 Version 2.0.14
|
||||
### 📃 module updates:
|
||||
- admintools | send msg as reply if possible
|
||||
|
||||
## 🆕 Version 2.0.13
|
||||
#### General:
|
||||
- scope: hikka_min 1.3.3
|
||||
- refactored code for 1.3.0
|
||||
- renamed modules to `Apo-Modulename`
|
||||
|
||||
### 📃 module updates:
|
||||
- admintools | can now be deactivated in a chat even without admin perms
|
||||
- purge | fixed apurge, spurge for private chats
|
||||
|
||||
## 🆕 Version 2.0.12
|
||||
### 📃 module updates:
|
||||
- dnd | reduced api requests
|
||||
|
||||
## 🆕 Version 2.0.11
|
||||
### 📃 module updates:
|
||||
- admintools | delete messages via bot
|
||||
|
||||
## 🆕 Version 2.0.10
|
||||
### 📃 module updates:
|
||||
- admintools | send messages via bot
|
||||
|
||||
## 🆕 Version 2.0.9
|
||||
### 📃 module updates:
|
||||
- auto_update | added skip message
|
||||
- auto_update | multilang support
|
||||
|
||||
## 🆕 Version 2.0.8
|
||||
### 📃 module updates:
|
||||
- admintools | added debug message for global_queue_handler
|
||||
|
||||
## 🆕 Version 2.0.7
|
||||
#### General:
|
||||
- changed copyright banner
|
||||
|
||||
## 🆕 Version 2.0.6
|
||||
### 📃 module updates:
|
||||
- dnd | added optional further informations
|
||||
|
||||
## 🆕 Version 2.0.5
|
||||
#### General:
|
||||
- black formatting
|
||||
|
||||
### 📃 module updates:
|
||||
- admintools | `admin_tags` is not case insensetive
|
||||
- admintools | added message link to bot message
|
||||
- admintools | added `whitelist` config for admin_tag
|
||||
- admintools | added `ignore_admins` config to ignore `admin tags` of admins
|
||||
- dnd | hopefully fixed entity error
|
||||
|
||||
## 🆕 Version 2.0.4
|
||||
### 📃 module updates:
|
||||
- admintools | added ability to add custom admin tags in config such as `/report`, `.report`, etc.
|
||||
|
||||
## 🆕 Version 2.0.3
|
||||
### 📃 module updates:
|
||||
- admintools | added `@admin` tag for chats (check config)
|
||||
- autoreact | added `ignore_self` config
|
||||
- dnd | automatically removes status after given time, improved bot message
|
||||
|
||||
## 🆕 Version 2.0.2
|
||||
### 📃 module updates:
|
||||
- apoinfo | fixed apoinfo `{upd}`
|
||||
|
||||
## 🆕 Version 2.0.1
|
||||
### 📃 module updates:
|
||||
- dnd | fixed is_linkedchannel
|
||||
|
||||
## 🆕 Version 2.0.0
|
||||
### 📦 apodiktum_library:
|
||||
#### General:
|
||||
- Switched Library to his own repo -> https://github.com/anon97945/hikka-libs
|
||||
- Library now supports scope for requirements, not need to add unnecessary imports into the module
|
||||
|
||||
### 📃 module updates:
|
||||
- all | dropped imports and req scope of emoji
|
||||
|
||||
## 🆕 Version 1.0.6
|
||||
### ℹ️ General:
|
||||
- reformatted modules with black
|
||||
|
||||
### 📦 apodiktum_library:
|
||||
#### Utils:
|
||||
- added get_buttons
|
||||
|
||||
### 📃 module updates:
|
||||
- dnd | fixed .status without optional time
|
||||
|
||||
## 🆕 Version 1.0.5
|
||||
### ℹ️ General:
|
||||
- added # meta banner and # meta pic
|
||||
|
||||
### 📦 apodiktum_library:
|
||||
#### Utils:
|
||||
- renamed get_buttons_as_dict to get_buttons
|
||||
|
||||
### 📃 module updates:
|
||||
- all | removed migrator log configs from modules, they are now in the lib config
|
||||
- apo_migrator_class | removed, is now implemented in library
|
||||
- dnd | .status now remove reply messages if it was already .status
|
||||
- msg_merger | fixed multiple errors, fixed unmerge command
|
||||
|
||||
## 🆕 Version 1.0.4
|
||||
### 📦 apodiktum_library:
|
||||
#### Utils:
|
||||
- library utils beta | added get_buttons_as_dict
|
||||
|
||||
## 🆕 Version 1.0.3
|
||||
### 📦 apodiktum_library:
|
||||
#### Utils:
|
||||
- added get_user_id
|
||||
- added validate_bool
|
||||
- added validate_datetime
|
||||
- added validate_dict
|
||||
- added validate_email
|
||||
- added validate_float
|
||||
- added validate_integer
|
||||
- added validate_list
|
||||
- added validate_none
|
||||
- added validate_regex
|
||||
- added validate_string
|
||||
- added validate_tgid
|
||||
- added validate_tuple
|
||||
- removed get_attrs
|
||||
- removed get_sub
|
||||
|
||||
## 🆕 Version 1.0.2
|
||||
### 📦 apodiktum_library:
|
||||
#### General:
|
||||
- deepsource fix
|
||||
- fixed logger
|
||||
|
||||
#### Utils:
|
||||
- fixed keyerror in get_str() when db has no `hikka.translations`
|
||||
- added get_all_urls
|
||||
|
||||
### 📃 module updates:
|
||||
- autoreact | fixed `local variable 'emoji_list' referenced before assignment`
|
||||
|
||||
## 🆕 Version 1.0.2
|
||||
### 📦 apodiktum_library:
|
||||
#### Utils:
|
||||
- added get_uptime
|
||||
- added tdstring_to_seconds
|
||||
- added time_formatter(short=True)
|
||||
|
||||
### 📃 module updates:
|
||||
- all | added emoji requirement scope to ensure lib can load
|
||||
- admintools | fixed get_tag not awaited
|
||||
- apoinfo | changed get_uptime to apo_lib instead of hikka native
|
||||
|
||||
## 🆕 Version 1.0.1
|
||||
### 📦 apodiktum_library:
|
||||
#### General:
|
||||
- ControllerLoader Log set as debug_msg
|
||||
- fixed config
|
||||
- reworked hikka anonymous stats
|
||||
- reworked logger
|
||||
|
||||
#### Utils:
|
||||
- added humanbytes
|
||||
- added time_formatter
|
||||
|
||||
## 🆕 Version 1.0.0
|
||||
### ℹ️ General:
|
||||
- scope: hikka_min 1.2.11
|
||||
- deepsource fixes
|
||||
|
||||
### 📦 apodiktum_library:
|
||||
#### General:
|
||||
- added beta_id list
|
||||
- added full docstring to library
|
||||
- added hikka anonymous stats
|
||||
- added hikka min version scope to library
|
||||
- added translation strings
|
||||
- added utils_beta for testing
|
||||
- changed to self.apo_lib.utils
|
||||
- edited library to use hikka native libs
|
||||
- get_str rework. forcelang > setlang > basestring
|
||||
- implemented migrator class
|
||||
- library sideloads apolib_controller.py
|
||||
- logger rework
|
||||
- new beta utils
|
||||
- new internal class
|
||||
- using different classes
|
||||
|
||||
#### Utils:
|
||||
- added convert_time
|
||||
- added distinct_emoji_list
|
||||
- added emoji_list
|
||||
- added escape_html
|
||||
- added get_attrs
|
||||
- added get_entityurls
|
||||
- added get_href_urls
|
||||
- added get_ids_from_tglink
|
||||
- added get_invite_link
|
||||
- added get_str
|
||||
- added get_sub
|
||||
- added get_tag
|
||||
- added get_tag_link
|
||||
- added get_urls
|
||||
- added is_emoji
|
||||
- added is_linkedchannel
|
||||
- added is_member
|
||||
- added log
|
||||
- added rem_duplicates_list
|
||||
- added rem_emoji
|
||||
- added unescape_html
|
||||
|
||||
### 📕 new modules:
|
||||
- apo_python.py
|
||||
- apolib_controller.py
|
||||
|
||||
### 📁 new files:
|
||||
- apodiktum_library.py
|
||||
|
||||
### 📃 module updates:
|
||||
- all | changed self.strings to support new library
|
||||
- all | dropped fast_download
|
||||
- all | removed migrator class, will be build into library
|
||||
- all | use my apodiktum_library.py
|
||||
- apoinfo | changed default msg time to `code`
|
||||
- apoinfo | update for uptime
|
||||
- dnd | added self expiring afk messages
|
||||
- dnd | changed reason banned to blocked.
|
||||
- langreplier | added auto translation
|
||||
- langreplier | fix requirements of langreplier
|
||||
- langreplier | ignore mathematical as alphabet
|
||||
- langreplier | replace `cyrillic` with `vodka` in respond message of alphabet (optional)
|
||||
- lcr | check for digit count instead of message content to support different languages.
|
||||
- msg_merger | add ignore prefix to ignore the message fully
|
||||
- msg_merger | added new is_emoji skip
|
||||
- msg_merger | added reverse_merge to msg_merger to merge into newest
|
||||
- msg_merger | added unmerge cmd
|
||||
- msg_merger | delete reply message if its from self
|
||||
- msg_merger | dont merge into ignored prefix
|
||||
- msg_merger | fix ignores time on merge_own_reply `false`
|
||||
- msg_merger | force link_preview `True`/`False` or decide automatically if set to `None` (config bool)
|
||||
- msg_merger | merge urls `True`/`False` (config bool))
|
||||
- msg_merger | some bug fixes in msg_merger#
|
||||
- msg_merger | try / except get_messages
|
||||
- show_viewer | fix .sv args
|
||||
- show_viewer | fix send as reply
|
||||
674
anon97945/hikka-mods/LICENSE
Normal file
674
anon97945/hikka-mods/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
212
anon97945/hikka-mods/_skeleton.py
Normal file
212
anon97945/hikka-mods/_skeleton.py
Normal file
@@ -0,0 +1,212 @@
|
||||
__version__ = (0, 0, 21)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumSkeletonMod(loader.Module):
|
||||
"""
|
||||
This is a skeleton module.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-Skeleton",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_skel_bool": "This is a skeleton boolean config.",
|
||||
"_cfg_skel_series": "This is a skeleton series config.",
|
||||
"_cfg_skel_union": "This is a skeleton union config.",
|
||||
"_cfg_translation_chats": "Define Chats where the translation is forced.",
|
||||
"_cmd_skeleton": "This is a skeleton command.",
|
||||
"no_args": "No args are given...",
|
||||
"no_int": "Your input was no Integer.",
|
||||
"skeleton_argmsg": "This is a skeleton message with args.\n{}",
|
||||
"skeleton_msg": "This is a skeleton message.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_skel_bool": "Dies ist ein Skeleton Boolean Config.",
|
||||
"_cfg_skel_series": "Dies ist ein Skeleton Series Config.",
|
||||
"_cfg_skel_union": "Dies ist ein Skeleton Union Config.",
|
||||
"_cfg_translation_chats": "Definiere Chats, wo die Übersetzung erzwungen wird.",
|
||||
"_cmd_doc_cskeleton": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
"_cmd_doc_skeleton": "Dies ist ein Skeleton Command.",
|
||||
"_cmd_doc_skeletonargs": "Dies ist ein Skeleton Command mit Argumenten.",
|
||||
"no_args": "Keine Argumente angegeben...",
|
||||
"no_int": "Dein Eingabe war keine Integer.",
|
||||
"skeleton_argmsg": "Dies ist ein Skeleton Nachricht mit Argumenten.\n{}",
|
||||
"skeleton_msg": "Dies ist ein Skeleton Nachricht.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_skel_bool": "Это скелетная булевая конфигурация.",
|
||||
"_cfg_skel_series": "Это скелетная конфигурация с сериями.",
|
||||
"_cfg_skel_union": "Это скелетная конфигурация с объединением.",
|
||||
"_cfg_translation_chats": "Задать чаты, где применяется перевод.",
|
||||
"_cls_doc": "Это скелетный модуль.",
|
||||
"_cmd_doc_cskeleton": "Это откроет конфиг для модуля.",
|
||||
"_cmd_doc_skeleton": "Это скелетная команда.",
|
||||
"_cmd_doc_skeletonargs": "Это скелетное сообщение с аргументами.",
|
||||
"no_args": "ргументы не указаны...",
|
||||
"no_int": "Ваш ввод не является целочисленным типом (int)",
|
||||
"skeleton_argmsg": "Это команда с аргументами.\n{}",
|
||||
"skeleton_msg": "Это скелетное сообщение.",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo Skeleton",
|
||||
"new": "Apo-Skeleton",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"skeleton_bool",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_skel_bool"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skeleton_union",
|
||||
"None",
|
||||
doc=lambda: self.strings("_cfg_skel_union"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Float(minimum=0, maximum=600),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skeleton_series",
|
||||
[123, 456, 789],
|
||||
doc=lambda: self.strings("_cfg_skel_series"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.TelegramID(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClas
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
self.apo_lib.watcher_q.register(
|
||||
self.__class__.__name__, "q_watcher1"
|
||||
) # Register the q_watcher1; name is optional if you only have one watcher (q_watcher is the default name)
|
||||
|
||||
async def on_unload(self):
|
||||
self.apo_lib.watcher_q.unregister(
|
||||
self.__class__.__name__, "q_watcher1"
|
||||
) # Unregister the q_watcher1; name is necessary if you registered the watcher with a name. Must match the name you registered the watcher with
|
||||
return
|
||||
|
||||
async def on_dlmod(self, client, _):
|
||||
return
|
||||
|
||||
async def cskeletoncmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def skeletoncmd(self, message):
|
||||
"""
|
||||
This is a skeleton command.
|
||||
"""
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("skeleton_msg", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
async def skeletoargscmd(self, message):
|
||||
"""
|
||||
This is a skeleton command with args.
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_args", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if not self.apo_lib.utils.validate_integer(args[0]):
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_int", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"skeleton_argmsg", self.all_strings, message
|
||||
).format(args),
|
||||
)
|
||||
|
||||
async def q_watcher1(self, message: Message):
|
||||
"""
|
||||
This is the watcher function. Name must be the same as the one you registered the q_watcher with
|
||||
"""
|
||||
return
|
||||
|
||||
async def watcher(self, message):
|
||||
"""
|
||||
This is a watcher.
|
||||
"""
|
||||
return
|
||||
2119
anon97945/hikka-mods/admintools.py
Normal file
2119
anon97945/hikka-mods/admintools.py
Normal file
File diff suppressed because it is too large
Load Diff
446
anon97945/hikka-mods/apoinfo.py
Normal file
446
anon97945/hikka-mods/apoinfo.py
Normal file
@@ -0,0 +1,446 @@
|
||||
__version__ = (0, 1, 24)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: inline
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
import git
|
||||
from telethon.tl.types import Message
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from .. import loader, main, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumInfoMod(loader.Module):
|
||||
"""
|
||||
Show userbot info
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-Info",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_banner": "Set `True` in order to disable an media banner.",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_cst_bnr": "Custom Banner.",
|
||||
"_cfg_cst_btn": "Custom button. Leave empty to remove button.",
|
||||
"_cfg_cst_frmt": "Custom fileformat for Banner.",
|
||||
"_cfg_cst_msg": (
|
||||
"Custom message for info. May contain {me}, {version}, {build},"
|
||||
" {prefix}, {platform}, {upd}, {uptime} keywords."
|
||||
),
|
||||
"_cfg_inline_banner": "Set `True` in order to disable an inline media banner.",
|
||||
"build": "Build",
|
||||
"description": "ℹ This will not compromise any sensitive info.",
|
||||
"owner": "Owner",
|
||||
"prefix": "Prefix",
|
||||
"send_info": "Send userbot info.",
|
||||
"up-to-date": "😌 Up-to-date.",
|
||||
"update_required": "😕 Update required: <code>{}update</code>",
|
||||
"uptime": "Uptime",
|
||||
"version": "Version",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_banner": "Setzen Sie `True`, um das Media Banner zu deaktivieren.",
|
||||
"_cfg_cst_bnr": "Benutzerdefiniertes Banner.",
|
||||
"_cfg_cst_btn": (
|
||||
"Benutzerdefinierte Schaltfläche für Informationen. Leer lassen, um"
|
||||
" die Schaltfläche zu entfernen."
|
||||
),
|
||||
"_cfg_cst_frmt": "Benutzerdefiniertes Dateiformat für das Banner.",
|
||||
"_cfg_cst_msg": (
|
||||
"Benutzerdefinierte Nachricht für Info. Kann die Schlüsselwörter"
|
||||
" {me}, {version}, {build}, {prefix}, {platform}, {upd}, {uptime}"
|
||||
" enthalten."
|
||||
),
|
||||
"_cfg_inline_banner": (
|
||||
"Setzen Sie `True`, um das Inline Media Banner zu deaktivieren."
|
||||
),
|
||||
"_cmd_doc_capoinfo": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
"_ihandle_doc_info": "Отправить информацию о юзерботе",
|
||||
"build": "Build",
|
||||
"description": "ℹ Dadurch werden keine sensiblen Daten gefährdet.",
|
||||
"owner": "Eigentümer",
|
||||
"prefix": "Prefix",
|
||||
"send_info": "Benutzerbot-Informationen senden.",
|
||||
"up-to-date": "😌 Up-to-date",
|
||||
"update_required": "😕 Aktualisierung erforderlich: <code>{}update</code>",
|
||||
"uptime": "Betriebszeit",
|
||||
"version": "Version",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_banner": "Поставь `True`, чтобы отключить баннер-картинку.",
|
||||
"_cfg_cst_bnr": "Кастомный баннер.",
|
||||
"_cfg_cst_btn": (
|
||||
"Кастомная кнопка в сообщении в info. Оставь пустым, чтобы убрать кнопку."
|
||||
),
|
||||
"_cfg_cst_frmt": "Кастомный формат файла для баннера.",
|
||||
"_cfg_cst_msg": (
|
||||
"Кастомный текст сообщения в info. Может содержать ключевые слова"
|
||||
" {me}, {version}, {build}, {prefix}, {platform}, {upd}, {uptime}."
|
||||
),
|
||||
"_cfg_inline_banner": (
|
||||
"Установите `True`, чтобы отключить встроенный медиа-баннер"
|
||||
),
|
||||
"_cmd_doc_capoinfo": "Это откроет конфиг для модуля.",
|
||||
"_ihandle_doc_info": "Отправить информацию о юзерботе.",
|
||||
"build": "Сборка",
|
||||
"description": "ℹ Это не раскроет никакой личной информации.",
|
||||
"owner": "Владелец",
|
||||
"prefix": "Префикс",
|
||||
"send_info": "Отправить информацию о юзерботе.",
|
||||
"up-to-date": "😌 Актуальная версия.",
|
||||
"update_required": "😕 Требуется обновление: <code>{}update</code>",
|
||||
"uptime": "Аптайм",
|
||||
"version": "Версия",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo Info",
|
||||
"new": "Apo-Info",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"custom_banner",
|
||||
"https://t.me/apodiktum_dumpster/6",
|
||||
lambda: self.strings("_cfg_cst_bnr"),
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_format",
|
||||
"video",
|
||||
lambda: self.strings("_cfg_cst_frmt"),
|
||||
validator=loader.validators.Choice(["photo", "video", "audio", "gif"]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_message",
|
||||
None,
|
||||
doc=lambda: self.strings("_cfg_cst_msg"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.String(),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"disable_banner",
|
||||
False,
|
||||
lambda: self.strings("_cfg_banner"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"disable_inline_banner",
|
||||
False,
|
||||
lambda: self.strings("_cfg_inline_banner"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button1",
|
||||
[
|
||||
"🔥 Apodiktum Hikka Modules 🔥",
|
||||
"https://t.me/apodiktum_modules",
|
||||
],
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button2",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button3",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button4",
|
||||
["🌘 Hikka EN Support chat", "https://t.me/hikka_en"],
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button5",
|
||||
["🌘 Hikka. userbot", "https://t.me/hikka_ub"],
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button6",
|
||||
["🌘 Hikka RU Support chat", "https://t.me/hikka_talks"],
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button7",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button8",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button9",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button10",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button11",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_button12",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_btn"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Series(fixed_len=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self, client, _):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
self._me = await client.get_me()
|
||||
|
||||
def _render_info(self) -> str:
|
||||
ver = utils.get_git_hash() or "Unknown"
|
||||
|
||||
try:
|
||||
repo = git.Repo()
|
||||
diff = repo.git.log(["HEAD..origin/master", "--oneline"])
|
||||
upd = (
|
||||
self.strings("update_required").format(
|
||||
utils.escape_html(self.get_prefix())
|
||||
)
|
||||
if diff
|
||||
else self.strings("up-to-date")
|
||||
)
|
||||
except Exception:
|
||||
upd = ""
|
||||
|
||||
me = (
|
||||
"<b><a"
|
||||
f' href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a></b>'
|
||||
)
|
||||
version = f'<i>{".".join(list(map(str, list(main.__version__))))}</i>'
|
||||
build = f'<a href="https://github.com/hikariatama/Hikka/commit/{ver}">#{ver[:8]}</a>' # fmt: skip
|
||||
prefix = f"«<code>{utils.escape_html(self.get_prefix())}</code>»"
|
||||
platform = utils.get_named_platform()
|
||||
uptime = self.apo_lib.utils.get_uptime(short=True)
|
||||
|
||||
return (
|
||||
self.config["custom_message"].format(
|
||||
me=me,
|
||||
version=version,
|
||||
build=build,
|
||||
prefix=prefix,
|
||||
platform=platform,
|
||||
upd=upd,
|
||||
uptime=uptime,
|
||||
)
|
||||
if self.config["custom_message"] and self.config["custom_message"] != "no"
|
||||
else (
|
||||
"<b>🌚 Apodiktum Hikka Info</b>\n"
|
||||
f"<b>🤴 {self.strings('owner')}: </b>{me}\n\n"
|
||||
f"<b>🕰 {self.strings('uptime')}: </b><code>{uptime}</code>\n"
|
||||
f"<b>🔮 {self.strings('version')}: </b>{version} {build}\n"
|
||||
f"<b>{upd}</b>\n\n"
|
||||
f"<b>📼 {self.strings('prefix')}: </b>{prefix}\n"
|
||||
f"<b>{platform}</b>\n"
|
||||
)
|
||||
)
|
||||
|
||||
def _get_mark(self, btn_count: int) -> dict:
|
||||
btn_count = str(btn_count)
|
||||
return (
|
||||
{
|
||||
"text": self.config[f"custom_button{btn_count}"][0],
|
||||
"url": self.config[f"custom_button{btn_count}"][1],
|
||||
}
|
||||
if self.config[f"custom_button{btn_count}"]
|
||||
else None
|
||||
)
|
||||
|
||||
@loader.inline_everyone
|
||||
async def apoinfo_inline_handler(self, _) -> dict:
|
||||
"""Send userbot info"""
|
||||
m = {x: self._get_mark(x) for x in range(13)}
|
||||
btns = [
|
||||
[
|
||||
*([m[1]] if m[1] else []),
|
||||
*([m[2]] if m[2] else []),
|
||||
*([m[3]] if m[3] else []),
|
||||
],
|
||||
[
|
||||
*([m[4]] if m[4] else []),
|
||||
*([m[5]] if m[5] else []),
|
||||
*([m[6]] if m[6] else []),
|
||||
],
|
||||
[
|
||||
*([m[7]] if m[7] else []),
|
||||
*([m[8]] if m[8] else []),
|
||||
*([m[9]] if m[9] else []),
|
||||
],
|
||||
[
|
||||
*([m[10]] if m[10] else []),
|
||||
*([m[11]] if m[11] else []),
|
||||
*([m[12]] if m[12] else []),
|
||||
],
|
||||
]
|
||||
msg_type = "message" if self.config["disable_inline_banner"] else "caption"
|
||||
return {
|
||||
"title": self.strings("send_info"),
|
||||
"description": self.strings("description"),
|
||||
msg_type: self._render_info(),
|
||||
self.config["custom_format"]: self.config["custom_banner"],
|
||||
"thumb": (
|
||||
"https://github.com/hikariatama/Hikka/raw/master/assets/hikka_pfp.png"
|
||||
),
|
||||
"reply_markup": btns,
|
||||
}
|
||||
|
||||
async def capoinfocmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
async def apoinfocmd(self, message: Message):
|
||||
"""Send userbot info"""
|
||||
m = {x: self._get_mark(x) for x in range(13)}
|
||||
btns = [
|
||||
[
|
||||
*([m[1]] if m[1] else []),
|
||||
*([m[2]] if m[2] else []),
|
||||
*([m[3]] if m[3] else []),
|
||||
],
|
||||
[
|
||||
*([m[4]] if m[4] else []),
|
||||
*([m[5]] if m[5] else []),
|
||||
*([m[6]] if m[6] else []),
|
||||
],
|
||||
[
|
||||
*([m[7]] if m[7] else []),
|
||||
*([m[8]] if m[8] else []),
|
||||
*([m[9]] if m[9] else []),
|
||||
],
|
||||
[
|
||||
*([m[10]] if m[10] else []),
|
||||
*([m[11]] if m[11] else []),
|
||||
*([m[12]] if m[12] else []),
|
||||
],
|
||||
]
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self._render_info(),
|
||||
reply_markup=btns,
|
||||
**{}
|
||||
if self.config["disable_banner"]
|
||||
else {self.config["custom_format"]: self.config["custom_banner"]},
|
||||
)
|
||||
279
anon97945/hikka-mods/apolib_controller.py
Normal file
279
anon97945/hikka-mods/apolib_controller.py
Normal file
@@ -0,0 +1,279 @@
|
||||
__version__ = (0, 1, 20)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumLibControllerMod(loader.Module):
|
||||
"""
|
||||
This is a Library Controller module required for Apodiktum Library Modules and also 3rd-party modules.
|
||||
>>Do not unload this!<<
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-LibController",
|
||||
"developer": "@anon97945",
|
||||
"forced_lang": "<b>Forced language {}!</b>",
|
||||
"incorrect_language": "🚫 <b>Incorrect language specified.</b>",
|
||||
"lang_removed": "<b>Forced chat language removed!</b>",
|
||||
"lang_saved": "{} <b>forced chat language saved!</b>",
|
||||
"no_lang": "No forced language in this chat.",
|
||||
"q_pending": "\n<code> - Pending:</code> <code>{}</code>",
|
||||
"q_total_count": "\n<code> - Total count:</code> <code>{}</code>",
|
||||
"q_watcher_str": (
|
||||
"<b><u><i>Queue"
|
||||
" Watcher:</i></u></b>\n<b>Queue:</b>\n<code>{}</code>\n\n<b>Tasks:</b>\n<code>{}</code>"
|
||||
),
|
||||
"queues": "<b><u><i>Queues:</i></u></b>",
|
||||
"t_cancelled": "\n<code> - Cancelled:</code> <code>{}</code>",
|
||||
"t_done": "\n<code> - Done:</code> <code>{}</code>",
|
||||
"t_id": "\n<code> - ID:</code> <code>{}</code>",
|
||||
"t_name": "\n<code> - Name:</code> <code>{}</code>",
|
||||
"tasks": "<b><u><i>Tasks:</i></u></b>",
|
||||
"version_str": "📦 <b>Current Apodiktum Library <code>{}</code>.</b>",
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"_cls_doc": (
|
||||
"Dies ist ein Bibliothekssteuerungsmodul, das für Apodiktum Library"
|
||||
" Module und auch Module von Drittanbietern benötigt wird.\n>>Nicht"
|
||||
" entfernen!<<"
|
||||
),
|
||||
"_cmd_doc_capolib": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
"_cmd_doc_vapolib": (
|
||||
"Zeigt die aktuelle Version des Apodiktum_Library Moduls an."
|
||||
),
|
||||
"_cmd_doc_qapolib": (
|
||||
"Zeigt die aktuellen Queues und Tasks der Apodiktum Library an."
|
||||
),
|
||||
"forced_lang": "<b>Für diesen Chat ist die Sprache {}!</b>",
|
||||
"incorrect_language": "🚫 <b>Falsche Sprache angegeben.</b>",
|
||||
"lang_removed": "<b>Für diesen Chat wurde die Sprache entfernt!</b>",
|
||||
"lang_saved": "{} <b>Sprache für diesen Chat gespeichert!</b>",
|
||||
"no_lang": "Keine Sprache für diesen Chat gesetzt.",
|
||||
"q_pending": "\n<code> - Ausstehend:</code> <code>{}</code>",
|
||||
"q_total_count": "\n<code> - Total count:</code> <code>{}</code>",
|
||||
"q_watcher_str": (
|
||||
"<b><u><i>Queue"
|
||||
" Watcher:</i></u></b>\n<b>Queue:</b>\n<code>{}</code>\n\n<b>Tasks:</b>\n<code>{}</code>"
|
||||
),
|
||||
"queues": "<b><u><i>Queues:</i></u></b>",
|
||||
"t_cancelled": "\n<code> - Abgebrochen:</code> <code>{}</code>",
|
||||
"t_done": "\n<code> - Abgeschlossen:</code> <code>{}</code>",
|
||||
"t_id": "\n<code> - ID:</code> <code>{}</code>",
|
||||
"t_name": "\n<code> - Name:</code> <code>{}</code>",
|
||||
"tasks": "<b><u><i>Tasks:</i></u></b>",
|
||||
"version_str": "📦 <b>Aktuelle Apodiktum_Library <code>{}</code>.</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cmd_doc_capolib": "Это откроет конфиг для модуля.",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.ratelimit = []
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
self._lib_classname = self.apo_lib.__class__.__name__
|
||||
self._lib_db = self._db[self._lib_classname]
|
||||
self._chats_db = self._lib_db.setdefault("chats", {})
|
||||
if self.apo_lib._controllerloader.unload_controller:
|
||||
self.apo_lib._controllerloader.unload_controller = False
|
||||
|
||||
async def capolibcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(
|
||||
message, f"{self.get_prefix()}config {self._lib_classname}"
|
||||
)
|
||||
)
|
||||
|
||||
async def unloadapocontrollercmd(self, message: Message):
|
||||
"""
|
||||
This will unload the module and prevent it from loading through apo_lib.
|
||||
!!Beware that this will break all modules that depend on apo_lib q_watcher. Use this only if you know what you are doing!!
|
||||
"""
|
||||
self.apo_lib._controllerloader.unload_controller = True
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["unloadmod"](
|
||||
await utils.answer(message, f"{self.get_prefix()}unloadmod {name}")
|
||||
)
|
||||
|
||||
async def vapolibcmd(self, message: Message):
|
||||
"""
|
||||
shows the current version of the apodiktum_library.
|
||||
"""
|
||||
if lib_version := getattr(self.allmodules, "_apodiktum_lib_version", None):
|
||||
version_str = f"v{lib_version[0]}.{lib_version[1]}.{lib_version[2]}"
|
||||
else:
|
||||
version_str = "v Unknown"
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("version_str", self.all_strings, message).format(
|
||||
version_str
|
||||
),
|
||||
)
|
||||
|
||||
async def qapolibcmd(self, message):
|
||||
"""
|
||||
shows the current queue and tasks of the apodiktum_library.
|
||||
"""
|
||||
q_string = self.apo_lib.utils.get_str("queues", self.all_strings, message)
|
||||
t_string = self.apo_lib.utils.get_str("tasks", self.all_strings, message)
|
||||
tasks = "tasks="
|
||||
await asyncio.sleep(0.01)
|
||||
for name in self.apo_lib.watcher_q._watcher_q_queue:
|
||||
q_string += f"\n<code>{name}</code>"
|
||||
for q in self.apo_lib.watcher_q._watcher_q_queue[name]:
|
||||
q_string += f"\n<code> - {q}:</code>"
|
||||
q_string += self.apo_lib.utils.get_str(
|
||||
"q_total_count", self.all_strings, message
|
||||
).format(
|
||||
"".join(
|
||||
re.findall(
|
||||
"\d+",
|
||||
"".join(
|
||||
s
|
||||
for s in str(
|
||||
self.apo_lib.watcher_q._watcher_q_queue[name][q]
|
||||
).split()
|
||||
if tasks.lower() in s.lower()
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
q_string += self.apo_lib.utils.get_str(
|
||||
"q_pending", self.all_strings, message
|
||||
).format(self.apo_lib.watcher_q._watcher_q_queue[name][q].qsize())
|
||||
for name in self.apo_lib.watcher_q._watcher_q_task:
|
||||
t_string += f"\n<code>{name}</code>"
|
||||
for t in self.apo_lib.watcher_q._watcher_q_task[name]:
|
||||
t_string += f"\n<code> - {t}:</code> "
|
||||
t_string += self.apo_lib.utils.get_str(
|
||||
"t_name", self.all_strings, message
|
||||
).format(self.apo_lib.watcher_q._watcher_q_task[name][t].get_name())
|
||||
t_string += self.apo_lib.utils.get_str(
|
||||
"t_id", self.all_strings, message
|
||||
).format(id((self.apo_lib.watcher_q._watcher_q_task[name][t])))
|
||||
t_string += self.apo_lib.utils.get_str(
|
||||
"t_cancelled", self.all_strings, message
|
||||
).format(self.apo_lib.watcher_q._watcher_q_task[name][t].cancelled())
|
||||
t_string += self.apo_lib.utils.get_str(
|
||||
"t_done", self.all_strings, message
|
||||
).format(self.apo_lib.watcher_q._watcher_q_task[name][t].done())
|
||||
|
||||
string = f"{q_string or None}\n\n{t_string or None}"
|
||||
await utils.answer(message, string)
|
||||
|
||||
async def fclcmd(self, message: Message):
|
||||
"""
|
||||
<langcode> | force language of supported modules in this chat.
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
chat_id = utils.get_chat_id(message)
|
||||
chatid_str = str(chat_id)
|
||||
chatid_db = self._chats_db.setdefault(chatid_str, {})
|
||||
|
||||
if not args:
|
||||
if len(args) not in [0, 2]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("incorrect_language"),
|
||||
self.all_strings,
|
||||
message,
|
||||
)
|
||||
return
|
||||
if "forced_lang" in chatid_db:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"forced_lang", self.all_strings, message
|
||||
).format(
|
||||
utils.get_lang_flag(
|
||||
chatid_db.get("forced_lang").lower()
|
||||
if chatid_db.get("forced_lang").lower() != "en"
|
||||
else "gb"
|
||||
)
|
||||
),
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_lang", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
chatid_db.update({"forced_lang": args.lower()})
|
||||
self._db.set(self._lib_classname, "chats", self._chats_db)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("lang_saved", self.all_strings, message).format(
|
||||
utils.get_lang_flag(args.lower() if args.lower() != "en" else "gb")
|
||||
),
|
||||
)
|
||||
|
||||
async def remfclcmd(self, message: Message):
|
||||
"""
|
||||
remove force language in this chat.
|
||||
"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
chatid_str = str(chat_id)
|
||||
chatid_db = self._chats_db.setdefault(chatid_str, {})
|
||||
|
||||
if chatid_db.get("forced_lang"):
|
||||
chatid_db.pop("forced_lang")
|
||||
self._db.set(self._lib_classname, "chats", self._chats_db)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("lang_removed", self.all_strings, message),
|
||||
)
|
||||
|
||||
@loader.watcher(only_messages=True)
|
||||
async def watcher(self, message: Message):
|
||||
with contextlib.suppress(Exception):
|
||||
await self.apo_lib.watcher_q.msg_reciever(message)
|
||||
117
anon97945/hikka-mods/auto_delerror.py
Normal file
117
anon97945/hikka-mods/auto_delerror.py
Normal file
@@ -0,0 +1,117 @@
|
||||
__version__ = (0, 0, 4)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumAutoDelErrorMod(loader.Module):
|
||||
"""
|
||||
This module deletes error messages which have defined text in it.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-AutoDelError",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_additional_id": "Additional Telegram IDs to remove the error message from.",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_error_text": "The text of the error message to remove.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"additional_ids",
|
||||
doc=lambda: self.strings("_cfg_additional_id"),
|
||||
validator=loader.validators.Series(loader.validators.TelegramID()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"error_text",
|
||||
doc=lambda: self.strings("_cfg_error_text"),
|
||||
validator=loader.validators.Series(loader.validators.String()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClas
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cautodelerrorcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.watcher("in", "only_groups", "only_messages")
|
||||
async def watcher(self, message):
|
||||
if (
|
||||
message.sender_id != self.inline.bot_id
|
||||
and message.sender_id not in self.config["additional_ids"]
|
||||
):
|
||||
return
|
||||
if self.config["error_text"]:
|
||||
logchan_id = int(str(self.lookup("tester").logchat).replace("-100", ""))
|
||||
if message.chat.id == logchan_id:
|
||||
for error_text in self.config["error_text"]:
|
||||
if error_text in message.raw_text:
|
||||
await message.delete()
|
||||
return
|
||||
274
anon97945/hikka-mods/auto_update.py
Normal file
274
anon97945/hikka-mods/auto_update.py
Normal file
@@ -0,0 +1,274 @@
|
||||
__version__ = (1, 0, 27)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
skip_update = ["[do not install]", "[unstable]", "[test]"]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumAutoUpdateMod(loader.Module):
|
||||
"""
|
||||
Automatically update your Hikka Userbot
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-AutoUpdater",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_auto_update": (
|
||||
"Whether the Hikka Userbot should automatically update or not."
|
||||
),
|
||||
"_cfg_auto_update_delay": (
|
||||
"Choose a delay to wait to start the automatic update."
|
||||
),
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_update_msg_read": "Whether to mark the message as read or not.",
|
||||
"skip_old": "The update was skipped due to a newer update.",
|
||||
"skip_update": "The update was skipped due to {}.\n{}",
|
||||
"updating": (
|
||||
"Hikka Userbot will be automatically updated in {} seconds.\n\n"
|
||||
"Changelog:\n{}"
|
||||
),
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_auto_update": (
|
||||
"Ob der Hikka Userbot automatisch aktualisieren soll oder nicht."
|
||||
),
|
||||
"_cfg_auto_update_delay": (
|
||||
"Wählen Sie eine Wartezeit bis zum Start des automatischen Updates."
|
||||
),
|
||||
"_cfg_update_msg_read": (
|
||||
"Ob die Nachricht als gelesen markiert werden soll oder nicht."
|
||||
),
|
||||
"_cmd_doc_cautoupdate": (
|
||||
"Dadurch wird die Konfiguration für das Modul geöffnet."
|
||||
),
|
||||
"skip_old": "Das Update wurde aufgrund eines neueren Updates übersprungen.",
|
||||
"skip_update": "Das Update wurde wegen {} übersprungen.\n{}",
|
||||
"updating": (
|
||||
"Hikka Userbot wird in {} Sekunden automatisch aktualisiert.\n\n"
|
||||
"Changelog:\n{}"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_auto_update": (
|
||||
"Должен ли Hikka UserBot обновляться автоматически или нет."
|
||||
),
|
||||
"_cfg_auto_update_delay": "Выберите задержку для автоматического обновления.",
|
||||
"_cfg_update_msg_read": (
|
||||
"Отмечать ли сообщение с обновлением как прочитанное или нет."
|
||||
),
|
||||
"skip_old": (
|
||||
"Обновление было пропущено в связи с появлением более новой версии."
|
||||
),
|
||||
"skip_update": "Обновление было пропущено из-за {}.\n{}",
|
||||
"_cmd_doc_cautoupdate": "Открыть конфиг модуля.",
|
||||
"updating": (
|
||||
"Hikka будет автоматически обновлена через {} секунд.\n\n"
|
||||
"Список изменений:\n{}"
|
||||
),
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo AutoUpdater",
|
||||
"new": "Apo-AutoUpdater",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"auto_update",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_auto_update"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"mark_read",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_update_msg_read"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"update_delay",
|
||||
"600",
|
||||
doc=lambda: self.strings("_cfg_auto_update_delay"),
|
||||
validator=loader.validators.Integer(minimum=60),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
asyncio.ensure_future(self._check_on_load())
|
||||
|
||||
async def cautoupdatecmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _buttonhandler(
|
||||
bmsg: Message,
|
||||
chatid: int,
|
||||
caption1: str,
|
||||
caption2: str,
|
||||
data_btn1: str,
|
||||
data_btn2: str,
|
||||
) -> bool:
|
||||
fnd_btn1 = False
|
||||
fnd_btn2 = False
|
||||
bmsg = await bmsg.client.get_messages(chatid, ids=bmsg.id)
|
||||
buttons = bmsg.buttons
|
||||
if (
|
||||
caption1 in bmsg.message and caption2 in bmsg.message
|
||||
) and bmsg.buttons is not None:
|
||||
for row in buttons:
|
||||
for button in row:
|
||||
if data_btn1 in str(button.data):
|
||||
fnd_btn1 = True
|
||||
if data_btn2 in str(button.data):
|
||||
fnd_btn2 = True
|
||||
if fnd_btn1 and fnd_btn2:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _autoupdate(self, message: Message):
|
||||
if self.config["mark_read"]:
|
||||
await self._client.send_read_acknowledge(
|
||||
message.peer_id,
|
||||
clear_mentions=True,
|
||||
)
|
||||
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("updating").format(
|
||||
self.config["update_delay"],
|
||||
"\n".join(self.apo_lib.utils.raw_text(message).splitlines()[5:]),
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(self.config["update_delay"])
|
||||
with contextlib.suppress(Exception):
|
||||
return await message.click(0)
|
||||
|
||||
async def _check_skip(self, message: Message) -> bool:
|
||||
last_commit = self.apo_lib.utils.raw_text(message).splitlines()[5].lower()
|
||||
for x in skip_update:
|
||||
if x.lower() in last_commit and "revert" not in last_commit:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("skip_update").format(x, last_commit),
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _check_on_load(self):
|
||||
if not self.config["auto_update"]:
|
||||
return
|
||||
|
||||
async for message in self.client.iter_messages(
|
||||
entity=self.inline.bot_id, limit=5
|
||||
):
|
||||
if (
|
||||
isinstance(message, Message)
|
||||
and message.sender_id == self.inline.bot_id
|
||||
and await self._buttonhandler(
|
||||
message,
|
||||
self.inline.bot_id,
|
||||
"🌘",
|
||||
"🔮",
|
||||
"hikka_update",
|
||||
"hikka_upd_ignore",
|
||||
)
|
||||
):
|
||||
if await self._check_skip(message):
|
||||
return
|
||||
with contextlib.suppress(Exception):
|
||||
self._autoupdate_task.cancel()
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO, __name__, self.strings("skip_old")
|
||||
)
|
||||
self._autoupdate_task = asyncio.ensure_future(self._autoupdate(message))
|
||||
|
||||
@loader.watcher("in", "only_messages", "only_pm")
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
self.config["auto_update"]
|
||||
and utils.get_chat_id(message) == self.inline.bot_id
|
||||
and message.sender_id == self.inline.bot_id
|
||||
and await self._buttonhandler(
|
||||
message,
|
||||
self.inline.bot_id,
|
||||
"🌘",
|
||||
"🔮",
|
||||
"hikka_update",
|
||||
"hikka_upd_ignore",
|
||||
)
|
||||
):
|
||||
if await self._check_skip(message):
|
||||
return
|
||||
with contextlib.suppress(Exception):
|
||||
self._autoupdate_task.cancel()
|
||||
self.apo_lib.utils.log(logging.INFO, __name__, self.strings("skip_old"))
|
||||
self._autoupdate_task = asyncio.ensure_future(self._autoupdate(message))
|
||||
return
|
||||
322
anon97945/hikka-mods/autoreact.py
Normal file
322
anon97945/hikka-mods/autoreact.py
Normal file
@@ -0,0 +1,322 @@
|
||||
__version__ = (0, 1, 30)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
|
||||
from telethon.errors import ReactionInvalidError
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumAutoReactMod(loader.Module):
|
||||
"""
|
||||
AutoReact to messages.
|
||||
Check the `.config apodiktum autoreact`
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-AutoReact",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_doc_delay": "The delay between reactions are send in seconds.",
|
||||
"_cfg_doc_delay_chats": (
|
||||
"List of delay chats.\nIf the chat is in the list, the delay is used."
|
||||
),
|
||||
"_cfg_doc_ignore_self": "Do not react to messages from yourself.",
|
||||
"_cfg_doc_raise_error": "Raise an error if the emoji is not valid.",
|
||||
"_cfg_doc_random_delay": (
|
||||
"Randomizes the delay between reactions. Randomness is between 0"
|
||||
" and the global delay."
|
||||
),
|
||||
"_cfg_doc_random_delay_chats": (
|
||||
"List of random delay chats.\nIf the chat is in the list, a random"
|
||||
" delay is used."
|
||||
),
|
||||
"_cfg_doc_reactions": (
|
||||
"Setup AutoReact.\nYou can define alternative emojis to react with, when"
|
||||
" the Chat doesn't allow the first, second etc.\nYou can also define an all"
|
||||
" OR global state, which will either apply reactions to all chat members"
|
||||
" (all) or to one user in all groups(global).\nYou can't use both at the"
|
||||
" same time! Does also work for channels! You need to use"
|
||||
" ALL!\n\nPattern:\n<userid/all>|<chatid/global>|<emoji1>|<emoji2>|<emoji3>...\n\nExample:\nall|1792410946|❤️|👍|🔥\nFor"
|
||||
" Channels:\nall|<channelid>|❤️|👍|🔥"
|
||||
),
|
||||
"_cfg_doc_reactions_chance": (
|
||||
"The chance of reacting to a message.\n0.0 is the chance of not"
|
||||
" reacting to"
|
||||
" a message.\n1.0 is the chance of reacting to a message every"
|
||||
" time."
|
||||
"Pattern:\n<userid/all>|<chatid/global>|<percentage(0.00-1)>\n\nExample:\n1234567|global|0.8"
|
||||
),
|
||||
"_cfg_doc_shuffle_chats": "A list of chats where the emoji list is shuffled.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_doc_delay": "Задержка между реакциями в секундах",
|
||||
"_cfg_doc_delay_chats": (
|
||||
"Список чатов с задержкой.\nЕсли чат находится в списке, то"
|
||||
" используется задержка."
|
||||
),
|
||||
"_cfg_doc_ignore_self": "Не ставить реакции на свои сообщения",
|
||||
"_cfg_doc_raise_error": "Вызывает ошибку, если эмодзи неверный.",
|
||||
"_cfg_doc_random_delay": (
|
||||
"Случайным образом изменяет задержку между реакциями. Вероятность"
|
||||
" находится в диапазоне от 0 до глобальной задержки."
|
||||
),
|
||||
"_cfg_doc_random_delay_chats": (
|
||||
"Список чатов со случайной задержкой.\nЕсли чат находится в списке,"
|
||||
" используется случайная задержка."
|
||||
),
|
||||
"_cfg_doc_reactions": (
|
||||
"Настройка автореакции.\nВы можете указать альтернативные эмодзи для"
|
||||
" реакций. Будет выбран первый доступный в чате\nВы также можете определить"
|
||||
" состояние все(all) или глобальное(global) которое будет применять реакцию"
|
||||
" либо ко всем участникам чата (все), либо к одному пользователю во всех"
|
||||
" группах (глобальное).\nВы не можете использовать оба варианта"
|
||||
" одновременно! Это также работает для каналов! Вам нужно использовать"
|
||||
" ALL!\n\nФормат:\n<userid/all>|<chatid/global>|<emoji1>|<emoji2>|<emoji3>...\n\nПример:\nall|1792410946|❤️|👍|🔥\nДля"
|
||||
" каналов:\nall|<channelid>|❤️|👍|🔥"
|
||||
),
|
||||
"_cfg_doc_reactions_chance": (
|
||||
"Шанс реакции на сообщение.\n0.0 - всегда не реагировать на"
|
||||
" сообщение.\n1.0 -"
|
||||
" всегда реагировать на сообщение."
|
||||
"Формат:\n<userid/all>|<chatid/global>|<percentage(0.00-1)>\n\nПример:\n1234567|global|0.8"
|
||||
),
|
||||
"_cfg_doc_shuffle_chats": (
|
||||
"Список чатов, в которых перемешивается список эмодзи."
|
||||
),
|
||||
"_cls_doc": "Автореакция на сообщения.\nПроверьте .config apodiktum autoreact.",
|
||||
"_cmd_doc_cautoreact": "Это откроет конфиг для модуля.",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo AutoReact",
|
||||
"new": "Apo-AutoReact",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"reaction_active",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_doc_react"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delay",
|
||||
0.5,
|
||||
doc=lambda: self.strings("_cfg_doc_delay"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Float(minimum=0, maximum=600),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delay_chats",
|
||||
doc=lambda: self.strings("_cfg_doc_delay_chats"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.TelegramID(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_self",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_doc_ignore_self"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"raise_error",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_doc_raise_error"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"random_delay_chats",
|
||||
doc=lambda: self.strings("_cfg_doc_random_delay_chats"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.TelegramID(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"reactions",
|
||||
["all|1792410946|❤️|👍|🔥"],
|
||||
doc=lambda: self.strings("_cfg_doc_reactions"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.RegExp(
|
||||
r"^(?:(?:\d+)[|](?:\d+|global)|(?:\d+|all)[|]\d+)(?:[|][👍👎❤️🔥🥰👏😁🤔🤯😱🤬😢🎉🤩🤮💩🙏👌🕊🤡🥱🥴😍🐳❤️🔥🌚🌭💯🤣⚡️🍌🏆💔🤨😐🍓🍾💋🖕😈😴😭🤓👻👨💻👀🎃🙈😇😨🤝✍️🤗🫡🎅🎄☃️💅🤪🗿🆒💘🙉😎👾🤷♂️🤷🤷♀️😡]|[|][\u2764])+"
|
||||
)
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"reactions_chance",
|
||||
doc=lambda: self.strings("_cfg_doc_reactions_chance"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.RegExp(
|
||||
r"^(?:(?:\d+)[|](?:\d+|global)|(?:\d+|all)[|]\d+)(?:[|](?:0(?:\.\d{1,2})?|1(?:\.0{1,2})?))$"
|
||||
)
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"shuffle_reactions",
|
||||
doc=lambda: self.strings("_cfg_doc_shuffle_chats"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.TelegramID(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cautoreactcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.watcher("only_messages")
|
||||
async def watcher(self, message: Message):
|
||||
if not self.config["reaction_active"]:
|
||||
return
|
||||
reactions = self.config["reactions"]
|
||||
reactions_chance = self.config["reactions_chance"]
|
||||
|
||||
for reaction in reactions:
|
||||
userid, chatid, *emoji_list = reaction.split("|")
|
||||
if userid == "all" and chatid == "global":
|
||||
return
|
||||
if (
|
||||
(str(message.sender_id) == userid or userid == "all")
|
||||
and (str(utils.get_chat_id(message)) == chatid or chatid == "global")
|
||||
and not (userid == "all" and self.config["ignore_self"] and message.out)
|
||||
):
|
||||
if not await self._reactions_chance(reactions_chance, message):
|
||||
return
|
||||
if utils.get_chat_id(message) in self.config["shuffle_reactions"]:
|
||||
emoji_list = random.sample(emoji_list, len(emoji_list))
|
||||
await self._delay(chatid, userid)
|
||||
for emoji_reaction in emoji_list:
|
||||
if await self._react_message(message, emoji_reaction, chatid):
|
||||
return
|
||||
|
||||
async def _delay(self, chatid, userid):
|
||||
if chatid != "global":
|
||||
chatid = int(chatid)
|
||||
if userid != "all":
|
||||
userid = int(userid)
|
||||
if (
|
||||
chatid in self.config["delay_chats"]
|
||||
or userid in self.config["delay_chats"]
|
||||
or chatid in self.config["random_delay_chats"]
|
||||
or userid in self.config["random_delay_chats"]
|
||||
):
|
||||
if (
|
||||
chatid not in self.config["random_delay_chats"]
|
||||
or userid not in self.config["random_delay_chats"]
|
||||
):
|
||||
await asyncio.sleep(self.config["delay"])
|
||||
else:
|
||||
await asyncio.sleep(round(random.uniform(0, self.config["delay"]), 2))
|
||||
|
||||
@staticmethod
|
||||
async def _reactions_chance(reactions_chance: list, message: Message) -> bool:
|
||||
for r_chance in reactions_chance:
|
||||
userid, chatid, chance = r_chance.split("|")
|
||||
if userid == "all" and chatid == "global":
|
||||
return False
|
||||
if (
|
||||
(str(message.sender_id) == userid or userid == "all")
|
||||
and (str(utils.get_chat_id(message)) == chatid or chatid == "global")
|
||||
and random.random() > float(chance)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _react_message(
|
||||
self,
|
||||
message: Message,
|
||||
emoji_reaction: str,
|
||||
chatid: int,
|
||||
) -> bool:
|
||||
try:
|
||||
await message.react(emoji_reaction)
|
||||
return True
|
||||
except ReactionInvalidError:
|
||||
if self.config["raise_error"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
f"ReactionInvalidError: {emoji_reaction} in chat {chatid}",
|
||||
)
|
||||
return False
|
||||
except Exception as exc: # skipcq: PYL-W0703
|
||||
if self.config["raise_error"]:
|
||||
if "PREMIUM_ACCOUNT_REQUIRED" in str(exc):
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
f"PREMIUM_ACCOUNT_REQUIRED: {emoji_reaction} in chat {chatid}",
|
||||
)
|
||||
else:
|
||||
self.apo_lib.utils.log(logging.INFO, __name__, f"Error: {exc}")
|
||||
return False
|
||||
1068
anon97945/hikka-mods/dnd.py
Normal file
1068
anon97945/hikka-mods/dnd.py
Normal file
File diff suppressed because it is too large
Load Diff
444
anon97945/hikka-mods/donators.py
Normal file
444
anon97945/hikka-mods/donators.py
Normal file
@@ -0,0 +1,444 @@
|
||||
__version__ = (0, 0, 31)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
from datetime import date, timedelta
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumDonatorsMod(loader.Module):
|
||||
"""
|
||||
Handle donations in a given channel and kick them after the period of time.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-Donators",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_cst_channel": "The Channel ID where the donations should be saved.",
|
||||
"_cfg_cst_custom_message": (
|
||||
"The message send to the user after the subscription is added. Use"
|
||||
" <br> for new line."
|
||||
),
|
||||
"_cfg_cst_kickchannel": (
|
||||
"The channel ids to kick the user from after the subscription."
|
||||
),
|
||||
"_cfg_cst_monthlyamount": "The monthly cost of the subscription.",
|
||||
"_cfg_cst_subscription_gift": (
|
||||
"The gift to send to the user after the subscription. Will be"
|
||||
" attached to custom_message. Use <br> for new line."
|
||||
),
|
||||
"_cfg_doc_log_kick": "Logs successful kicks from the chats.",
|
||||
"_log_doc_kicked": "Kicked {} from {}.",
|
||||
"amount": "Amount",
|
||||
"code": "Code",
|
||||
"date": "Date",
|
||||
"donation_saved": "🎉 Donation saved!",
|
||||
"dtype": "DonationType",
|
||||
"no_amount": "No amount found.",
|
||||
"no_args": "No args.",
|
||||
"no_channel": "No logchannel set.",
|
||||
"no_reply": "You didn't reply to a message.",
|
||||
"rank": "Rank",
|
||||
"total_amount": "<b><u>Total amount of donations:</u></b>\n{}",
|
||||
"uname": "Name",
|
||||
"userid": "UserID",
|
||||
"username": "Username",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_cst_channel": "Die Kanal-ID, wo die Spenden gespeichert werden sollen.",
|
||||
"_cfg_cst_custom_message": (
|
||||
"Die Nachricht, die an den Benutzer gesendet wird, nachdem das"
|
||||
" Abonnement hinzugefügt wurde. Benutze <br> für einen"
|
||||
" Zeilenumbruch."
|
||||
),
|
||||
"_cfg_cst_kickchannel": (
|
||||
"Die Kanal-IDs, aus denen der Benutzer nach dem Abonnement gekickt"
|
||||
" werden soll."
|
||||
),
|
||||
"_cfg_cst_monthlyamount": "Die monatlichen Kosten des Abonnements.",
|
||||
"_cfg_cst_subscription_gift": (
|
||||
"Das Geschenk, das an den Benutzer gesendet wird, nachdem das"
|
||||
" Abonnement hinzugefügt wurde. Wird an custom_message angehängt."
|
||||
" Benutze <br> für einen Zeilenumbruch."
|
||||
),
|
||||
"_log_doc_kicked": "{} von {} gekickt.",
|
||||
"_log_doc_log_kicks": "Protokolliert die erfolgreichen Kicks aus den Chats.",
|
||||
"amount": "Betrag",
|
||||
"code": "Code",
|
||||
"date": "Datum",
|
||||
"donation_saved": "🎉 Spende gespeichert!",
|
||||
"dtype": "Spendentyp",
|
||||
"no_amount": "Kein Betrag gefunden.",
|
||||
"no_args": "Keine Argumente angegeben",
|
||||
"no_channel": "Kein Protokollkanal gesetzt.",
|
||||
"no_reply": "Du hast nicht auf eine Nachricht geantwortet.",
|
||||
"rank": "Rang",
|
||||
"total_amount": "<b><u>Gesamtbetrag der Spenden:</u></b>\n{}",
|
||||
"uname": "Name",
|
||||
"userid": "BenutzerID",
|
||||
"username": "Benutzername",
|
||||
}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo Donators",
|
||||
"new": "Apo-Donators",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"logchannel",
|
||||
"None",
|
||||
lambda: self.strings("_cfg_cst_logchannel"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.TelegramID(),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_message",
|
||||
["Thank you very much for your donation! 🎉"],
|
||||
doc=lambda: self.strings("_cfg_cst_custom_message"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.String()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"kick_channel",
|
||||
doc=lambda: self.strings("_cfg_cst_kickchannel"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_kicks",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_doc_log_kick"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"monthly_amount",
|
||||
10,
|
||||
doc=lambda: self.strings("_cfg_cst_monthlyamount"),
|
||||
validator=loader.validators.Integer(minimum=1),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"subscription_gift",
|
||||
doc=lambda: self.strings("_cfg_cst_subscription_gift"),
|
||||
validator=loader.validators.Hidden(
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.String()
|
||||
),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cdonatorscmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def donamountcmd(self, message: Message):
|
||||
"""
|
||||
Calculate the amount of donations.
|
||||
"""
|
||||
if not self.config["logchannel"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_channel", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
amounts = await self._get_amounts(message, self.config["logchannel"])
|
||||
if amounts:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"total_amount", self.all_strings, message
|
||||
).format(amounts),
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_amount", self.all_strings, message),
|
||||
)
|
||||
|
||||
async def donsavecmd(self, message: Message):
|
||||
"""
|
||||
Save donation. Reply to the user message!
|
||||
Pattern:
|
||||
.donsave <amount> <currency> <dtype> <rank> <code> | as reply!
|
||||
Example:
|
||||
.donsave 100 € amazon vip 123-123-123-123, 456-456-456-456 | as reply!
|
||||
"""
|
||||
reply = await message.get_reply_message()
|
||||
if not self.config["logchannel"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_channel", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if not reply:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_reply", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
user = await reply.get_sender()
|
||||
if not user:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_user", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
args = utils.get_args_raw(message).lower()
|
||||
args = str(args).split()
|
||||
if not args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_args", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
(
|
||||
monthly_amount,
|
||||
today,
|
||||
uname,
|
||||
username,
|
||||
userid,
|
||||
amount,
|
||||
currency,
|
||||
dtype,
|
||||
rank,
|
||||
code,
|
||||
) = self._vars(user, args)
|
||||
|
||||
string_join, string_kick = self._jk_strings(
|
||||
today, uname, username, userid, amount, currency, dtype, rank, code
|
||||
)
|
||||
|
||||
msg = await message.client.send_message(
|
||||
int(self.config["logchannel"]),
|
||||
string_join,
|
||||
)
|
||||
|
||||
await message.client.send_message(
|
||||
int(self.config["logchannel"]),
|
||||
string_kick,
|
||||
schedule=(
|
||||
date.today() + timedelta(days=int(amount) / monthly_amount * 30)
|
||||
),
|
||||
)
|
||||
if self.config["custom_message"]:
|
||||
custom_msg = " ".join(self.config["custom_message"])
|
||||
if self.config["subscription_gift"]:
|
||||
custom_msg += " ".join(self.config["subscription_gift"])
|
||||
custom_msg = custom_msg.replace("<br>", "\n")
|
||||
await utils.answer(message, custom_msg)
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("donation_saved", self.all_strings, message),
|
||||
)
|
||||
await msg.react("👍")
|
||||
|
||||
async def _get_amounts(self, message: Message, logchannel: int):
|
||||
amounts = ""
|
||||
amounts_euro = []
|
||||
amounts_usd = []
|
||||
amounts_gbp = []
|
||||
amounts_rub = []
|
||||
itermsg = message.client.iter_messages(entity=logchannel, limit=None)
|
||||
async for msg in itermsg:
|
||||
if (
|
||||
msg
|
||||
and isinstance(msg, Message)
|
||||
and "#join" in self.apo_lib.utils.raw_text(msg).lower()
|
||||
):
|
||||
msg_lines = self.apo_lib.utils.raw_text(msg).splitlines()
|
||||
for lines in msg_lines:
|
||||
if (
|
||||
"€" in lines.lower()
|
||||
or "$" in lines.lower()
|
||||
or "£" in lines.lower()
|
||||
or "₽" in lines.lower()
|
||||
):
|
||||
for z in lines.split():
|
||||
if "€" in z:
|
||||
z = z.replace("€", "")
|
||||
if z.isdigit():
|
||||
amounts_euro.append(int(z))
|
||||
if "$" in z:
|
||||
z = z.replace("$", "")
|
||||
if z.isdigit():
|
||||
amounts_usd.append(int(z))
|
||||
if "£" in z:
|
||||
z = z.replace("£", "")
|
||||
if z.isdigit():
|
||||
amounts_gbp.append(int(z))
|
||||
if "₽" in z:
|
||||
z = z.replace("₽", "")
|
||||
if z.isdigit():
|
||||
amounts_rub.append(int(z))
|
||||
if amounts_euro:
|
||||
amounts += f"<code>{sum(amounts_euro)}€</code>\n"
|
||||
if amounts_usd:
|
||||
amounts += f"<code>{sum(amounts_usd)}$</code>\n"
|
||||
if amounts_gbp:
|
||||
amounts += f"<code>{sum(amounts_gbp)}£</code>\n"
|
||||
if amounts_rub:
|
||||
amounts += f"<code>{sum(amounts_rub)}₽</code>\n"
|
||||
return amounts
|
||||
|
||||
def _vars(self, user, args):
|
||||
monthly_amount = self.config["monthly_amount"]
|
||||
today = date.today()
|
||||
uname = user.first_name
|
||||
if user.last_name:
|
||||
uname += f" {user.last_name}"
|
||||
username = f"@{user.username}" if user.username else ""
|
||||
userid = user.id
|
||||
amount = args[0]
|
||||
currency = args[1]
|
||||
dtype = args[2].capitalize()
|
||||
rank = args[3].upper()
|
||||
code = str(args[4:]).upper()
|
||||
return (
|
||||
monthly_amount,
|
||||
today,
|
||||
uname,
|
||||
username,
|
||||
userid,
|
||||
amount,
|
||||
currency,
|
||||
dtype,
|
||||
rank,
|
||||
code,
|
||||
)
|
||||
|
||||
def _jk_strings(
|
||||
self,
|
||||
today,
|
||||
uname,
|
||||
username,
|
||||
userid,
|
||||
amount,
|
||||
currency,
|
||||
dtype,
|
||||
rank,
|
||||
code,
|
||||
):
|
||||
string_join = (
|
||||
"#Join\n"
|
||||
+ f"#{self.strings('date')} {today}\n"
|
||||
+ f"#{self.strings('uname')} {uname}\n"
|
||||
+ f"#{self.strings('username')} {username}\n"
|
||||
+ f"#ID_{userid}\n"
|
||||
+ f"#{self.strings('dtype')} {dtype}\n"
|
||||
+ f"#{self.strings('amount')} {amount}{currency}\n"
|
||||
+ f"#{self.strings('rank')} {rank}\n"
|
||||
+ f"#{self.strings('code')} {code}\n"
|
||||
)
|
||||
|
||||
string_kick = (
|
||||
"#Kick\n"
|
||||
+ f"#{self.strings('date')} {today}\n"
|
||||
+ f"#{self.strings('uname')} {uname}\n"
|
||||
+ f"#{self.strings('username')} {username}\n"
|
||||
+ f"#ID_{userid}\n"
|
||||
+ f"#{self.strings('dtype')} {dtype}\n"
|
||||
+ f"#{self.strings('amount')} {amount}{currency}\n"
|
||||
+ f"#{self.strings('rank')} {rank}\n"
|
||||
+ f"#{self.strings('code')} {code}\n"
|
||||
)
|
||||
return string_join, string_kick
|
||||
|
||||
@loader.watcher("only_messages")
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
not self.config["logchannel"]
|
||||
or utils.get_chat_id(message) != self.config["logchannel"]
|
||||
or "#kick" not in self.apo_lib.utils.raw_text(message).lower()
|
||||
):
|
||||
return
|
||||
|
||||
msg_lines = self.apo_lib.utils.raw_text(message).splitlines()
|
||||
for text in msg_lines:
|
||||
if "#ID_" in text:
|
||||
userid = int(text.replace("#ID_", ""))
|
||||
kchannels = self.config["kick_channel"]
|
||||
for kchannel in kchannels:
|
||||
if await self.apo_lib.utils.is_member(kchannel, userid):
|
||||
await message.client.kick_participant(
|
||||
kchannel,
|
||||
userid,
|
||||
)
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("_log_doc_kicked").format(userid, kchannel),
|
||||
)
|
||||
await message.react("👍")
|
||||
24
anon97945/hikka-mods/full.txt
Normal file
24
anon97945/hikka-mods/full.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
admintools
|
||||
apoinfo
|
||||
apolib_controller
|
||||
auto_delerror
|
||||
auto_update
|
||||
autoreact
|
||||
dnd
|
||||
donators
|
||||
gtranslate
|
||||
heroku
|
||||
langreplier
|
||||
lcr
|
||||
linked_chat
|
||||
mark_read
|
||||
msg_merger
|
||||
no_ttl
|
||||
pmlog
|
||||
purge
|
||||
pypng
|
||||
quotes
|
||||
save_message
|
||||
show_viewer
|
||||
tts
|
||||
voicetools
|
||||
202
anon97945/hikka-mods/gtranslate.py
Normal file
202
anon97945/hikka-mods/gtranslate.py
Normal file
@@ -0,0 +1,202 @@
|
||||
__version__ = (0, 0, 72)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
# requires: googletrans==4.0.0-rc1
|
||||
|
||||
import logging
|
||||
|
||||
import googletrans
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
if googletrans.__version__ != "4.0.0-rc.1":
|
||||
raise ImportError( # This will force Hikka to attempt dependency re-installation
|
||||
f"The googletrans version is {googletrans.__version__}, not"
|
||||
' "4.0.0-rc.1".It means the module cannot run properly. To fix this,'
|
||||
" reinstall googletrans==4.0.0-rc1..terminal pip install"
|
||||
" googletrans==4.0.0-rc1"
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumGTranslateMod(loader.Module):
|
||||
"""
|
||||
Google Translator
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-GoogleTranslator",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_lang_msg": "Language to translate to by default.",
|
||||
"_cfg_vodkatr_msg": "If `RU` should be displayed as `Vodka`.",
|
||||
"invalid_text": "Invalid text to translate",
|
||||
"split_error": (
|
||||
"Python split() error, if there is -> in the text, it must split!"
|
||||
),
|
||||
"translated": (
|
||||
"<b>[ <code>{frlang}</code> -> </b><b><code>{to}</code>"
|
||||
" ]</b>\n<code>{output}</code>"
|
||||
),
|
||||
"translating": "Translating...",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_lang_msg": "Sprache, in die standardmäßig übersetzt werden soll.",
|
||||
"_cfg_vodkatr_msg": "Ob `RU` als `Vodka` angezeigt werden soll.",
|
||||
"_cmd_doc_cgtranslate": (
|
||||
"Dadurch wird die Konfiguration für das Modul geöffnet."
|
||||
),
|
||||
"invalid_text": "Ungültiger Text zum Übersetzen.",
|
||||
"split_error": (
|
||||
"Python split() error, wenn -> im Text steht, muss es gesplittet werden!"
|
||||
),
|
||||
"translated": (
|
||||
"<b>[ <code>{frlang}</code> -> </b><b><code>{to}</code>"
|
||||
" ]</b>\n<code>{output}</code>"
|
||||
),
|
||||
"translating": "Übersetze...",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_lang_msg": "Язык на который переводится по умолчанию.",
|
||||
"_cfg_vodkatr_msg": "Если `RU`, то должно отображаться как `Vodka`.",
|
||||
"_cmd_doc_cgtranslate": "Это откроет конфиг для модуля.",
|
||||
"invalid_text": "Неправильный текст для перевода",
|
||||
"split_error": (
|
||||
"Ошибка в функции Python – split(). Если в тексте есть ->, то это"
|
||||
" должно быть разделено."
|
||||
),
|
||||
"translated": (
|
||||
"<b>[ <code>{frlang}</code> -> </b><b><code>{to}</code>"
|
||||
" ]</b>\n<code>{output}</code>"
|
||||
),
|
||||
"translating": "Переводим...",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo GoogleTranslator",
|
||||
"new": "Apo-GoogleTranslator",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"DEFAULT_LANG",
|
||||
"en",
|
||||
doc=lambda: self.strings("_cfg_lang_msg"),
|
||||
validator=loader.validators.String(length=2),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"vodka_easteregg",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_vodkatr_msg"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
self.tr = googletrans.Translator()
|
||||
|
||||
async def cgtranslatecmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def gtranslatecmd(self, message: Message):
|
||||
""".gtranslate [from_lang->][->to_lang] <text>"""
|
||||
args = utils.get_args(message)
|
||||
|
||||
if len(args) == 0 or "->" not in args[0]:
|
||||
text = " ".join(args)
|
||||
args = ["", self.config["DEFAULT_LANG"]]
|
||||
else:
|
||||
text = " ".join(args[1:])
|
||||
args = args[0].split("->")
|
||||
|
||||
if not text and message.is_reply:
|
||||
text = (await message.get_reply_message()).message
|
||||
if len(text) == 0:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("invalid_text", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if args[0] == "":
|
||||
args[0] = (await utils.run_sync(self.tr.detect, text)).lang
|
||||
if len(args) == 3:
|
||||
del args[1]
|
||||
if len(args) == 1:
|
||||
logging.error(self.strings("split_error"))
|
||||
raise RuntimeError()
|
||||
if args[1] == "":
|
||||
args[1] = self.config["DEFAULT_LANG"]
|
||||
args[0] = args[0].lower()
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("translating", self.all_strings, message),
|
||||
)
|
||||
translated = (
|
||||
await utils.run_sync(self.tr.translate, text, dest=args[1], src=args[0])
|
||||
).text
|
||||
ret = self.apo_lib.utils.get_str("translated", self.all_strings, message)
|
||||
if self.config["vodka_easteregg"]:
|
||||
args = list(map(lambda x: x.replace("ru", "vodka"), args))
|
||||
ret = ret.format(
|
||||
text=utils.escape_html(text),
|
||||
frlang=utils.escape_html(args[0]),
|
||||
to=utils.escape_html(args[1]),
|
||||
output=utils.escape_html(translated),
|
||||
)
|
||||
await utils.answer(message, ret)
|
||||
423
anon97945/hikka-mods/heroku.py
Normal file
423
anon97945/hikka-mods/heroku.py
Normal file
@@ -0,0 +1,423 @@
|
||||
__version__ = (0, 0, 36)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
|
||||
import heroku3
|
||||
import requests
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import heroku, loader, main, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumHerokuManagerMod(loader.Module):
|
||||
"""
|
||||
Show Remaining Dyno Usage And Manage The Settings Of Your 🦸🏼♂️ Hero!ku Hikka Instance.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-HerokuManager",
|
||||
"developer": "@anon97945",
|
||||
"args_error": "<b>[🦸🏼♂️ Hero!ku]</b> Too many args are given.",
|
||||
"dyno_usage": (
|
||||
"<b><i><u>Dyno Usage</u></i></b>:\n"
|
||||
"\nDyno usage for <code>Hikka Userbot</code>:\n"
|
||||
" • <code>{}h {}m</code> <b>|</b> [<code>{}%</code>]\n"
|
||||
"Dyno hours quota remaining this month:\n"
|
||||
" • <code>{}h {}m</code> <b>|</b> [<code>{}%</code>]"
|
||||
),
|
||||
"get_usage": "<b>[🦸🏼♂️ Hero!ku]</b> Getting Dyno usage...</b>",
|
||||
"get_var": "<b>[🦸🏼♂️ Hero!ku]</b> Getting variable...</b>",
|
||||
"no_args": "<b>[🦸🏼♂️ Hero!ku]</b> No args are given...</b>",
|
||||
"no_force": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> You must use '--force' but this will leak"
|
||||
" credentials!</b>"
|
||||
),
|
||||
"restarted": "<b>[🦸🏼♂️ Hero!ku]</b> Restart finished.",
|
||||
"set_var": "<b>[🦸🏼♂️ Hero!ku]</b> Setting variable...</b>",
|
||||
"usage_error": "<b>Error:</b> An error occured.\n<code>{}</code>",
|
||||
"var_added": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Variable successfully added:\n"
|
||||
"<code>{}</code> = <code>{}</code>\n\n"
|
||||
"<b>The Heroku Dyno will now be restarted.</b>"
|
||||
),
|
||||
"var_changed": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Variable successfully changed to:\n"
|
||||
"<code>{}</code> = <code>{}</code>\n\n"
|
||||
"<b>The Heroku Dyno will now be restarted.</b>"
|
||||
),
|
||||
"var_deleted": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Variable successfully deleted:\n"
|
||||
"<code>{}</code>\n\n"
|
||||
"<b>The Heroku Dyno will now be restarted.</b>"
|
||||
),
|
||||
"var_not_exists": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Variable does not exist:\n<code>{}</code>"
|
||||
),
|
||||
"var_settings": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Current Config:\n<code>{}</code> = <code>{}</code>"
|
||||
),
|
||||
"wrong_platform": (
|
||||
"[🦸🏼♂️ Hero!ku] This module only works on Heroku. {} is not supported."
|
||||
),
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": (
|
||||
"Показать оставшееся использование Dyno и управлять настройками"
|
||||
" вашего экземпляра 🦸🏼♂️ Hero!ku Hikka."
|
||||
),
|
||||
"_cmd_doc_herodel": (
|
||||
" \n "
|
||||
" Удалить переменную настроек"
|
||||
" Heroku.\n "
|
||||
" - Example: .herodel <variable>"
|
||||
),
|
||||
"_cmd_doc_heroget": (
|
||||
" \n "
|
||||
" Получить переменную настроек"
|
||||
" Heroku.\n "
|
||||
" - Example: .heroget <variable>"
|
||||
),
|
||||
"_cmd_doc_herogetall": (
|
||||
" \n "
|
||||
" Получить все переменные настроек"
|
||||
" Heroku. Это может привести к утечке API!\n "
|
||||
" - Example:"
|
||||
" .herogetall --force"
|
||||
),
|
||||
"_cmd_doc_heroset": (
|
||||
" \n "
|
||||
" Установить переменную настроек"
|
||||
" Heroku.\n "
|
||||
" - Example: .heroset <variable> <some settings>"
|
||||
),
|
||||
"_cmd_doc_herousage": (
|
||||
" \n "
|
||||
" Получить использование Heroku"
|
||||
" Dyno."
|
||||
),
|
||||
"args_error": "<b>[🦸🏼♂️ Hero!ku]</b> Задано слишком много аргументов.",
|
||||
"dyno_usage": (
|
||||
"<b><i><u>Dyno Usage</u></i></b>:\n"
|
||||
"\nИспользование Дино для <code>Hikka Userbot</code>:\n"
|
||||
" • <code>{}h {}m</code> <b>|</b> [<code>{}%</code>]\n"
|
||||
"Осталось часов Дино по квоте в месяц:\n"
|
||||
" • <code>{}h {}m</code> <b>|</b> [<code>{}%</code>]"
|
||||
),
|
||||
"get_usage": "<b>[🦸🏼♂️ Hero!ku]</b> Получение использования Dyno...</b>",
|
||||
"get_var": "<b>[🦸🏼♂️ Hero!ku]</b> Получение переменной...</b>",
|
||||
"no_args": "<b>[🦸🏼♂️ Hero!ku]</b> Аргументы не указаны...</b>",
|
||||
"no_force": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Вы должны использовать '--force', но это"
|
||||
" приведет к утечке учетных данных!</b>"
|
||||
),
|
||||
"restarted": "<b>[🦸🏼♂️ Hero!ku]</b> Перезагрузка завершена.",
|
||||
"set_var": "<b>[🦸🏼♂️ Hero!ku]</b> Настройка переменной...</b>",
|
||||
"usage_error": "<b>Error:</b> Произошла ошибка.\n<code>{}</code>",
|
||||
"var_added": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Переменная успешно добавлена:\n"
|
||||
"<code>{}</code> = <code>{}</code>\n\n"
|
||||
"<b>Теперь Heroku Dyno будет перезапущен.</b>"
|
||||
),
|
||||
"var_changed": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Переменная успешно изменена на:\n"
|
||||
"<code>{}</code> = <code>{}</code>\n\n"
|
||||
"<b>Теперь Heroku Dyno будет перезапущен.</b>"
|
||||
),
|
||||
"var_deleted": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Переменная успешно удалена:\n"
|
||||
"<code>{}</code>\n\n"
|
||||
"<b>Теперь Heroku Dyno будет перезапущен.</b>"
|
||||
),
|
||||
"var_not_exists": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Переменная не существует:\n<code>{}</code>"
|
||||
),
|
||||
"var_settings": (
|
||||
"<b>[🦸🏼♂️ Hero!ku]</b> Текущая конфигурацияТекущая конфигурация:\n"
|
||||
"<code>{}</code> = <code>{}</code>"
|
||||
),
|
||||
"wrong_platform": (
|
||||
"[🦸🏼♂️ Hero!ku] Этот модуль работает только на Heroku. {} не"
|
||||
" поддерживается."
|
||||
),
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
platform = utils.get_named_platform()
|
||||
if "DYNO" not in os.environ:
|
||||
raise loader.LoadError(self.strings("wrong_platform").format(platform))
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
self._init_heroku_vars()
|
||||
|
||||
def _init_heroku_vars(self):
|
||||
self._happ, self._hconfig = heroku.get_app(api_token=main.hikka.api_token)
|
||||
self._heroku_api = "https://api.heroku.com"
|
||||
self._heroku_app_name = self._happ.name
|
||||
self._heroku_app_id = self._happ.id
|
||||
self._heroku_api_key = os.environ["heroku_api_token"]
|
||||
self._heroku = heroku3.from_key(self._heroku_api_key)
|
||||
self._heroku_app = self._heroku.app(self._heroku_app_name)
|
||||
self._platform = utils.get_named_platform()
|
||||
self._herokuid = self._heroku.account().id
|
||||
|
||||
async def herousagecmd(self, message: Message):
|
||||
"""Get Heroku Dyno Usage."""
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("get_usage", self.all_strings, message),
|
||||
)
|
||||
useragent = (
|
||||
"Mozilla/5.0 (Linux; Android 10; SM-G975F)"
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko)"
|
||||
"Chrome/80.0.3987.149 Mobile Safari/537.36"
|
||||
)
|
||||
headers = {
|
||||
"User-Agent": useragent,
|
||||
"Authorization": f"Bearer {self._heroku_api_key}",
|
||||
"Accept": "application/vnd.heroku+json; version=3.account-quotas",
|
||||
}
|
||||
path = f"/accounts/{self._herokuid}/actions/get-quota"
|
||||
r = requests.get(self._heroku_api + path, headers=headers)
|
||||
if r.status_code != 200:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"usage_error", self.all_strings, message
|
||||
).format(r.reason),
|
||||
)
|
||||
result = r.json()
|
||||
quota = result["account_quota"]
|
||||
quota_used = result["quota_used"]
|
||||
|
||||
# Used
|
||||
remaining_quota = quota - quota_used
|
||||
app_quota_used = 0
|
||||
percentage = math.floor(remaining_quota / quota * 100)
|
||||
minutes_remaining = remaining_quota / 60
|
||||
hours = math.floor(minutes_remaining / 60)
|
||||
minutes = math.floor(minutes_remaining % 60)
|
||||
|
||||
# Current
|
||||
App = result["apps"]
|
||||
try:
|
||||
for app in App:
|
||||
if app["app_uuid"] == self._heroku_app_id:
|
||||
app_quota_used = app["quota_used"]
|
||||
break
|
||||
except IndexError:
|
||||
AppQuotaUsed = 0
|
||||
AppPercentage = 0
|
||||
else:
|
||||
AppQuotaUsed = app_quota_used / 60
|
||||
AppPercentage = math.floor(app_quota_used * 100 / quota)
|
||||
AppHours = math.floor(AppQuotaUsed / 60)
|
||||
AppMinutes = math.floor(AppQuotaUsed % 60)
|
||||
# AppName = self._heroku_app_name
|
||||
await asyncio.sleep(1.5)
|
||||
return await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str("dyno_usage", self.all_strings, message).format(
|
||||
AppHours, AppMinutes, AppPercentage, hours, minutes, percentage
|
||||
),
|
||||
)
|
||||
|
||||
@loader.owner
|
||||
async def herosetcmd(self, message: Message):
|
||||
"""
|
||||
Set Heroku Settings Variable.
|
||||
- Example: .heroset <variable> <some settings>
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args := str(args).split():
|
||||
heroku_var = self._heroku_app.config()
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("set_var", self.all_strings, message),
|
||||
)
|
||||
await asyncio.sleep(1.5)
|
||||
if args[0] in heroku_var:
|
||||
msg = await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_changed", self.all_strings, message
|
||||
).format(args[0], " ".join(args[1:])),
|
||||
)
|
||||
else:
|
||||
msg = await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_added", self.all_strings, message
|
||||
).format(args[0], " ".join(args[1:])),
|
||||
)
|
||||
heroku_var[args[0]] = " ".join(args[1:])
|
||||
return
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_var", self.all_strings, message),
|
||||
)
|
||||
|
||||
@loader.owner
|
||||
async def herogetcmd(self, message: Message):
|
||||
"""
|
||||
Get Heroku Settings Variable.
|
||||
- Example: .heroget <variable>
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args := str(args).split():
|
||||
if len(args) > 1:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("args_error", self.all_strings, message),
|
||||
)
|
||||
heroku_var = self._heroku_app.config()
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("get_var", self.all_strings, message),
|
||||
)
|
||||
await asyncio.sleep(1.5)
|
||||
if args[0] in heroku_var:
|
||||
return await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_settings", self.all_strings, message
|
||||
).format(args[0], heroku_var[args[0]]),
|
||||
)
|
||||
return await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_not_exists", self.all_strings, message
|
||||
).format(args[0]),
|
||||
)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_var", self.all_strings, message),
|
||||
)
|
||||
|
||||
@loader.owner
|
||||
async def herogetallcmd(self, message: Message):
|
||||
"""
|
||||
Get All Heroku Settings Variable. This may leak API!
|
||||
- Example: .herogetall --force
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
args = str(args).split()
|
||||
if args and args[0] == "--force":
|
||||
if len(args) > 1:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("args_error", self.all_strings, message),
|
||||
)
|
||||
heroku_var = self._heroku_app.config()
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("get_var", self.all_strings, message),
|
||||
)
|
||||
await asyncio.sleep(1.5)
|
||||
cmpl_cnfg = ""
|
||||
for x in heroku_var.to_dict():
|
||||
cmpl_cnfg = (
|
||||
f"{cmpl_cnfg}<code>{x}</code>"
|
||||
+ ":\n<code>"
|
||||
+ heroku_var.to_dict()[x]
|
||||
+ "</code>\n\n"
|
||||
)
|
||||
return await utils.answer(msg, cmpl_cnfg)
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_force", self.all_strings, message),
|
||||
)
|
||||
|
||||
@loader.owner
|
||||
async def herodelcmd(self, message: Message):
|
||||
"""
|
||||
Delete Heroku Settings Variable.
|
||||
- Example: .herodel <variable>
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not (args := str(args).split()):
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_var", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
if len(args) > 1:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("args_error", self.all_strings, message),
|
||||
)
|
||||
heroku_var = self._heroku_app.config()
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("get_var", self.all_strings, message),
|
||||
)
|
||||
await asyncio.sleep(1.5)
|
||||
if args[0] in heroku_var:
|
||||
msg = await utils.answer(
|
||||
msg,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_deleted", self.all_strings, message
|
||||
).format(args[0]),
|
||||
)
|
||||
del heroku_var[args[0]]
|
||||
return
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"var_not_exists", self.all_strings, message
|
||||
).format(args[0]),
|
||||
)
|
||||
311
anon97945/hikka-mods/langreplier.py
Normal file
311
anon97945/hikka-mods/langreplier.py
Normal file
@@ -0,0 +1,311 @@
|
||||
__version__ = (0, 1, 26)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
# requires: alphabet-detector googletrans==4.0.0-rc1
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
import googletrans
|
||||
from alphabet_detector import AlphabetDetector
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumLangReplierMod(loader.Module):
|
||||
"""
|
||||
This module automatically respond to messages with unknown languages.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-LangReplier",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_active": "Whether the module is turned on (or not).",
|
||||
"_cfg_allowed_alphabets": "The list of alphabets that the module will allow.",
|
||||
"_cfg_blacklist_chats": "The list of chats that the module will watch(or not).",
|
||||
"_cfg_check_lang": (
|
||||
"Whether the module will check the language of the message(or not)."
|
||||
),
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_custom_message": "The custom message that will be sent.",
|
||||
"_cfg_lang_codes": "The list of language codes that the module will ignore.",
|
||||
"_cfg_vodka_mode": (
|
||||
"Whether the module will replace `cyrillic` in reply message with `vodka`."
|
||||
),
|
||||
"_cfg_whitelist": (
|
||||
"Whether the chatlist includes(True) or excludes(False) the chat."
|
||||
),
|
||||
"_cfg_auto_translate": (
|
||||
"Whether the module will auto translate the message(or not)."
|
||||
),
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"active",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_turned_on"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"check_language",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_check_lang"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_translate",
|
||||
"en",
|
||||
doc=lambda: self.strings("_cfg_auto_translate"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.String(length=2),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"allowed_alphabets",
|
||||
["latin"],
|
||||
lambda: self.strings("_cfg_allowed_alphabets"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.Choice(
|
||||
[
|
||||
"arabic",
|
||||
"cjk",
|
||||
"cyrillic",
|
||||
"greek",
|
||||
"hangul",
|
||||
"hebrew",
|
||||
"hiragana",
|
||||
"katakana",
|
||||
"latin",
|
||||
"thai",
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"chatlist",
|
||||
doc=lambda: self.strings("_cfg_blacklist_chats"),
|
||||
validator=loader.validators.Series(loader.validators.TelegramID()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_message",
|
||||
"<b>[🤖 Automatic]</b> <u>I don't understand"
|
||||
" {}.</u><br><b>Sorry.</b> 😉{}",
|
||||
doc=lambda: self.strings("_cfg_custom_message"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"custom_transl_msg",
|
||||
"<br><br>Translation"
|
||||
" <code>{}</code>-><code>{}</code><br><code>{}</code>",
|
||||
doc=lambda: self.strings("_cfg_custom_message"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"lang_codes",
|
||||
["en"],
|
||||
doc=lambda: self.strings("_cfg_lang_codes"),
|
||||
validator=loader.validators.Series(loader.validators.String(length=2)),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"vodka_mode",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_vodka_mode"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_whitelist"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
self._fw_protect = {}
|
||||
self._fw_protect_limit = 3
|
||||
self._ad = AlphabetDetector()
|
||||
self._tr = googletrans.Translator()
|
||||
|
||||
def _is_alphabet(self, message):
|
||||
text = self.apo_lib.utils.raw_text(message)
|
||||
denied_alphabet = ""
|
||||
text.encode("utf-8")
|
||||
detected_alphabet = self._ad.detect_alphabet(text)
|
||||
alphabet_list = [each_string.lower() for each_string in list(detected_alphabet)]
|
||||
for found_alphabet in alphabet_list:
|
||||
if (
|
||||
found_alphabet not in self.config["allowed_alphabets"]
|
||||
and found_alphabet != "mathematical"
|
||||
):
|
||||
denied_alphabet += (
|
||||
f", {found_alphabet}" if denied_alphabet else found_alphabet
|
||||
)
|
||||
allowed_alphabet = not denied_alphabet
|
||||
return allowed_alphabet, denied_alphabet, detected_alphabet
|
||||
|
||||
async def _check_lang(self, message):
|
||||
text = self.apo_lib.utils.raw_text(message)
|
||||
lang_code = (await utils.run_sync(self._tr.detect, text)).lang
|
||||
if lang_code in googletrans.LANGUAGES:
|
||||
full_lang = googletrans.LANGUAGES[lang_code]
|
||||
else:
|
||||
full_lang = lang_code
|
||||
return (
|
||||
(True, None, None)
|
||||
if lang_code in self.config["lang_codes"]
|
||||
else (False, full_lang, lang_code)
|
||||
)
|
||||
|
||||
async def clangrepliercmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.watcher("only_messages", "in")
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
not self.config["active"]
|
||||
or message.is_private
|
||||
or not message.mentioned
|
||||
or (
|
||||
self.config["whitelist"]
|
||||
and utils.get_chat_id(message) not in self.config["chatlist"]
|
||||
)
|
||||
or (
|
||||
not self.config["whitelist"]
|
||||
and utils.get_chat_id(message) in self.config["chatlist"]
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
full_lang = ""
|
||||
delay = 15
|
||||
user_id = message.sender_id
|
||||
allowed_alphabet, alphabet, detected_alphabet = self._is_alphabet(message)
|
||||
respond = not allowed_alphabet
|
||||
if self.apo_lib.utils.is_emoji(self.apo_lib.utils.raw_text(message)):
|
||||
return
|
||||
if (
|
||||
self.config["check_language"]
|
||||
and len(self.apo_lib.utils.raw_text(message).split()) >= 4
|
||||
and len(self.apo_lib.utils.raw_text(message)) >= 12
|
||||
and detected_alphabet
|
||||
and not respond
|
||||
) or (self.config["auto_translate"]):
|
||||
allowed_lang, full_lang, lang_code = await self._check_lang(message)
|
||||
if not allowed_lang:
|
||||
respond = True
|
||||
if not respond or (
|
||||
user_id in self._fw_protect
|
||||
and len(list(filter(lambda x: x > time.time(), self._fw_protect[user_id])))
|
||||
>= self._fw_protect_limit
|
||||
):
|
||||
return
|
||||
|
||||
if user_id not in self._fw_protect:
|
||||
self._fw_protect[user_id] = []
|
||||
|
||||
self._fw_protect[user_id] += [time.time() + 5 * 60]
|
||||
if self.config["auto_translate"]:
|
||||
text = self.apo_lib.utils.raw_text(message).lower()
|
||||
to_lang = self.config["auto_translate"]
|
||||
translated = (
|
||||
await utils.run_sync(
|
||||
self._tr.translate, text, dest=to_lang, src=lang_code
|
||||
)
|
||||
).text
|
||||
delay = 30
|
||||
|
||||
if self.config["check_language"] and full_lang:
|
||||
if self.config["auto_translate"]:
|
||||
msg = await message.reply(
|
||||
self.config["custom_message"]
|
||||
.format(
|
||||
full_lang,
|
||||
self.config["custom_transl_msg"].format(
|
||||
lang_code, to_lang, translated
|
||||
),
|
||||
)
|
||||
.replace("<br>", "\n")
|
||||
)
|
||||
else:
|
||||
msg = await message.reply(
|
||||
self.config["custom_message"]
|
||||
.format(full_lang)
|
||||
.replace("<br>", "\n")
|
||||
)
|
||||
else:
|
||||
if self.config["vodka_mode"] and "cyrillic" in alphabet:
|
||||
alphabet = alphabet.replace("cyrillic", "vodka")
|
||||
if self.config["auto_translate"]:
|
||||
msg = await message.reply(
|
||||
self.config["custom_message"]
|
||||
.format(
|
||||
alphabet,
|
||||
self.config["custom_transl_msg"].format(
|
||||
lang_code, to_lang, translated
|
||||
),
|
||||
)
|
||||
.replace("<br>", "\n")
|
||||
)
|
||||
else:
|
||||
msg = await message.reply(
|
||||
self.config["custom_message"].format(alphabet).replace("<br>", "\n")
|
||||
)
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
await msg.delete()
|
||||
197
anon97945/hikka-mods/lcr.py
Normal file
197
anon97945/hikka-mods/lcr.py
Normal file
@@ -0,0 +1,197 @@
|
||||
__version__ = (0, 0, 36)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from telethon import events
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumLCRMod(loader.Module):
|
||||
"""
|
||||
Telegram Login Code Reciever
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-LoginCodeReciever",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_timeout": "<b>Define a time to wait for the Code.</b>",
|
||||
"error": "<b>No Login code in the message found.</b>",
|
||||
"no_self": "<b>You can't use it on yourself.</b>",
|
||||
"not_group": "This command is for groups only.",
|
||||
"not_pchat": (
|
||||
"<b>This is no private chat. Use <code>.lcr group --force</code></b>"
|
||||
),
|
||||
"timeouterror": "<b>TimeoutError:</b>\nNo login code for {} seconds recieved.",
|
||||
"waiting": "<b>Waiting for the login code...</b>",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_timeout": "<b>Definieren Sie eine Wartezeit für den Code.</b>",
|
||||
"error": "<b>Kein Anmeldecode in der Nachricht gefunden.</b>",
|
||||
"no_self": "<b>Sie können es nicht an sich selbst verwenden.</b>",
|
||||
"not_group": "Dieser Befehl ist nur für Gruppen.",
|
||||
"not_pchat": (
|
||||
"<b>Dies ist kein privater Chat. Verwenden Sie <code>.lcr group"
|
||||
" --force</code></b>"
|
||||
),
|
||||
"timeouterror": (
|
||||
"<b>TimeoutError:</b>\nKein Anmeldecode für {} Sekunden erhalten."
|
||||
),
|
||||
"waiting": "<b>Warten auf den Anmeldecode...</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_timeout": "<b>Время ожидания кода.</b>",
|
||||
"error": "<b>Код входа не найден в сообщении.</b>",
|
||||
"no_self": "<b>Вы не можете использовать это на себе.</b>",
|
||||
"not_group": "Эта команда только для групп.",
|
||||
"not_pchat": (
|
||||
"<b>Это не приватный чат. Используйте <code>.lcr группа --force</code></b>"
|
||||
),
|
||||
"timeouterror": "<b>TimeoutError:</b>\nНе получен код за {} секунд.",
|
||||
"waiting": "<b>Ожидание кода для входа...</b>",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo LoginCodeReciever",
|
||||
"new": "Apo-LoginCodeReciever",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"timeout",
|
||||
"120",
|
||||
doc=lambda: self.strings("_cfg_timeout"),
|
||||
validator=loader.validators.Integer(minimum=0, maximum=300),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
@loader.owner
|
||||
async def lcrcmd(self, message: Message):
|
||||
"""
|
||||
Available commands:
|
||||
.lcr
|
||||
- waiting for the login code from TG service chat, use in private.
|
||||
.lcr group --force
|
||||
- waiting for the login code from TG service chat, use in group.
|
||||
"""
|
||||
|
||||
user_msg = utils.get_args_raw(message)
|
||||
chatid = utils.get_chat_id(message)
|
||||
logincode = False
|
||||
tgacc = 777000
|
||||
lc_timeout = self.config["timeout"]
|
||||
if chatid == self.tg_id:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_self", self.all_strings, message),
|
||||
)
|
||||
if user_msg not in ["", "group --force"]:
|
||||
return
|
||||
if not message.is_private and user_msg != "group --force":
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("not_pchat", self.all_strings, message),
|
||||
)
|
||||
if message.is_private and user_msg == "group --force":
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("not_group", self.all_strings, message),
|
||||
)
|
||||
async with self._client.conversation(tgacc) as conv:
|
||||
try:
|
||||
msgs = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("waiting", self.all_strings, message),
|
||||
)
|
||||
logincode = conv.wait_event(
|
||||
events.NewMessage(incoming=True, from_users=tgacc),
|
||||
timeout=lc_timeout,
|
||||
)
|
||||
logincode = await logincode
|
||||
logincodemsg = " ".join(
|
||||
(await self._client.get_messages(tgacc, 1))[0].message
|
||||
)
|
||||
if (
|
||||
logincodemsg is not None
|
||||
and sum(bool(s.isnumeric()) for s in logincodemsg) == 5
|
||||
):
|
||||
logincode = True
|
||||
if logincode:
|
||||
await self._client.send_read_acknowledge(tgacc, clear_mentions=True)
|
||||
await self._client.delete_messages(chatid, msgs)
|
||||
return await self._client.send_message(chatid, logincodemsg)
|
||||
await self._client.delete_messages(chatid, msgs)
|
||||
return await self._client.send_message(
|
||||
chatid,
|
||||
self.apo_lib.utils.get_str("error", self.all_strings, message),
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
await self._client.delete_messages(chatid, msgs)
|
||||
return await self._client.send_message(
|
||||
chatid,
|
||||
self.apo_lib.utils.get_str(
|
||||
"timeouterror", self.all_strings, message
|
||||
).format(lc_timeout),
|
||||
)
|
||||
247
anon97945/hikka-mods/linked_chat.py
Normal file
247
anon97945/hikka-mods/linked_chat.py
Normal file
@@ -0,0 +1,247 @@
|
||||
__version__ = (0, 0, 8)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2025
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from telethon.hints import EntityLike
|
||||
from telethon.tl.types import (
|
||||
Channel,
|
||||
Message,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumLinkedChatMod(loader.Module):
|
||||
"""
|
||||
Forces users to join a linked chat before they can send messages in the current chat.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-LinkedChat",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_activate_bool": "Activate the Module.",
|
||||
"_cfg_linked_chats": "Link a chat to another chat.\nFormat: <chat_id1>|<chat_id2>\nUser must be in Chat2 or will be punished in Chat1.",
|
||||
"_cfg_delete_timer": "Delete the message after x seconds.",
|
||||
"_cfg_mute_timer": "Mute the user for x minutes. 0 = no mute.",
|
||||
"_cfg_doc_raise_error": "Raise an error instead of a debug msg.",
|
||||
"triggered": (
|
||||
"{}, the comments are limited to discussiongroup members, "
|
||||
"please join our discussiongroup first."
|
||||
"\n\n👉🏻 {}\n\nRespectfully, the admins."
|
||||
),
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_activate_bool": "Aktiviere das Modul.",
|
||||
"_cfg_linked_chats": "Verlinke einen Chat mit einem anderen Chat.\nFormat: <chat_id1>|<chat_id2>\nUser muss in Chat2 sein oder wird in Chat1 bestraft.",
|
||||
"_cfg_delete_timer": "Lösche die Nachricht nach x Sekunden.",
|
||||
"_cfg_mute_timer": "Stummschalten des Users für x Minuten. 0 = kein Stummschalten.",
|
||||
"_cfg_doc_raise_error": "Werfe einen Fehler anstatt einer Debug-Nachricht.",
|
||||
"triggered": (
|
||||
"{}, die Kommentarfunktion wurde auf die Chatmitglieder begrenzt, "
|
||||
"tritt bitte zuerst unserem Hauptchat bei."
|
||||
"\n\n👉🏻 {}\n\nHochachtungsvoll, die Obrigkeit."
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"Activate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_activate_bool"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"linked_chats",
|
||||
[],
|
||||
doc=lambda: self.strings("_cfg_linked_chats"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.RegExp(
|
||||
r"^(?:\d+){8,12}[|](?:\d+){8,12}$"
|
||||
),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delete_timer",
|
||||
60,
|
||||
doc=lambda: self.strings("_cfg_delete_timer"),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"mute_timer",
|
||||
1,
|
||||
doc=lambda: self.strings("_cfg_mute_timer"),
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClas
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self._classname = self.__class__.__name__
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
self.apo_lib.watcher_q.register(self.__class__.__name__, "q_watcher")
|
||||
|
||||
async def on_unload(self):
|
||||
self.apo_lib.watcher_q.unregister(self.__class__.__name__, "q_watcher")
|
||||
return
|
||||
|
||||
async def clinkedchatcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def q_watcher(self, message: Message):
|
||||
await self.queue_handler(message)
|
||||
|
||||
async def queue_handler(self, message: Message): # sourcery skip: low-code-quality
|
||||
if (
|
||||
not isinstance(message, Message)
|
||||
or message.out
|
||||
or not message.is_channel
|
||||
or not message.is_group
|
||||
):
|
||||
return
|
||||
forced_links = self.config["linked_chats"]
|
||||
chat_id = utils.get_chat_id(message)
|
||||
user_id = await self.apo_lib.utils.get_user_id(message)
|
||||
for links in forced_links:
|
||||
chat1, chat2 = map(int, links.split("|"))
|
||||
if user_id not in [chat_id, self.inline.bot_id] and chat_id == chat1:
|
||||
try:
|
||||
chat = await self._client.get_entity(chat_id)
|
||||
chat1 = await self._client.get_entity(chat1)
|
||||
chat2 = await self._client.get_entity(chat2)
|
||||
user = await message.get_sender()
|
||||
if (
|
||||
(
|
||||
(not chat.admin_rights and not chat.creator)
|
||||
or not chat.admin_rights.delete_messages
|
||||
)
|
||||
or (
|
||||
isinstance(user, User)
|
||||
and (perms := await self.apo_lib.utils.is_member(chat2, user))
|
||||
and perms.is_admin
|
||||
)
|
||||
or (
|
||||
isinstance(user, Channel)
|
||||
and not (perms := None)
|
||||
and await self.apo_lib.utils.is_linkedchannel(chat2, user)
|
||||
or await self.apo_lib.utils.is_linkedchannel(chat1, user)
|
||||
)
|
||||
):
|
||||
return
|
||||
if (isinstance(user, User) and not perms) or isinstance(user, Channel):
|
||||
await self.punish_handler(chat1, chat2, user, message)
|
||||
except Exception as e:
|
||||
self.apo_lib.utils.log(
|
||||
logging.ERROR,
|
||||
self._libclassname,
|
||||
f"Exception occurred while processing links: {e}",
|
||||
debug_msg=True,
|
||||
)
|
||||
with contextlib.suppress(Exception):
|
||||
continue
|
||||
return
|
||||
|
||||
async def punish_handler(
|
||||
self,
|
||||
chat1: EntityLike,
|
||||
chat2: EntityLike,
|
||||
user: User,
|
||||
message: Message,
|
||||
): # sourcery skip: low-code-quality
|
||||
await self.apo_lib.utils.delete_message(message, True)
|
||||
if chat1.admin_rights.ban_users and self.config["mute_timer"] > 0:
|
||||
await self.apo_lib.utils.mute(chat1.id, user.id, self.config["mute_timer"])
|
||||
usertag = await self.apo_lib.utils.get_tag(user, True)
|
||||
link = await self.apo_lib.utils.get_invite_link(
|
||||
chat2
|
||||
) # get the invite link of the linked chat
|
||||
if message.is_reply:
|
||||
reply = await self.apo_lib.utils.get_first_msg(message)
|
||||
else:
|
||||
reply = None
|
||||
if reply and not isinstance(await reply.get_sender(), Channel):
|
||||
reply = None
|
||||
if await self.apo_lib.utils.check_inlinebot(chat1.id):
|
||||
msg = await self.inline.bot.send_message(
|
||||
chat1.id
|
||||
if str(chat1.id).startswith("-100")
|
||||
else int(f"-100{chat1.id}"),
|
||||
self.apo_lib.utils.get_str(
|
||||
"triggered", self.all_strings, message
|
||||
).format(usertag, link),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
reply_to_message_id=getattr(reply, "id", None),
|
||||
allow_sending_without_reply=True,
|
||||
)
|
||||
else:
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"triggered", self.all_strings, message
|
||||
).format(usertag, link),
|
||||
)
|
||||
await self.apo_lib.utils.delete_message(msg, self.config["delete_timer"])
|
||||
return
|
||||
164
anon97945/hikka-mods/mark_read.py
Normal file
164
anon97945/hikka-mods/mark_read.py
Normal file
@@ -0,0 +1,164 @@
|
||||
__version__ = (0, 1, 5)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
from telethon.tl.functions.messages import (
|
||||
ReadDiscussionRequest,
|
||||
)
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumMarkReadMod(loader.Module):
|
||||
"""
|
||||
This module marks chats as read.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-MarkRead",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_chat_list": "Chats to mark as read.",
|
||||
"_cfg_clear_mentions": "Whether to clear mentions or not.",
|
||||
"_cfg_clear_reactions": "Whether to clear reactions or not.",
|
||||
"_cfg_clear_pms": "Whether to clear pms or not.",
|
||||
"_cfg_whitelist": (
|
||||
"Whether the chatlist includes(True) or excludes(False) the chat."
|
||||
),
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_error_text": "The text of the error message to remove.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"chatlist",
|
||||
doc=lambda: self.strings("_cfg_chat_list"),
|
||||
validator=loader.validators.Series(loader.validators.TelegramID()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"clear_mentions",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_clear_mentions"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"clear_pms",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_clear_pms"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"clear_reactions",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_clear_reactions"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_whitelist"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClas
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cmarkreadcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.watcher("in")
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
(
|
||||
self.config["whitelist"]
|
||||
and utils.get_chat_id(message) not in self.config["chatlist"]
|
||||
)
|
||||
or (
|
||||
not self.config["whitelist"]
|
||||
and utils.get_chat_id(message) in self.config["chatlist"]
|
||||
)
|
||||
or (message.is_private and not self.config["clear_pms"])
|
||||
):
|
||||
return
|
||||
if (await message.get_chat()).forum:
|
||||
await self._client(
|
||||
ReadDiscussionRequest(
|
||||
message.chat_id,
|
||||
getattr(getattr(message, "reply_to", None), "reply_to_top_id", None)
|
||||
or getattr(
|
||||
getattr(message, "reply_to", None), "reply_to_msg_id", None
|
||||
),
|
||||
2**31 - 1,
|
||||
)
|
||||
)
|
||||
else:
|
||||
await self._client.send_read_acknowledge(
|
||||
message.chat_id,
|
||||
message,
|
||||
clear_mentions=self.config["clear_mentions"],
|
||||
clear_reactions=self.config["clear_reactions"],
|
||||
)
|
||||
return
|
||||
596
anon97945/hikka-mods/msg_merger.py
Normal file
596
anon97945/hikka-mods/msg_merger.py
Normal file
@@ -0,0 +1,596 @@
|
||||
__version__ = (0, 1, 23)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.6.1
|
||||
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
from telethon.errors import MessageIdInvalidError
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumMsgMergerMod(loader.Module):
|
||||
"""
|
||||
This module will merge own messages, if there is no message in between.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-MsgMerger",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_active": "Whether the module is turned on (or not).",
|
||||
"_cfg_blacklist_chats": "The list of chats that the module will watch(or not).",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_new_line_prefix": "The prefix which will be added to the new line.",
|
||||
"_cfg_edit_timeout": (
|
||||
"The maximum time in minuted to edit the message. 0 for no limit."
|
||||
),
|
||||
"_cfg_ignore_prefix": "The prefix to ignore the merging fully.",
|
||||
"_cfg_link_preview": (
|
||||
"Whether to send webpage previews.\nLeave empty to use"
|
||||
" automatically decide based on the messages to merge."
|
||||
),
|
||||
"_cfg_merge_own_reply": "Whether to merge any message from own reply.",
|
||||
"_cfg_merge_own_reply_msg": (
|
||||
"The message which will stay if the message is merged from own reply."
|
||||
),
|
||||
"_cfg_merge_urls": "Whether to merge messages with URLs.",
|
||||
"_cfg_new_lines": "The number of new lines to add to the message.",
|
||||
"_cfg_reverse_merge": (
|
||||
"Whether to merge into the new(True) or old(False) message."
|
||||
),
|
||||
"_cfg_skip_emoji": "Whether to skip the merging of messages with single emoji.",
|
||||
"_cfg_skip_reactions": (
|
||||
"Whether to skip the merging of messages with reactions."
|
||||
),
|
||||
"_cfg_skip_length": "The length of the message to skip the merging.",
|
||||
"_cfg_skip_prefix": "The prefix to skip the merging.",
|
||||
"_cfg_skip_reply": "Whether to skip the merging of messages with reply.",
|
||||
"_cfg_whitelist": (
|
||||
"Whether the chatlist includes(True) or excludes(False) the chat."
|
||||
),
|
||||
"undo_merge_fail": "<b>Failed to unmerge the messages.</b>",
|
||||
"nothing_to_merge": "<b>Nothing to merge.</b>",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo MsgMerger",
|
||||
"new": "Apo-MsgMerger",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.merged_msgs = {}
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"active",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_active"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"chatlist",
|
||||
doc=lambda: self.strings("_cfg_blacklist_chats"),
|
||||
validator=loader.validators.Series(loader.validators.TelegramID()),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"edit_timeout",
|
||||
2,
|
||||
doc=lambda: self.strings("_cfg_edit_timeout"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Integer(minimum=1),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_prefix",
|
||||
["+"],
|
||||
doc=lambda: self.strings("_cfg_ignore_prefix"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.Union(
|
||||
loader.validators.String(length=1),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"link_preview",
|
||||
doc=lambda: self.strings("_cfg_link_preview"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Boolean(),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"merge_own_reply",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_merge_own_reply"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"merge_urls",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_merge_urls"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"new_line_pref",
|
||||
">",
|
||||
doc=lambda: self.strings("_cfg_new_line_prefix"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.String(length=1),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"new_lines",
|
||||
1,
|
||||
doc=lambda: self.strings("_cfg_new_lines"),
|
||||
validator=loader.validators.Integer(minimum=1, maximum=2),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"own_reply_msg",
|
||||
"<code>☝️</code>",
|
||||
doc=lambda: self.strings("_cfg_merge_own_reply_msg"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.String(),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"reverse_merge",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_reverse_merge"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skip_emoji",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_skip_emoji"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skip_length",
|
||||
doc=lambda: self.strings("_cfg_skip_length"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.Integer(minimum=0),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skip_prefix",
|
||||
[">"],
|
||||
doc=lambda: self.strings("_cfg_skip_prefix"),
|
||||
validator=loader.validators.Series(
|
||||
loader.validators.Union(
|
||||
loader.validators.String(length=1),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skip_reactions",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_skip_reactions"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"skip_reply",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_skip_reply"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_whitelist"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
self.apo_lib.watcher_q.register(self.__class__.__name__)
|
||||
|
||||
async def on_unload(self):
|
||||
self.apo_lib.watcher_q.unregister(self.__class__.__name__)
|
||||
|
||||
async def cmsgmergercmd(self, message: Message):
|
||||
"""
|
||||
open the config of the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def mergecmd(self, message: Message):
|
||||
"""
|
||||
merge all messages of own until the last message of another user.
|
||||
"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
text = ""
|
||||
del_msgs = []
|
||||
merge_msgs = []
|
||||
try:
|
||||
msgs = await self._client.get_messages(chat_id, limit=100)
|
||||
for i in range(-len(msgs), -1):
|
||||
if (
|
||||
not isinstance(msgs[i], Message)
|
||||
or not msgs[i].out
|
||||
or msgs[i].forward
|
||||
or msgs[i].via_bot
|
||||
or msgs[i].sender_id != self.tg_id
|
||||
or (self.config["skip_reactions"] and msgs[i].reactions)
|
||||
or (msgs[i].media and not getattr(msgs[i].media, "webpage", False))
|
||||
or (not self.config["merge_own_reply"] and msgs[i].is_reply)
|
||||
):
|
||||
break
|
||||
if i != -len(msgs):
|
||||
del_msgs += [msgs[i].id]
|
||||
merge_msgs += [msgs[i]]
|
||||
if merge_msgs:
|
||||
for msg in reversed(merge_msgs):
|
||||
if text:
|
||||
text += "\n" * self.config["new_lines"]
|
||||
if self.config["new_line_pref"]:
|
||||
text += self.config["new_line_pref"]
|
||||
if (
|
||||
not text
|
||||
and self.apo_lib.utils.raw_text(msg).startswith(
|
||||
self.get_prefix()
|
||||
)
|
||||
and not self.config["new_line_pref"]
|
||||
):
|
||||
text += ">"
|
||||
text += self.apo_lib.utils.raw_text(msg, True)
|
||||
await self._client.delete_messages(chat_id, del_msgs)
|
||||
if not text:
|
||||
text = self.apo_lib.utils.get_str(
|
||||
"nothing_to_merge", self.all_strings, message
|
||||
)
|
||||
return await utils.answer(message, text)
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
async def unmergecmd(self, message: Message):
|
||||
"""
|
||||
unmerge the messages.
|
||||
"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
if utils.get_chat_id(message) in self.merged_msgs:
|
||||
try:
|
||||
for key, msg_value in self.merged_msgs[chat_id]["message"].items():
|
||||
if key == "text":
|
||||
msg_text = msg_value
|
||||
elif key == "link_preview":
|
||||
msg_link_preview = msg_value
|
||||
for key, msg_value in self.merged_msgs[chat_id]["last_msg"].items():
|
||||
if key == "id":
|
||||
last_msg_id = msg_value
|
||||
elif key == "text":
|
||||
last_msg_text = msg_value
|
||||
elif key == "link_preview":
|
||||
last_msg_link_preview = msg_value
|
||||
await self.client.edit_message(
|
||||
chat_id,
|
||||
last_msg_id,
|
||||
last_msg_text,
|
||||
link_preview=last_msg_link_preview,
|
||||
)
|
||||
await self.client.edit_message(
|
||||
message, msg_text, link_preview=msg_link_preview
|
||||
)
|
||||
except MessageIdInvalidError:
|
||||
await utils.answer(message, self.strings("undo_merge_fail"))
|
||||
else:
|
||||
await utils.answer(message, self.strings("undo_merge_fail"))
|
||||
self.merged_msgs.clear()
|
||||
|
||||
async def q_watcher(self, message: Message):
|
||||
await self._queue_handler(message)
|
||||
|
||||
async def _queue_handler(self, message: Message):
|
||||
if (
|
||||
not self.config["active"]
|
||||
or not isinstance(message, Message)
|
||||
or not message.out
|
||||
or message.forward
|
||||
or message.via_bot
|
||||
or self.apo_lib.utils.raw_text(message).startswith(self.get_prefix())
|
||||
):
|
||||
return
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
if (self.config["whitelist"] and chat_id not in self.config["chatlist"]) or (
|
||||
not self.config["whitelist"] and chat_id in self.config["chatlist"]
|
||||
):
|
||||
return
|
||||
|
||||
if self.config["ignore_prefix"] and any(
|
||||
self.apo_lib.utils.raw_text(message).startswith(prefix)
|
||||
for prefix in self.config["ignore_prefix"]
|
||||
):
|
||||
return
|
||||
|
||||
if self.config["skip_prefix"] and (
|
||||
found_prefix := next(
|
||||
(
|
||||
prefix
|
||||
for prefix in self.config["skip_prefix"]
|
||||
if self.apo_lib.utils.raw_text(message).startswith(prefix)
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
text = message.text.replace(utils.escape_html(found_prefix), "", 1)
|
||||
if len(text) > 0:
|
||||
with contextlib.suppress(Exception):
|
||||
await message.edit(text)
|
||||
return
|
||||
|
||||
if (
|
||||
(
|
||||
self.config["skip_length"]
|
||||
and len(self.apo_lib.utils.remove_html(message.text))
|
||||
>= self.config["skip_length"]
|
||||
)
|
||||
or (self.config["skip_reactions"] and message.reactions)
|
||||
or (
|
||||
message.media
|
||||
and not getattr(message.media, "webpage", False)
|
||||
or (
|
||||
not self.config["merge_urls"]
|
||||
and self.apo_lib.utils.get_entityurls(message)
|
||||
)
|
||||
)
|
||||
):
|
||||
return
|
||||
last_msg = None
|
||||
try:
|
||||
if utils.get_topic(message):
|
||||
last_msg_iter = await self._client.get_messages(
|
||||
chat_id, limit=5, reply_to=utils.get_topic(message)
|
||||
)
|
||||
else:
|
||||
last_msg_iter = await self._client.get_messages(chat_id, limit=5)
|
||||
for i in range(-4, -1):
|
||||
if last_msg_iter[i].id != message.id:
|
||||
last_msg = last_msg_iter[i]
|
||||
break
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["merge_own_reply"]
|
||||
and message.is_reply
|
||||
and (
|
||||
not message.reply_to_msg_id
|
||||
or message.reply_to_msg_id != utils.get_topic(message)
|
||||
)
|
||||
):
|
||||
last_msg_reply = await message.get_reply_message()
|
||||
last_msg = last_msg_reply
|
||||
else:
|
||||
last_msg_reply = None
|
||||
|
||||
if (
|
||||
(
|
||||
self.config["skip_emoji"]
|
||||
and (
|
||||
self.apo_lib.utils.is_emoji(self.apo_lib.utils.raw_text(message))
|
||||
or self.apo_lib.utils.is_emoji(
|
||||
self.apo_lib.utils.raw_text(last_msg)
|
||||
)
|
||||
)
|
||||
)
|
||||
or (
|
||||
self.config["skip_reply"]
|
||||
and not self.config["merge_own_reply"]
|
||||
and (message.is_reply or last_msg.is_reply)
|
||||
and (
|
||||
not message.reply_to_msg_id
|
||||
or message.reply_to_msg_id != utils.get_topic(message)
|
||||
)
|
||||
)
|
||||
or (
|
||||
last_msg.is_reply
|
||||
and message.is_reply
|
||||
and (
|
||||
not message.reply_to_msg_id
|
||||
or message.reply_to_msg_id != utils.get_topic(message)
|
||||
)
|
||||
and not self.config["merge_own_reply"]
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
if (
|
||||
last_msg.sender_id != self.tg_id
|
||||
or not isinstance(last_msg, Message)
|
||||
or last_msg.via_bot
|
||||
or last_msg.fwd_from
|
||||
or (self.config["skip_reactions"] and last_msg.reactions)
|
||||
or (
|
||||
last_msg.media
|
||||
and not getattr(last_msg.media, "webpage", False)
|
||||
or (
|
||||
not self.config["merge_urls"]
|
||||
and self.apo_lib.utils.get_entityurls(last_msg)
|
||||
)
|
||||
)
|
||||
or self.apo_lib.utils.remove_html(last_msg.text)[0] == self.get_prefix()
|
||||
):
|
||||
return
|
||||
|
||||
if self.config["ignore_prefix"] and any(
|
||||
self.apo_lib.utils.raw_text(last_msg).startswith(prefix)
|
||||
for prefix in self.config["ignore_prefix"]
|
||||
):
|
||||
return
|
||||
|
||||
if (
|
||||
self.config["edit_timeout"]
|
||||
and (
|
||||
datetime.now(timezone.utc) - (last_msg.edit_date or last_msg.date)
|
||||
).total_seconds()
|
||||
> self.config["edit_timeout"] * 60
|
||||
) and (
|
||||
self.config["merge_own_reply"]
|
||||
and (
|
||||
not message.is_reply
|
||||
or message.reply_to_msg_id
|
||||
and message.reply_to_msg_id == utils.get_topic(message)
|
||||
)
|
||||
or not self.config["merge_own_reply"]
|
||||
):
|
||||
return
|
||||
|
||||
text = last_msg.text
|
||||
text += "\n" * self.config["new_lines"]
|
||||
|
||||
if self.config["new_line_pref"]:
|
||||
text += self.config["new_line_pref"]
|
||||
text += message.text
|
||||
|
||||
if (
|
||||
(message.is_reply and message.reply_to_msg_id != utils.get_topic(message))
|
||||
or self.config["reverse_merge"]
|
||||
) and (
|
||||
not self.config["merge_own_reply"]
|
||||
or not message.is_reply
|
||||
or message.reply_to_msg_id == utils.get_topic(message)
|
||||
):
|
||||
message, last_msg = last_msg, message
|
||||
message_text = last_msg.text
|
||||
last_msg_text = message.text
|
||||
else:
|
||||
message_text = message.text
|
||||
last_msg_text = last_msg.text
|
||||
if self.config["link_preview"] is None:
|
||||
link_preview = getattr(message.media, "webpage", False) or getattr(
|
||||
last_msg.media, "webpage", False
|
||||
)
|
||||
else:
|
||||
link_preview = bool(self.config["link_preview"])
|
||||
try:
|
||||
if self.config["reverse_merge"] and (
|
||||
self.config["merge_own_reply"]
|
||||
and (
|
||||
(
|
||||
last_msg.is_reply
|
||||
and last_msg.reply_to_msg_id != utils.get_topic(last_msg)
|
||||
)
|
||||
or (
|
||||
message.is_reply
|
||||
and (
|
||||
not message.reply_to_msg_id
|
||||
or message.reply_to_msg_id != utils.get_topic(message)
|
||||
)
|
||||
)
|
||||
)
|
||||
):
|
||||
if last_msg.is_reply and last_msg.reply_to_msg_id != utils.get_topic(
|
||||
last_msg
|
||||
):
|
||||
reply = await last_msg.get_reply_message()
|
||||
else:
|
||||
reply = await message.get_reply_message()
|
||||
await last_msg.delete()
|
||||
msg = await last_msg.client.send_message(
|
||||
chat_id, text, reply_to=reply, link_preview=link_preview
|
||||
)
|
||||
else:
|
||||
msg = await last_msg.edit(text, link_preview=link_preview)
|
||||
|
||||
if msg.out:
|
||||
if (
|
||||
self.config["merge_own_reply"]
|
||||
and self.config["own_reply_msg"]
|
||||
and not self.config["reverse_merge"]
|
||||
and (
|
||||
message.is_reply
|
||||
and (
|
||||
not message.reply_to_msg_id
|
||||
or message.reply_to_msg_id != utils.get_topic(message)
|
||||
)
|
||||
)
|
||||
):
|
||||
await message.edit(
|
||||
self.config["own_reply_msg"], link_preview=link_preview
|
||||
)
|
||||
else:
|
||||
await message.delete()
|
||||
self.merged_msgs.clear()
|
||||
self.merged_msgs[chat_id] = {
|
||||
"message": {
|
||||
"text": message_text,
|
||||
"link_preview": link_preview,
|
||||
},
|
||||
"last_msg": {
|
||||
"id": msg.id or last_msg.id,
|
||||
"text": last_msg_text,
|
||||
"link_preview": link_preview,
|
||||
},
|
||||
}
|
||||
return
|
||||
except Exception as exc:
|
||||
self.apo_lib.utils.log(
|
||||
logging.DEBUG,
|
||||
__name__,
|
||||
f"Edit failed on last_msg:\n{str(exc)}",
|
||||
debug_msg=True,
|
||||
)
|
||||
return
|
||||
147
anon97945/hikka-mods/no_ttl.py
Normal file
147
anon97945/hikka-mods/no_ttl.py
Normal file
@@ -0,0 +1,147 @@
|
||||
__version__ = (0, 0, 2)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2025
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message, UpdateNewChannelMessage, MessageService
|
||||
from telethon.tl.functions.messages import SetHistoryTTLRequest
|
||||
from telethon.tl.functions.channels import GetFullChannelRequest
|
||||
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NoTTLMod(loader.Module):
|
||||
"""
|
||||
Send messages without TTL.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-NoTTL",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cmd_nottl": "Send a message without TTL. Can be used as a reply or with arguments.",
|
||||
"_cmd_cnottl": "Open the config for the module.",
|
||||
"no_args": "No args are given or not replied to a message...",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_cst_auto_migrate": "Ob Änderungen beim Start automatisch migriert werden sollen.",
|
||||
"_cmd_notll": "Senden Sie eine Nachricht ohne TTL. Kann als Antwort oder mit Argumenten verwendet werden.",
|
||||
"_cmd_cnottl": "Öffnen Sie die Konfiguration für das Modul.",
|
||||
"no_args": "Keine Argumente angegeben oder nicht auf eine Nachricht geantwortet...",
|
||||
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_cst_auto_migrate": "Автоматически мигрировать определенные изменения при запуске.",
|
||||
"_cmd_notll": "Отправить сообщение без TTL. Может использоваться в качестве ответа или с аргументами.",
|
||||
"_cmd_cnottl": "Открыть конфигурацию модуля.",
|
||||
"no_args": "ргументы не указаны или не ответили на сообщение...",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClas
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def on_dlmod(self, client, _):
|
||||
return
|
||||
|
||||
async def cnottlcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def nottlcmd(self, message: Message):
|
||||
"""
|
||||
Command to send a message without TTL.
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message() if message.is_reply else None
|
||||
|
||||
if not args and not reply:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_args", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
old_ttl = (await self.client(GetFullChannelRequest(chat_id))).full_chat.ttl_period
|
||||
|
||||
ttl_msg_id = await self._set_ttl(chat_id, 0)
|
||||
|
||||
await self.client.send_message(chat_id, reply if reply else args, reply_to=utils.get_topic(message))
|
||||
|
||||
restore_ttl_msg_id = await self._set_ttl(chat_id, old_ttl)
|
||||
|
||||
await self._client.delete_messages(chat_id, [ttl_msg_id, restore_ttl_msg_id, message.id])
|
||||
|
||||
async def _set_ttl(self, chat_id: int, ttl: int) -> int:
|
||||
"""
|
||||
Set the TTL for the chat and return the message ID of the TTL update.
|
||||
"""
|
||||
ttl_req = await self.client(SetHistoryTTLRequest(chat_id, ttl))
|
||||
ttl_msg = next(
|
||||
(update for update in ttl_req.updates if isinstance(update, UpdateNewChannelMessage) and isinstance(update.message, MessageService)),
|
||||
None
|
||||
)
|
||||
return ttl_msg.message.id if ttl_msg else None
|
||||
324
anon97945/hikka-mods/pmlog.py
Normal file
324
anon97945/hikka-mods/pmlog.py
Normal file
@@ -0,0 +1,324 @@
|
||||
__version__ = (0, 1, 9)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
|
||||
from telethon.errors import MessageIdInvalidError
|
||||
from telethon.tl.types import Message, User
|
||||
|
||||
from telethon.tl.functions.messages import (
|
||||
ReadDiscussionRequest,
|
||||
)
|
||||
from telethon.tl.functions.channels import (
|
||||
GetForumTopicsRequest,
|
||||
CreateForumTopicRequest,
|
||||
ToggleForumRequest,
|
||||
EditForumTopicRequest,
|
||||
)
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumPMLogMod(loader.Module):
|
||||
"""
|
||||
Logs PMs to a group/channel
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-PMLog",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_bots": "Whether to log bots or not.",
|
||||
"_cfg_loglist": "Add telegram id's to log them.",
|
||||
"_cfg_selfdestructive": (
|
||||
"Whether selfdestructive media should be logged or not. This"
|
||||
" violates TG TOS!"
|
||||
),
|
||||
"_cfg_whitelist": (
|
||||
"Whether the list is a for excluded(True) or included(False) chats."
|
||||
),
|
||||
"_cfg_reatime_usernames": "Whether to update the topic names in realtime or not.",
|
||||
"_cfg_mark_read": "Whether to mark the messsages in the log as read or not.",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_bots": "Ob Bots geloggt werden sollen oder nicht.",
|
||||
"_cfg_loglist": "Fügen Sie Telegram-IDs hinzu, um sie zu protokollieren.",
|
||||
"_cfg_selfdestructive": (
|
||||
"Ob selbstzerstörende Medien geloggt werden sollen oder nicht. Dies"
|
||||
" verstößt gegen die TG TOS!"
|
||||
),
|
||||
"_cfg_whitelist": (
|
||||
"Ob die Liste für ausgeschlossene (Wahr) oder eingeschlossene"
|
||||
" (Falsch) Chats ist."
|
||||
),
|
||||
"_cmd_doc_cpmlog": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_bots": "Логировать ли ботов или нет",
|
||||
"_cfg_loglist": "Добавьте айди Telegram, чтобы зарегистрировать их",
|
||||
"_cfg_selfdestructive": (
|
||||
"Должны ли самоуничтожающиеся медиафайлы регистрироваться или нет."
|
||||
" Это нарушает «Условия использования Telegram» (ToS)"
|
||||
),
|
||||
"_cfg_whitelist": "Использовать белый список (True) или черный (False).",
|
||||
"_cmd_doc_cpmlog": "Это откроет конфиг для модуля.",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo PMLogger",
|
||||
"new": "Apo-PMLog",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"log_bots",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_bots"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_list",
|
||||
[777000],
|
||||
doc=lambda: self.strings("_cfg_loglist"),
|
||||
validator=loader.validators.Series(
|
||||
validator=loader.validators.TelegramID()
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_self_destr",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_selfdestructive"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"realtime_names",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_reatime_usernames"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"whitelist",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_whitelist"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"mark_read",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_mark_read"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
self.apo_lib.watcher_q.register(self.__class__.__name__)
|
||||
self._topic_cache = {}
|
||||
self.c, _ = await utils.asset_channel(
|
||||
self._client,
|
||||
"[Apo] PMLog",
|
||||
"Chat for logged PMs. The ID's in the topic titles are the user ID's, don't remove them!",
|
||||
silent=True,
|
||||
invite_bot=False,
|
||||
)
|
||||
if not self.c.forum:
|
||||
await self._client(ToggleForumRequest(self.c.id, True))
|
||||
|
||||
async def on_unload(self):
|
||||
self.apo_lib.watcher_q.unregister(self.__class__.__name__)
|
||||
|
||||
async def cpmlogcmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def _topic_cacher(self, user: User):
|
||||
if user.id not in self._topic_cache:
|
||||
forum = await self._client(
|
||||
GetForumTopicsRequest(
|
||||
channel=self.c.id,
|
||||
offset_date=datetime.now(),
|
||||
offset_id=0,
|
||||
offset_topic=0,
|
||||
limit=0,
|
||||
)
|
||||
)
|
||||
for topic in forum.topics:
|
||||
if str(user.id) in topic.title:
|
||||
self._topic_cache[user.id] = topic
|
||||
break
|
||||
return user.id in self._topic_cache
|
||||
|
||||
async def _topic_creator(self, user: User):
|
||||
await self._client(
|
||||
CreateForumTopicRequest(
|
||||
channel=self.c.id,
|
||||
title=f"{user.first_name} ({user.id})",
|
||||
icon_color=42,
|
||||
)
|
||||
)
|
||||
return await self._topic_cacher(user)
|
||||
|
||||
async def _topic_handler(self, user: User, message: Message):
|
||||
if not await self._topic_cacher(user): # create topic if not exists
|
||||
await self._topic_creator(user)
|
||||
if (
|
||||
self.config["realtime_names"]
|
||||
and self._topic_cache[user.id].title != f"{user.first_name} ({user.id})"
|
||||
):
|
||||
await self._client(
|
||||
EditForumTopicRequest(
|
||||
channel=self.c.id,
|
||||
topic_id=self._topic_cache[user.id].id,
|
||||
title=f"{user.first_name} ({user.id})",
|
||||
)
|
||||
)
|
||||
self._topic_cache[user.id].title = f"{user.first_name} ({user.id})"
|
||||
await message.client.send_message(
|
||||
self.c.id,
|
||||
f"New name:\n<code>{user.first_name} ({user.id})</code>\n\nOld name:\n<code>{self._topic_cache[user.id].title}</code>",
|
||||
reply_to=self._topic_cache[user.id].id,
|
||||
)
|
||||
return True
|
||||
|
||||
async def q_watcher(self, message: Message):
|
||||
try:
|
||||
await self._queue_handler(message)
|
||||
except Exception as exc: # skipcq: PYL-W0703
|
||||
if "topic was deleted" in str(exc):
|
||||
self._topic_cache.pop(utils.get_chat_id(message))
|
||||
await self._queue_handler(message)
|
||||
return
|
||||
|
||||
async def _queue_handler(self, message: Message):
|
||||
if not isinstance(message, Message) or not message.is_private:
|
||||
return
|
||||
user = await message.get_sender()
|
||||
if user.id == self.tg_id:
|
||||
user = await message.get_chat()
|
||||
if user.bot and not self.config["log_bots"] or user.id == self.tg_id:
|
||||
return
|
||||
chatidindb = utils.get_chat_id(message) in (self.config["log_list"] or [])
|
||||
if (
|
||||
self.config["whitelist"]
|
||||
and chatidindb
|
||||
or not self.config["whitelist"]
|
||||
and not chatidindb
|
||||
):
|
||||
return
|
||||
try:
|
||||
if await self._topic_handler(user, message):
|
||||
msg = await message.forward_to(
|
||||
self.c.id, top_msg_id=self._topic_cache[user.id].id
|
||||
)
|
||||
if self.config["mark_read"]:
|
||||
await self._client(
|
||||
ReadDiscussionRequest(
|
||||
self.c.id,
|
||||
getattr(
|
||||
getattr(msg, "reply_to", None),
|
||||
"reply_to_top_id",
|
||||
None,
|
||||
)
|
||||
or getattr(
|
||||
getattr(msg, "reply_to", None),
|
||||
"reply_to_msg_id",
|
||||
None,
|
||||
),
|
||||
2**31 - 1,
|
||||
)
|
||||
)
|
||||
except MessageIdInvalidError:
|
||||
if not message.file or not self.config["log_self_destr"]:
|
||||
return
|
||||
file = BytesIO()
|
||||
caption = f"{utils.escape_html(message.text)}"
|
||||
await self._client.download_file(message, file)
|
||||
file.name = (
|
||||
message.file.name or f"{message.file.media.id}{message.file.ext}"
|
||||
)
|
||||
file.seek(0)
|
||||
msg = await self._client.send_file(
|
||||
self.c.id,
|
||||
file,
|
||||
force_document=True,
|
||||
caption=caption,
|
||||
reply_to=self._topic_cache[user.id].id,
|
||||
)
|
||||
await self._client(
|
||||
ReadDiscussionRequest(
|
||||
self.c.id,
|
||||
getattr(
|
||||
getattr(msg, "reply_to", None),
|
||||
"reply_to_top_id",
|
||||
None,
|
||||
)
|
||||
or getattr(
|
||||
getattr(msg, "reply_to", None),
|
||||
"reply_to_msg_id",
|
||||
None,
|
||||
),
|
||||
2**31 - 1,
|
||||
)
|
||||
)
|
||||
630
anon97945/hikka-mods/purge.py
Normal file
630
anon97945/hikka-mods/purge.py
Normal file
@@ -0,0 +1,630 @@
|
||||
__version__ = (0, 1, 34)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
from telethon.hints import EntityLike
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def represents_int(s: str) -> bool:
|
||||
try:
|
||||
loader.validators.Integer().validate(s)
|
||||
return True
|
||||
except loader.validators.ValidationError:
|
||||
return False
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumPurgeMod(loader.Module):
|
||||
"""
|
||||
Userbot module for purging unneeded messages(usually spam or ot).
|
||||
Check the `.config apodiktum purge` to enable/disable logging.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-Purge",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_log_edit": "Log `edit` as info.",
|
||||
"_cfg_log_purge": "Log purge `count` as info.",
|
||||
"_cfg_log_purgeme": "Log `purgeme `count as info.",
|
||||
"_cfg_log_sd": "Log `sd` as info.",
|
||||
"edit_success": (
|
||||
"Edit done successfully.\nOld message:\n{}\n\n\nNew message:\n{}"
|
||||
),
|
||||
"err_cmd_wrong": "<b>Your command was wrong.</b>",
|
||||
"err_purge_start": "<b>Please reply to a message to start purging.</b>",
|
||||
"no_int": "<b>Your input was no integer.</b>",
|
||||
"permerror": "<b>You don't have permission to use this command.</b>",
|
||||
"purge_cmpl": "<b>Purge complete!</b>\nPurged <code>{}</code> messages.",
|
||||
"purge_success": "Purge of {} messages done successfully.",
|
||||
"sd_success": "Message after {} seconds successfully deleted.",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_log_edit": "Protokollieren Sie `edit` Nachrichten als Info.",
|
||||
"_cfg_log_purge": (
|
||||
"Protokollieren Sie die Anzahl der `purge` Nachrichten als Info."
|
||||
),
|
||||
"_cfg_log_purgeme": (
|
||||
"Protokollieren Sie die Anzahl der `purgeme` Nachrichten als Info."
|
||||
),
|
||||
"_cfg_log_sd": "Protokollieren `self-destructive` Nachrichten als Info.",
|
||||
"_cls_doc:": (
|
||||
"Module zum entfernen von Nachrichten(normalerweise spam,"
|
||||
" etc.).\nCheck `.config apodiktum purge` um das Protokollieren zu"
|
||||
" aktivieren/deaktivieren."
|
||||
),
|
||||
"_cmd_doc_cpurge": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
"_cmd_doc_del": (
|
||||
"Löscht die beantwortete Nachricht.\n- Verwendung: .adel <Antwort>"
|
||||
),
|
||||
"_cmd_doc_edit": (
|
||||
"Bearbeitet die letzte Nachricht.\n- Verwendung: .edit <Nachricht>"
|
||||
),
|
||||
"_cmd_doc_purge": (
|
||||
"Löscht alle Nachrichten bis zu und inklusive der Antwort.\n"
|
||||
"- Verwendung: .apurge <Antwort>"
|
||||
),
|
||||
"_cmd_doc_purgeme": (
|
||||
"Löscht x (oder alle) Nachrichten von dir.\n"
|
||||
"- Verwendung: .purgeme <anzahl/all>"
|
||||
),
|
||||
"_cmd_doc_purgeuser": (
|
||||
"Löscht alle Nachrichten von einem Nutzer.\n"
|
||||
"- Verwendung: .purgeuser <Antwort>"
|
||||
),
|
||||
"_cmd_doc_sd": (
|
||||
"Löscht die letzte Nachricht nach x Sekunden. Funktioniert auch mit"
|
||||
" Medien.\nVerwendung: .sd <Sekunden> <Nachricht>"
|
||||
),
|
||||
"_cmd_doc_spurge": (
|
||||
"Löscht alle Nachrichten bis zu und inklusive der Antwort ohne"
|
||||
" Benachrichtigung.\n- Verwendung: .spurge <Antwort>"
|
||||
),
|
||||
"_cmd_doc_spurgeme": (
|
||||
"Löscht x (oder alle) Nachrichten von dir ohne Benachrichtigung.\n"
|
||||
"- Verwendung: .spurgeme <anzahl/all>"
|
||||
),
|
||||
"_cmd_doc_spurgeuser": (
|
||||
"Löscht alle Nachrichten von einem Nutzer ohne Benachrichtigung.\n"
|
||||
"- Verwendung: .spurgeuser <Antwort>"
|
||||
),
|
||||
"edit_success": (
|
||||
"Bearbeitung erfolgreich.\nAlte Nachricht:\n{}\n\n\nNeue Nachricht:\n{}"
|
||||
),
|
||||
"err_cmd_wrong": "<b>Deine Eingabe war falsch.</b>",
|
||||
"err_purge_start": (
|
||||
"<b>Bitte antworte auf eine Nachricht, um die Löschung zu starten.</b>"
|
||||
),
|
||||
"no_int": "<b>Dein Eingabe war kein Integer.</b>",
|
||||
"permerror": "<b>Du hast keine Berechtigung, diesen Befehl zu verwenden.</b>",
|
||||
"purge_cmpl": (
|
||||
"<b>Purge fertig!</b>\n<code>{}</code> Nachrichten wurden gelöscht."
|
||||
),
|
||||
"purge_success": "Löschung von {} Nachrichten erfolgreich durchgeführt.",
|
||||
"sd_success": "Nachricht nach {} Sekunden erfolgreich gelöscht.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_log_edit": "Логировать редактирование сообщения как info.",
|
||||
"_cfg_log_purge": "Логировать количество очищенных сообщений как info.",
|
||||
"_cfg_log_purgeme": (
|
||||
"Логировать количество удаленных сообщений от вас как info."
|
||||
),
|
||||
"_cfg_log_sd": "Логировать создание сообщения как info.",
|
||||
"_cls_doc": (
|
||||
"Модуль для очистки спама и т.д.Проверьте `.config apodiktum"
|
||||
" purge`, чтобы включить/выключить ведение журнала."
|
||||
),
|
||||
"_cmd_doc_cpurge": "Это откроет конфиг для модуля.",
|
||||
"_cmd_doc_edit": (
|
||||
"Редактирует последнее сообщение.\n- Использование: .aedit <сообщение>"
|
||||
),
|
||||
"_cmd_doc_purge": (
|
||||
"Удаляет все сообщения до и включая ответ.\n"
|
||||
"- Использование: .apurge <реплай>"
|
||||
),
|
||||
"_cmd_doc_purgeme": (
|
||||
"Удаляет x (или все) сообщений от вас.\n"
|
||||
"- Использование: .purgeme <количество/все>"
|
||||
),
|
||||
"_cmd_doc_purgeuser": (
|
||||
"Удаляет все сообщения от определенного пользователя.\n"
|
||||
"- Использование: .purgeuser <реплай>"
|
||||
),
|
||||
"_cmd_doc_sd": (
|
||||
"Удаляет последнее сообщение через x секунд.\n"
|
||||
"- Использование: .sd <секунды> <сообщение>"
|
||||
),
|
||||
"_cmd_doc_spurge": (
|
||||
"Удаляет все сообщения до и включая ответ без оповещения.\n"
|
||||
"- Использование: .spurge <реплай>"
|
||||
),
|
||||
"_cmd_doc_spurgeme": (
|
||||
"Удаляет x (или все) сообщений от вас без оповещения.\n"
|
||||
"- Использование: .spurgeme <количество/все>"
|
||||
),
|
||||
"_cmd_doc_spurgeuser": (
|
||||
"Удаляет все сообщения от определенного пользователя без"
|
||||
" оповещения.\n- Использование: .spurgeuser <реплай>"
|
||||
),
|
||||
"edit_success": (
|
||||
"Редактирование завершено успешно.\n"
|
||||
"Старое сообщение:\n{}\n\n\nНовое сообщение:\n{}"
|
||||
),
|
||||
"err_cmd_wrong": "<b>Ваш команда была неверной.</b>",
|
||||
"err_purge_start": (
|
||||
"<b>Пожалуйста, ответьте на сообщение для начала очистки.</b>"
|
||||
),
|
||||
"no_int": "<b>Введенное значение не является целым числом (int)</b>",
|
||||
"permerror": "<b>У вас недостаточно прав для использования этой команды.</b>",
|
||||
"purge_cmpl": "<b>Очистка завершена!</b>\nОчищено <code>{}</code> сообщений.",
|
||||
"purge_success": "Очистка {} сообщений завершена успешно.",
|
||||
"sd_success": "Сообщение после {} секунд успешно удалено.",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo Purge",
|
||||
"new": "Apo-Purge",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"log_edit",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_log_edit"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_purge",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_log_purge"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_purgeme",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_log_purgeme"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"log_sd",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_log_sd"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def _purge_user_messages(
|
||||
self,
|
||||
chat: EntityLike,
|
||||
user_id: int,
|
||||
purge_count: int,
|
||||
) -> int:
|
||||
msgs = []
|
||||
msg_count = 0
|
||||
itermsg = self._client.iter_messages(entity=chat)
|
||||
if purge_count == "all":
|
||||
async for msg in itermsg:
|
||||
if msg.sender_id == user_id:
|
||||
msgs += [msg.id]
|
||||
msg_count += 1
|
||||
if len(msgs) >= 99:
|
||||
await self._client.delete_messages(chat, msgs)
|
||||
msgs.clear()
|
||||
else:
|
||||
i = 0
|
||||
async for msg in itermsg:
|
||||
if msg.sender_id == user_id:
|
||||
if i == purge_count:
|
||||
break
|
||||
i += 1
|
||||
msgs += [msg.id]
|
||||
msg_count += 1
|
||||
if len(msgs) >= 99:
|
||||
await self._client.delete_messages(chat, msgs)
|
||||
msgs.clear()
|
||||
|
||||
if msgs:
|
||||
await self._client.delete_messages(chat, msgs)
|
||||
|
||||
return msg_count
|
||||
|
||||
async def _purge_messages(
|
||||
self,
|
||||
chat: EntityLike,
|
||||
self_id: int,
|
||||
can_delete: bool,
|
||||
message: Message,
|
||||
) -> int:
|
||||
msg_count = 0
|
||||
itermsg = self._client.iter_messages(
|
||||
entity=chat,
|
||||
min_id=message.reply_to_msg_id,
|
||||
reverse=True,
|
||||
)
|
||||
msgs = [message.reply_to_msg_id]
|
||||
async for msg in itermsg:
|
||||
if can_delete:
|
||||
msgs.append(msg)
|
||||
msg_count += 1
|
||||
elif msg.sender_id == self_id:
|
||||
msgs.append(msg)
|
||||
if msg.id != message.id:
|
||||
msg_count += 1
|
||||
if len(msgs) >= 99:
|
||||
await self._client.delete_messages(chat, msgs)
|
||||
msgs.clear()
|
||||
if msgs:
|
||||
await self._client.delete_messages(chat, msgs)
|
||||
return msg_count
|
||||
|
||||
async def cpurgecmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def apurgecmd(self, message: Message):
|
||||
"""
|
||||
Delete all messages up to and including the reply.
|
||||
- Usage: .apurge <reply>
|
||||
"""
|
||||
chat = message.chat
|
||||
if message.reply_to_msg_id is not None:
|
||||
can_delete = bool(
|
||||
message.is_private
|
||||
or (
|
||||
(chat.admin_rights or chat.creator)
|
||||
and chat.admin_rights.delete_messages
|
||||
or chat.admin_rights
|
||||
and chat.creator
|
||||
)
|
||||
)
|
||||
|
||||
msg_count = await self._purge_messages(
|
||||
chat, self.tg_id, can_delete, message
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"err_purge_start", self.all_strings, message
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
done = await self._client.send_message(
|
||||
chat.id,
|
||||
self.apo_lib.utils.get_str("purge_cmpl", self.all_strings, message).format(
|
||||
msg_count
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
await done.delete()
|
||||
|
||||
if self.config["log_purge"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def spurgecmd(self, message: Message):
|
||||
"""
|
||||
Delete all messages up to and including the reply silently.
|
||||
- Usage: .spurge <reply>
|
||||
"""
|
||||
chat = message.chat
|
||||
if message.reply_to_msg_id is not None:
|
||||
can_delete = bool(
|
||||
message.is_private
|
||||
or (
|
||||
(chat.admin_rights or chat.creator)
|
||||
and chat.admin_rights.delete_messages
|
||||
or chat.admin_rights
|
||||
and chat.creator
|
||||
)
|
||||
)
|
||||
|
||||
msg_count = await self._purge_messages(
|
||||
chat,
|
||||
self.tg_id,
|
||||
can_delete,
|
||||
message,
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"err_purge_start",
|
||||
self.all_strings,
|
||||
message,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
if self.config["log_purge"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def purgemecmd(self, message: Message):
|
||||
"""
|
||||
Delete x count (or all) of your latest messages.
|
||||
- Usage: .purgeme <count/all>
|
||||
"""
|
||||
chat = message.chat
|
||||
args = utils.get_args_raw(message).lower()
|
||||
args = str(args).split()
|
||||
if not represents_int(args[0]) and "all" not in args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_int", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if len(args) > 1:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("err_cmd_wrong", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
purge_count = "all" if len(args) == 1 and "all" in args else int(args[0])
|
||||
user_id = self.tg_id
|
||||
await message.delete()
|
||||
msg_count = await self._purge_user_messages(chat, user_id, purge_count)
|
||||
done = await self._client.send_message(
|
||||
chat.id,
|
||||
self.apo_lib.utils.get_str("purge_cmpl", self.all_strings, message).format(
|
||||
str(msg_count)
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
await done.delete()
|
||||
if self.config["log_purgeme"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def spurgemecmd(self, message: Message):
|
||||
"""
|
||||
Delete x count (or all) of your latest messages silently.
|
||||
- Usage: .spurgeme <count/all>
|
||||
"""
|
||||
chat = message.chat
|
||||
args = utils.get_args_raw(message).lower()
|
||||
args = str(args).split()
|
||||
if not represents_int(args[0]) and "all" not in args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_int", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if len(args) > 1:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("err_cmd_wrong", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
purge_count = "all" if len(args) == 1 and "all" in args else int(args[0])
|
||||
user_id = self.tg_id
|
||||
await message.delete()
|
||||
msg_count = await self._purge_user_messages(chat, user_id, purge_count)
|
||||
if self.config["log_purgeme"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def purgeusercmd(self, message: Message):
|
||||
"""
|
||||
Delete all messages from the replied user.
|
||||
- Usage: .purgeuser <reply>
|
||||
"""
|
||||
chat = message.chat
|
||||
if not message.is_reply:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_reply", self.all_strings, message),
|
||||
)
|
||||
reply = await message.get_reply_message()
|
||||
user_id = reply.sender_id
|
||||
if (
|
||||
(chat.admin_rights or chat.creator)
|
||||
and not chat.admin_rights.delete_messages
|
||||
or not chat.admin_rights
|
||||
and not chat.creator
|
||||
):
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("permerror", self.all_strings, message),
|
||||
)
|
||||
purge_count = "all"
|
||||
await message.delete()
|
||||
msg_count = await self._purge_user_messages(chat, user_id, purge_count)
|
||||
done = await self._client.send_message(
|
||||
chat.id,
|
||||
self.apo_lib.utils.get_str("purge_cmpl", self.all_strings, message).format(
|
||||
str(msg_count)
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
await done.delete()
|
||||
if self.config["log_purgeme"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def spurgeusercmd(self, message: Message):
|
||||
"""
|
||||
Delete all messages from the replied user silently.
|
||||
- Usage: .spurgeuser <reply>
|
||||
"""
|
||||
chat = message.chat
|
||||
if not message.is_reply:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_reply", self.all_strings, message),
|
||||
)
|
||||
reply = await message.get_reply_message()
|
||||
user_id = reply.sender_id
|
||||
if (
|
||||
(chat.admin_rights or chat.creator)
|
||||
and not chat.admin_rights.delete_messages
|
||||
or not chat.admin_rights
|
||||
and not chat.creator
|
||||
):
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("permerror", self.all_strings, message),
|
||||
)
|
||||
purge_count = "all"
|
||||
await message.delete()
|
||||
msg_count = await self._purge_user_messages(chat, user_id, purge_count)
|
||||
if self.config["log_purgeme"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("purge_success").format(str(msg_count)),
|
||||
)
|
||||
|
||||
async def adelcmd(self, message: Message):
|
||||
"""
|
||||
Delete the replied message.
|
||||
- Usage: .adel <reply>
|
||||
"""
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
with contextlib.suppress(Exception):
|
||||
await reply.delete()
|
||||
await message.delete()
|
||||
|
||||
async def editcmd(self, message: Message):
|
||||
"""
|
||||
Edit your last message.
|
||||
- Usage: .edit <text>
|
||||
"""
|
||||
chat = message.chat
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("err_cmd_wrong", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
i = 1
|
||||
async for msg in self._client.iter_messages(chat):
|
||||
if msg.out:
|
||||
if i == 2:
|
||||
old_msg = self.apo_lib.utils.raw_text(msg)
|
||||
new_msg = args
|
||||
await msg.edit(new_msg)
|
||||
await message.delete()
|
||||
break
|
||||
i += 1
|
||||
if self.config["log_edit"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO,
|
||||
__name__,
|
||||
self.strings("edit_success").format(old_msg, new_msg),
|
||||
)
|
||||
|
||||
async def sdcmd(self, message: Message):
|
||||
"""
|
||||
Make self-destructive messages. Also works for media when used in caption.
|
||||
- Usage: .sd <time> <text>
|
||||
"""
|
||||
args = utils.get_args_raw(message).split()
|
||||
if len(args) < 2:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("err_cmd_wrong", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
counter = int(" ".join(args[:1]))
|
||||
text = " ".join(args[1:])
|
||||
if (not counter or not text) and not represents_int(counter):
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("err_cmd_wrong", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
msg = await utils.answer(message, text)
|
||||
await asyncio.sleep(counter)
|
||||
await msg.delete()
|
||||
|
||||
if self.config["log_sd"]:
|
||||
self.apo_lib.utils.log(
|
||||
logging.INFO, __name__, self.strings("sd_success").format(str(counter))
|
||||
)
|
||||
138
anon97945/hikka-mods/pypng.py
Normal file
138
anon97945/hikka-mods/pypng.py
Normal file
@@ -0,0 +1,138 @@
|
||||
__version__ = (0, 0, 32)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
# requires: pygments emoji pillow
|
||||
|
||||
import logging
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
import pygments
|
||||
from pygments.formatters import ImageFormatter
|
||||
from pygments.lexers import Python3Lexer
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumPyPNGMod(loader.Module):
|
||||
"""
|
||||
Converts link/file from Py to PNG.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-PyPNG",
|
||||
"developer": "@anon97945",
|
||||
"no_file": "<b>Reply to file.py or url</b>",
|
||||
"no_url": "<b>No url in reply found.</b>",
|
||||
"py2png": "<b>Converting Py to PNG</b>",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
async def pypngcmd(self, message: Message):
|
||||
"""
|
||||
url/(reply to url or py file)
|
||||
"""
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("py2png", self.all_strings, message),
|
||||
)
|
||||
reply = await message.get_reply_message() if message.is_reply else None
|
||||
file = BytesIO()
|
||||
pngfile = BytesIO()
|
||||
args = utils.get_args_raw(message)
|
||||
if not message.is_reply and not args:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_file", self.all_strings, message),
|
||||
)
|
||||
if message.is_reply:
|
||||
if len(self.apo_lib.utils.get_urls(self.apo_lib.utils.raw_text(reply))) > 0:
|
||||
url = self.apo_lib.utils.get_urls(self.apo_lib.utils.raw_text(reply))[0]
|
||||
else:
|
||||
url = None
|
||||
if reply.file:
|
||||
await self._client.download_file(reply, file)
|
||||
file.name = reply.file.name
|
||||
if url:
|
||||
file, file.name = await self.apo_lib.utils.get_file_from_url(url)
|
||||
if not getattr(file, "name", None):
|
||||
if args and len(self.apo_lib.utils.get_urls(args)) > 0:
|
||||
file, file.name = await self.apo_lib.utils.get_file_from_url(
|
||||
self.apo_lib.utils.get_urls(args)[0]
|
||||
)
|
||||
else:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_url", self.all_strings, message),
|
||||
)
|
||||
file.seek(0)
|
||||
byte_str = file.read()
|
||||
text = byte_str.decode("utf-8")
|
||||
pygments.highlight(
|
||||
text,
|
||||
Python3Lexer(),
|
||||
ImageFormatter(font_name="DejaVu Sans Mono", line_numbers=True),
|
||||
pngfile,
|
||||
)
|
||||
pngfile.name = f"{os.path.splitext(file.name)[0]}.png"
|
||||
pngfile.seek(0)
|
||||
await self._client.send_file(
|
||||
message.peer_id,
|
||||
pngfile,
|
||||
force_document=True,
|
||||
reply_to=reply,
|
||||
)
|
||||
await message.delete()
|
||||
567
anon97945/hikka-mods/quotes.py
Normal file
567
anon97945/hikka-mods/quotes.py
Normal file
@@ -0,0 +1,567 @@
|
||||
__version__ = (2, 1, 1)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU AGPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
# Original author: @mishase
|
||||
# API author: @mishase
|
||||
|
||||
# requires: requests Pillow cryptg
|
||||
|
||||
import logging
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import requests
|
||||
import io
|
||||
import PIL
|
||||
from telethon import utils
|
||||
from telethon.utils import get_display_name
|
||||
from telethon.tl.types import (
|
||||
Message,
|
||||
MessageEntityBold,
|
||||
MessageEntityItalic,
|
||||
MessageEntityMention,
|
||||
MessageEntityTextUrl,
|
||||
MessageEntityCode,
|
||||
MessageEntityMentionName,
|
||||
MessageEntityHashtag,
|
||||
MessageEntityCashtag,
|
||||
MessageEntityBotCommand,
|
||||
MessageEntityUrl,
|
||||
MessageEntityStrike,
|
||||
MessageEntityUnderline,
|
||||
MessageEntityPhone,
|
||||
ChatPhotoEmpty,
|
||||
MessageMediaPhoto,
|
||||
MessageMediaDocument,
|
||||
MessageMediaWebPage,
|
||||
User,
|
||||
PeerUser,
|
||||
PeerBlocked,
|
||||
PeerChannel,
|
||||
PeerChat,
|
||||
DocumentAttributeSticker,
|
||||
ChannelParticipantsAdmins,
|
||||
ChannelParticipantCreator,
|
||||
)
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
null = None
|
||||
false = False
|
||||
true = True
|
||||
|
||||
PIL.Image.MAX_IMAGE_PIXELS = null
|
||||
|
||||
|
||||
class dict(dict):
|
||||
def __setattr__(self, attr, value):
|
||||
self[attr] = value
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumQuotesMod(loader.Module):
|
||||
"""Quote a message using Mishase Quotes API"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-Quotes",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_msg_limit": "Messages limit",
|
||||
"_cfg_max_width": "Max width (px)",
|
||||
"_cfg_scale_factor": "Scale factor",
|
||||
"_cfg_square_avatar": "Square avatar",
|
||||
"_cfg_text_color": "Text color",
|
||||
"_cfg_reply_line_color": "Reply line color",
|
||||
"_cfg_admin_title_color": "Admin title color",
|
||||
"_cfg_message_border_radius": "Message radius (px)",
|
||||
"_cfg_reply_thumb_border_radius": "Reply thumbnail radius (px)",
|
||||
"_cfg_picture_border_radius": "Picture radius (px)",
|
||||
"_cfg_background_color": "Background color",
|
||||
}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"QUOTE_MESSAGES_LIMIT",
|
||||
50,
|
||||
doc=lambda: self.strings("_cfg_msg_limit"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "QUOTE_MESSAGES_LIMIT",
|
||||
# 50,
|
||||
# "Messages limit",
|
||||
loader.ConfigValue(
|
||||
"MAX_WIDTH",
|
||||
384,
|
||||
doc=lambda: self.strings("_cfg_max_width"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "MAX_WIDTH",
|
||||
# 384,
|
||||
# "Max width (px)",
|
||||
loader.ConfigValue(
|
||||
"SCALE_FACTOR",
|
||||
5,
|
||||
doc=lambda: self.strings("_cfg_scale_factor"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "SCALE_FACTOR",
|
||||
# 5,
|
||||
# "Scale factor",
|
||||
loader.ConfigValue(
|
||||
"SQUARE_AVATAR",
|
||||
False,
|
||||
doc=lambda: self.strings("_cfg_square_avatar"),
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
# "SQUARE_AVATAR",
|
||||
# false,
|
||||
# "Square avatar",
|
||||
loader.ConfigValue(
|
||||
"TEXT_COLOR",
|
||||
"white",
|
||||
doc=lambda: self.strings("_cfg_text_color"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
# "TEXT_COLOR",
|
||||
# "white",
|
||||
# "Text color",
|
||||
loader.ConfigValue(
|
||||
"REPLY_LINE_COLOR",
|
||||
"white",
|
||||
doc=lambda: self.strings("_cfg_reply_line_color"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
# "REPLY_LINE_COLOR",
|
||||
# "white",
|
||||
# "Reply line color",
|
||||
loader.ConfigValue(
|
||||
"REPLY_THUMB_BORDER_RADIUS",
|
||||
2,
|
||||
doc=lambda: self.strings("_cfg_reply_thumb_border_radius"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "REPLY_THUMB_BORDER_RADIUS",
|
||||
# 2,
|
||||
# "Reply thumbnail radius (px)",
|
||||
loader.ConfigValue(
|
||||
"ADMINTITLE_COLOR",
|
||||
"#969ba0",
|
||||
doc=lambda: self.strings("_cfg_admin_title_color"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
# "ADMINTITLE_COLOR",
|
||||
# "#969ba0",
|
||||
# "Admin title color",
|
||||
loader.ConfigValue(
|
||||
"MESSAGE_BORDER_RADIUS",
|
||||
10,
|
||||
doc=lambda: self.strings("_cfg_message_border_radius"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "MESSAGE_BORDER_RADIUS",
|
||||
# 10,
|
||||
# "Message radius (px)",
|
||||
loader.ConfigValue(
|
||||
"PICTURE_BORDER_RADIUS",
|
||||
8,
|
||||
doc=lambda: self.strings("_cfg_picture_border_radius"),
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
# "PICTURE_BORDER_RADIUS",
|
||||
# 8,
|
||||
# "Picture radius (px)",
|
||||
loader.ConfigValue(
|
||||
"BACKGROUND_COLOR",
|
||||
"#162330",
|
||||
doc=lambda: self.strings("_cfg_background_color"),
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
# "BACKGROUND_COLOR",
|
||||
# "#162330",
|
||||
# "Background color",
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cquotescmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def quotecmd(self, msg):
|
||||
"""Quote a message. Args: .<count> .file"""
|
||||
args = utils.get_args_raw(msg)
|
||||
reply = await msg.get_reply_message()
|
||||
|
||||
if not reply:
|
||||
return await msg.edit("No reply message")
|
||||
|
||||
if not msg.out:
|
||||
msg = await msg.reply("_")
|
||||
|
||||
count = 1
|
||||
forceDocument = false
|
||||
|
||||
if args:
|
||||
args = args.split()
|
||||
forceDocument = "file" in args
|
||||
with contextlib.suppress(StopIteration):
|
||||
count = next(int(arg) for arg in args if arg.isdigit())
|
||||
count = max(1, min(self.config["QUOTE_MESSAGES_LIMIT"], count))
|
||||
messagePacker = MessagePacker(self._client)
|
||||
|
||||
if count == 1:
|
||||
await msg.edit("<b>Processing...</b>")
|
||||
await messagePacker.add(reply)
|
||||
if count > 1:
|
||||
it = self._client.iter_messages(
|
||||
reply.peer_id,
|
||||
offset_id=reply.id,
|
||||
reverse=true,
|
||||
add_offset=1,
|
||||
limit=count,
|
||||
)
|
||||
|
||||
i = 1
|
||||
async for message in it:
|
||||
await msg.edit(f"<b>Processing {i}/{count}</b>")
|
||||
i += 1
|
||||
await messagePacker.add(message)
|
||||
|
||||
messages = messagePacker.messages
|
||||
|
||||
if not messages:
|
||||
return await msg.edit("No messages to quote")
|
||||
|
||||
files = [("files", f) for f in messagePacker.files.values()]
|
||||
if not files:
|
||||
files.append(("files", bytearray()))
|
||||
|
||||
await msg.edit("<b>API Processing...</b>")
|
||||
|
||||
resp = await utils.run_sync(
|
||||
requests.post,
|
||||
"https://quotes.mishase.dev/create",
|
||||
data={
|
||||
"data": json.dumps(
|
||||
{
|
||||
"messages": messages,
|
||||
"maxWidth": self.config["MAX_WIDTH"],
|
||||
"scaleFactor": self.config["SCALE_FACTOR"],
|
||||
"squareAvatar": self.config["SQUARE_AVATAR"],
|
||||
"textColor": self.config["TEXT_COLOR"],
|
||||
"replyLineColor": self.config["REPLY_LINE_COLOR"],
|
||||
"adminTitleColor": self.config["ADMINTITLE_COLOR"],
|
||||
"messageBorderRadius": self.config["MESSAGE_BORDER_RADIUS"],
|
||||
"replyThumbnailBorderRadius": self.config[
|
||||
"REPLY_THUMB_BORDER_RADIUS"
|
||||
],
|
||||
"pictureBorderRadius": self.config["PICTURE_BORDER_RADIUS"],
|
||||
"backgroundColor": self.config["BACKGROUND_COLOR"],
|
||||
}
|
||||
),
|
||||
"moduleBuild": null,
|
||||
},
|
||||
files=files,
|
||||
timeout=99,
|
||||
)
|
||||
|
||||
if resp.status_code == 418:
|
||||
logger.error("API Error: %s", resp.text)
|
||||
msg.delete()
|
||||
return
|
||||
|
||||
await msg.edit("<b>Sending...</b>")
|
||||
|
||||
image = io.BytesIO()
|
||||
image.name = "quote.webp"
|
||||
|
||||
PIL.Image.open(io.BytesIO(resp.content)).save(image, "WEBP")
|
||||
image.seek(0)
|
||||
await self._client.send_message(
|
||||
msg.peer_id,
|
||||
file=image,
|
||||
force_document=forceDocument,
|
||||
reply_to=utils.get_topic(msg),
|
||||
)
|
||||
await msg.delete()
|
||||
|
||||
async def fquotecmd(self, msg):
|
||||
"""Fake message quote. Args: @<username>/<id>/<reply> <text>"""
|
||||
args = utils.get_args_raw(msg)
|
||||
reply = await msg.get_reply_message()
|
||||
splitArgs = args.split(maxsplit=1)
|
||||
if len(splitArgs) == 2 and (
|
||||
splitArgs[0].startswith("@") or splitArgs[0].isdigit()
|
||||
):
|
||||
user = (
|
||||
splitArgs[0][1:] if splitArgs[0].startswith("@") else int(splitArgs[0])
|
||||
)
|
||||
text = splitArgs[1]
|
||||
elif reply:
|
||||
user = reply.sender_id
|
||||
text = args
|
||||
else:
|
||||
return await msg.edit("Incorrect args")
|
||||
|
||||
try:
|
||||
uid = (await self._client.get_entity(user)).id
|
||||
except Exception:
|
||||
return await msg.edit("User not found")
|
||||
|
||||
async def getMessage():
|
||||
return Message(0, uid, message=text)
|
||||
|
||||
msg.message = ""
|
||||
msg.get_reply_message = getMessage
|
||||
|
||||
await self.quotecmd(msg)
|
||||
|
||||
|
||||
class MessagePacker:
|
||||
def __init__(self, client):
|
||||
self.files = {}
|
||||
self.messages = []
|
||||
self._client = client
|
||||
|
||||
async def add(self, msg):
|
||||
packed = await self.packMessage(msg)
|
||||
if packed:
|
||||
self.messages.append(packed)
|
||||
|
||||
async def packMessage(self, msg):
|
||||
obj = {}
|
||||
|
||||
if text := msg.message:
|
||||
obj["text"] = text
|
||||
|
||||
if entities := MessagePacker.encodeEntities(msg.entities or []):
|
||||
obj["entities"] = entities
|
||||
|
||||
if media := msg.media:
|
||||
file = await self.downloadMedia(media)
|
||||
if file:
|
||||
obj["picture"] = {"file": file}
|
||||
|
||||
if "text" not in obj and "picture" not in obj:
|
||||
return null
|
||||
|
||||
obj["author"] = await self.encodeAuthor(msg)
|
||||
|
||||
reply = await msg.get_reply_message()
|
||||
if reply:
|
||||
obj["reply"] = await self.encodeReply(reply)
|
||||
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def encodeEntities(entities):
|
||||
encEntities = []
|
||||
for entity in entities:
|
||||
if entityType := MessagePacker.getEntityType(entity):
|
||||
encEntities.append(
|
||||
{
|
||||
"type": entityType,
|
||||
"offset": entity.offset,
|
||||
"length": entity.length,
|
||||
}
|
||||
)
|
||||
return encEntities
|
||||
|
||||
@staticmethod
|
||||
def getEntityType(entity):
|
||||
t = type(entity)
|
||||
if t is MessageEntityBold:
|
||||
return "bold"
|
||||
if t is MessageEntityItalic:
|
||||
return "italic"
|
||||
if t in [MessageEntityUrl, MessageEntityPhone]:
|
||||
return "url"
|
||||
if t is MessageEntityCode:
|
||||
return "monospace"
|
||||
if t is MessageEntityStrike:
|
||||
return "strikethrough"
|
||||
if t is MessageEntityUnderline:
|
||||
return "underline"
|
||||
if t in [
|
||||
MessageEntityMention,
|
||||
MessageEntityTextUrl,
|
||||
MessageEntityMentionName,
|
||||
MessageEntityHashtag,
|
||||
MessageEntityCashtag,
|
||||
MessageEntityBotCommand,
|
||||
]:
|
||||
return "bluetext"
|
||||
return null
|
||||
|
||||
async def downloadMedia(self, inMedia, thumb=null):
|
||||
media = MessagePacker.getMedia(inMedia)
|
||||
if not media:
|
||||
return null
|
||||
mid = str(media.id)
|
||||
if thumb:
|
||||
mid += f".{str(thumb)}"
|
||||
if mid not in self.files:
|
||||
try:
|
||||
mime = media.mime_type
|
||||
except AttributeError:
|
||||
mime = "image/jpg"
|
||||
dl = await self._client.download_media(media, bytes, thumb=thumb)
|
||||
self.files[mid] = (str(len(self.files)), dl, mime)
|
||||
return self.files[mid][0]
|
||||
|
||||
@staticmethod
|
||||
def getMedia(media):
|
||||
t = type(media)
|
||||
if t is MessageMediaPhoto:
|
||||
return media.photo
|
||||
if t is MessageMediaDocument:
|
||||
for attribute in media.document.attributes:
|
||||
if isinstance(attribute, DocumentAttributeSticker):
|
||||
return media.document
|
||||
elif t is MessageMediaWebPage:
|
||||
if media.webpage.type == "photo":
|
||||
return media.webpage.photo
|
||||
return null
|
||||
|
||||
async def downloadProfilePicture(self, entity):
|
||||
media = entity.photo
|
||||
if not media or isinstance(media, ChatPhotoEmpty):
|
||||
return null
|
||||
mid = str(media.photo_id)
|
||||
if mid not in self.files:
|
||||
dl = await self._client.download_profile_photo(entity, bytes)
|
||||
self.files[mid] = (str(len(self.files)), dl, "image/jpg")
|
||||
return self.files[mid][0]
|
||||
|
||||
async def encodeAuthor(self, msg):
|
||||
uid, name, picture, adminTitle = await self.getAuthor(msg)
|
||||
|
||||
obj = {"id": uid, "name": name}
|
||||
if picture:
|
||||
obj["picture"] = {"file": picture}
|
||||
if adminTitle:
|
||||
obj["adminTitle"] = adminTitle
|
||||
|
||||
return obj
|
||||
|
||||
async def getAuthor(self, msg, full=true):
|
||||
uid = null
|
||||
name = null
|
||||
picture = null
|
||||
adminTitle = null
|
||||
|
||||
chat = msg.peer_id
|
||||
peer = msg.from_id or chat
|
||||
if fwd := msg.fwd_from:
|
||||
peer = fwd.from_id
|
||||
name = fwd.post_author or fwd.from_name
|
||||
|
||||
t = type(peer)
|
||||
if t is int:
|
||||
uid = peer
|
||||
elif t is PeerUser:
|
||||
uid = peer.user_id
|
||||
elif t is PeerChannel:
|
||||
uid = peer.channel_id
|
||||
elif t is PeerChat:
|
||||
uid = peer.chat_id
|
||||
elif t is PeerBlocked:
|
||||
uid = peer.peer_id
|
||||
elif not peer:
|
||||
uid = int(hashlib.shake_256(name.encode("utf-8")).hexdigest(6), 16)
|
||||
|
||||
if not name:
|
||||
entity = null
|
||||
try:
|
||||
entity = await self._client.get_entity(peer)
|
||||
except Exception:
|
||||
entity = await msg.get_chat()
|
||||
|
||||
if isinstance(entity, User) and entity.deleted:
|
||||
name = "Deleted Account"
|
||||
else:
|
||||
name = get_display_name(entity)
|
||||
|
||||
if full:
|
||||
picture = await self.downloadProfilePicture(entity)
|
||||
|
||||
if isinstance(chat, (PeerChannel, PeerChat)):
|
||||
admins = await self._client.get_participants(
|
||||
chat, filter=ChannelParticipantsAdmins
|
||||
)
|
||||
for admin in admins:
|
||||
participant = admin.participant
|
||||
if participant.user_id == uid:
|
||||
try:
|
||||
adminTitle = participant.rank
|
||||
except AttributeError:
|
||||
adminTitle = null
|
||||
if not adminTitle:
|
||||
if isinstance(participant, ChannelParticipantCreator):
|
||||
adminTitle = "owner"
|
||||
else:
|
||||
adminTitle = "admin"
|
||||
break
|
||||
|
||||
return uid, name, picture, adminTitle
|
||||
|
||||
async def encodeReply(self, reply):
|
||||
obj = {}
|
||||
|
||||
if text := reply.message:
|
||||
obj["text"] = text
|
||||
elif media := reply.media:
|
||||
t = type(media)
|
||||
obj.text = "📷 Photo" if t is MessageMediaPhoto else "💾 File"
|
||||
name = (await self.getAuthor(reply, full=false))[1]
|
||||
|
||||
obj["author"] = name
|
||||
|
||||
if media := reply.media:
|
||||
file = await self.downloadMedia(media, -1)
|
||||
if file:
|
||||
obj["thumbnail"] = {"file": file}
|
||||
|
||||
return obj
|
||||
180
anon97945/hikka-mods/save_message.py
Normal file
180
anon97945/hikka-mods/save_message.py
Normal file
@@ -0,0 +1,180 @@
|
||||
__version__ = (0, 0, 32)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
from io import BytesIO
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumSaveMessageMod(loader.Module):
|
||||
"""
|
||||
Get Message/Media from given link (also works for forward restricted content).
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-SaveMessage",
|
||||
"developer": "@anon97945",
|
||||
"done": "<b>Forward to saved complete.</b>",
|
||||
"invalid_link": "<b>Invalid link.</b>",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"done": "<b>Weiterleitung zu gespeicherten Daten abgeschlossen.</b>",
|
||||
"invalid_link": "<b>Ungültiger Link.</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"done": "<b>Перешлите для завершения сохранения.</b>",
|
||||
"invalid_link": "<b>Неверная ссылка.</b>",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo SaveMessage",
|
||||
"new": "Apo-SaveMessage",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def smcmd(self, message: Message):
|
||||
"""<messagelink> to forward message/media to SavedMessages."""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
if not args:
|
||||
return
|
||||
if not self.apo_lib.utils.get_ids_from_tglink(args):
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("invalid_link", self.all_strings, message),
|
||||
)
|
||||
channel_id, msg_id = self.apo_lib.utils.get_ids_from_tglink(args)
|
||||
msgs = await self._client.get_messages(channel_id, ids=msg_id)
|
||||
try:
|
||||
msgs = await msgs.forward_to(self.tg_id)
|
||||
except Exception as exc: # skipcq: PYL-W0703
|
||||
if "You can't forward messages from a protected chat (caused by ForwardMessagesRequest)" in str(exc):
|
||||
if not msgs.file:
|
||||
msgs = await self._client.send_message(self.tg_id, message=msgs)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("done", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
file = BytesIO()
|
||||
caption = f"{utils.escape_html(msgs.text)}"
|
||||
await self._client.download_file(msgs, file)
|
||||
file.name = (
|
||||
msgs.file.name or f"{msgs.file.media.id}{msgs.file.ext}"
|
||||
)
|
||||
file.seek(0)
|
||||
msgs = await self._client.send_file(
|
||||
self.tg_id,
|
||||
file,
|
||||
force_document=True,
|
||||
caption=caption,
|
||||
)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("done", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
async def smhcmd(self, message: Message):
|
||||
"""<messagelink> to forward message/media to current chat."""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
if not args:
|
||||
return
|
||||
|
||||
if not self.apo_lib.utils.get_ids_from_tglink(args):
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("invalid_link", self.all_strings, message),
|
||||
)
|
||||
|
||||
channel_id, msg_id = self.apo_lib.utils.get_ids_from_tglink(args)
|
||||
msgs = await self._client.get_messages(channel_id, ids=msg_id)
|
||||
try:
|
||||
msgs = await self._client.send_message(
|
||||
utils.get_chat_id(message),
|
||||
message=msgs,
|
||||
)
|
||||
await message.delete()
|
||||
except Exception as exc: # skipcq: PYL-W0703
|
||||
if "You can't forward messages from a protected chat (caused by ForwardMessagesRequest)" in str(exc) or "You can't forward messages from a protected chat (caused by SendMediaRequest)" in str(exc):
|
||||
if not msgs.file:
|
||||
await message.delete()
|
||||
return
|
||||
file = BytesIO()
|
||||
caption = f"{utils.escape_html(msgs.text)}"
|
||||
await self._client.download_file(msgs, file)
|
||||
file.name = (
|
||||
msgs.file.name or f"{msgs.file.media.id}{msgs.file.ext}"
|
||||
)
|
||||
file.seek(0)
|
||||
msgs = await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
file,
|
||||
force_document=True,
|
||||
caption=caption,
|
||||
)
|
||||
await message.delete()
|
||||
return
|
||||
154
anon97945/hikka-mods/show_viewer.py
Normal file
154
anon97945/hikka-mods/show_viewer.py
Normal file
@@ -0,0 +1,154 @@
|
||||
__version__ = (0, 0, 28)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumShowViewsMod(loader.Module):
|
||||
"""
|
||||
Send a message to get the current count of viewers.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Apo-ShowViews",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
"_cfg_cst_channel": "The Channel ID to send the message from.",
|
||||
"no_args": "No message to send.",
|
||||
"no_channel": "No channel set.",
|
||||
"no_reply": "You need to reply to a message.",
|
||||
"views": "Total <code>{}</code> views.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {}
|
||||
|
||||
strings_ru = {}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"channel",
|
||||
None,
|
||||
lambda: self.strings("_cfg_cst_channel"),
|
||||
validator=loader.validators.Union(
|
||||
loader.validators.TelegramID(),
|
||||
loader.validators.NoneType(),
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def svcmd(self, message: Message):
|
||||
"""
|
||||
<message/reply to msg> Send a message to get the current count of viewers with that message.
|
||||
"""
|
||||
chat_id = utils.get_chat_id(message)
|
||||
args = utils.get_args_raw(message)
|
||||
msg = None
|
||||
if not self.config["channel"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_channel", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if message.is_reply:
|
||||
msg = await message.get_reply_message()
|
||||
elif not args:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_args", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
await message.delete()
|
||||
|
||||
if message.is_reply and msg.out:
|
||||
await msg.delete()
|
||||
|
||||
msg = (
|
||||
await self._client.send_message(self.config["channel"], msg)
|
||||
if msg
|
||||
else await self._client.send_message(self.config["channel"], args)
|
||||
)
|
||||
|
||||
await msg.forward_to(chat_id)
|
||||
|
||||
if msg.out:
|
||||
await msg.delete()
|
||||
|
||||
async def gvcmd(self, message: Message):
|
||||
"""
|
||||
<reply to msg> Get current views of the message.
|
||||
"""
|
||||
if message.is_reply:
|
||||
msg = await message.get_reply_message()
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_reply", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
|
||||
view_count = msg.views
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("views", self.all_strings, message).format(
|
||||
view_count
|
||||
),
|
||||
)
|
||||
348
anon97945/hikka-mods/tts.py
Normal file
348
anon97945/hikka-mods/tts.py
Normal file
@@ -0,0 +1,348 @@
|
||||
__version__ = (0, 1, 93)
|
||||
|
||||
|
||||
# ▄▀█ █▄ █ █▀█ █▄ █ █▀█ ▀▀█ █▀█ █ █ █▀
|
||||
# █▀█ █ ▀█ █▄█ █ ▀█ ▀▀█ █ ▀▀█ ▀▀█ ▄█
|
||||
#
|
||||
# © Copyright 2024
|
||||
#
|
||||
# developed by @anon97945
|
||||
#
|
||||
# https://t.me/apodiktum_modules
|
||||
# https://github.com/anon97945
|
||||
#
|
||||
# 🔒 Licensed under the GNU GPLv3
|
||||
# 🌐 https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
# meta developer: @apodiktum_modules
|
||||
# meta banner: https://t.me/apodiktum_dumpster/11
|
||||
# meta pic: https://t.me/apodiktum_dumpster/13
|
||||
|
||||
# scope: ffmpeg
|
||||
# scope: hikka_only
|
||||
# scope: hikka_min 1.3.3
|
||||
|
||||
# requires: gtts pydub soundfile pyrubberband numpy AudioSegment wave
|
||||
# apt-requirements: libsndfile1 gcc ffmpeg rubberband-cli
|
||||
# ⚠️ Execute:
|
||||
# sudo libsndfile1 gcc ffmpeg rubberband-cli -y
|
||||
# In order for this module to work properly
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
import pyrubberband
|
||||
import soundfile
|
||||
from gtts import gTTS
|
||||
from pydub import AudioSegment, effects
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def audionormalizer(
|
||||
bytes_io_file: io.BytesIO,
|
||||
filename: str,
|
||||
file_ext: str,
|
||||
) -> tuple:
|
||||
bytes_io_file.seek(0)
|
||||
bytes_io_file.name = filename + file_ext
|
||||
rawsound = AudioSegment.from_file(bytes_io_file, "wav")
|
||||
normalizedsound = effects.normalize(rawsound)
|
||||
bytes_io_file.seek(0)
|
||||
normalizedsound.export(bytes_io_file, format="wav")
|
||||
bytes_io_file.name = f"{filename}.wav"
|
||||
filename, file_ext = os.path.splitext(bytes_io_file.name)
|
||||
return bytes_io_file, filename, file_ext
|
||||
|
||||
|
||||
async def audiohandler(
|
||||
bytes_io_file: io.BytesIO,
|
||||
filename: str,
|
||||
file_ext: str,
|
||||
) -> tuple:
|
||||
bytes_io_file.seek(0)
|
||||
bytes_io_file.name = filename + file_ext
|
||||
content = bytes_io_file.getvalue()
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i",
|
||||
"pipe:",
|
||||
"-acodec",
|
||||
"pcm_s16le",
|
||||
"-f",
|
||||
"wav",
|
||||
"-ac",
|
||||
"1",
|
||||
"pipe:",
|
||||
]
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1)
|
||||
out, _ = p.communicate(input=content)
|
||||
p.stdin.close()
|
||||
bytes_io_file.name = f"{filename}.wav"
|
||||
filename, file_ext = os.path.splitext(bytes_io_file.name)
|
||||
return (
|
||||
io.BytesIO(out),
|
||||
filename,
|
||||
file_ext if out.startswith(b"RIFF\xff\xff\xff") else None,
|
||||
)
|
||||
|
||||
|
||||
async def makewaves(bytes_io_file: io.BytesIO, filename: str, file_ext: str) -> tuple:
|
||||
bytes_io_file.seek(0)
|
||||
bytes_io_file.name = filename + file_ext
|
||||
content = bytes_io_file.getvalue()
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i",
|
||||
"pipe:",
|
||||
"-c:a",
|
||||
"libopus",
|
||||
"-f",
|
||||
"opus",
|
||||
"-ac",
|
||||
"2",
|
||||
"pipe:",
|
||||
]
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1)
|
||||
out, _ = p.communicate(input=content)
|
||||
p.stdin.close()
|
||||
bytes_io_file.name = f"{filename}.ogg"
|
||||
filename, file_ext = os.path.splitext(bytes_io_file.name)
|
||||
return io.BytesIO(out), filename, file_ext
|
||||
|
||||
|
||||
def represents_speed(s: str) -> bool:
|
||||
try:
|
||||
float(s)
|
||||
return 0.25 <= float(s) <= 3
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
async def speedup(
|
||||
bytes_io_file: io.BytesIO,
|
||||
filename: str,
|
||||
file_ext: str,
|
||||
speed: float,
|
||||
) -> tuple:
|
||||
bytes_io_file.seek(0)
|
||||
bytes_io_file.name = filename + file_ext
|
||||
y, sr = soundfile.read(bytes_io_file)
|
||||
y_stretch = pyrubberband.time_stretch(y, sr, speed)
|
||||
bytes_io_file.seek(0)
|
||||
soundfile.write(bytes_io_file, y_stretch, sr, format="wav")
|
||||
bytes_io_file.seek(0)
|
||||
bytes_io_file.name = f"{filename}.wav"
|
||||
return bytes_io_file, filename, file_ext
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ApodiktumTTSMod(loader.Module):
|
||||
strings = {
|
||||
"name": "Apo-TextToSpeech",
|
||||
"developer": "@anon97945",
|
||||
"_cfg_tts_lang": "Set your language code for the TTS here.",
|
||||
"_cfg_tts_speed": "Set the desired speech speed.",
|
||||
"needspeed": "You need to provide a speed value between 0.25 and 3.0.",
|
||||
"needvoice": "<b>[TTS]</b> This command needs a voicemessage.",
|
||||
"no_reply": "<b>[TTS]</b> You need to reply to a voicemessage.",
|
||||
"no_speed": "<b>[TTS]</b> Your input was an unsupported speed value.",
|
||||
"processing": "<b>[TTS]</b> Message is being processed ...",
|
||||
"tts_needs_text": "<b>[TTS]</b> I need text to convert to speech!",
|
||||
"_cfg_cst_auto_migrate": "Wheather to auto migrate defined changes on startup.",
|
||||
}
|
||||
|
||||
strings_en = {}
|
||||
|
||||
strings_de = {
|
||||
"_cfg_tts_lang": "Stellen Sie hier Ihren Sprachcode für TTS ein.",
|
||||
"_cfg_tts_speed": "Stellen Sie die gewünschte Sprechgeschwindigkeit ein.",
|
||||
"_cmd_doc_ctts": "Dadurch wird die Konfiguration für das Modul geöffnet.",
|
||||
"needspeed": (
|
||||
"Sie müssen einen Geschwindigkeitswert zwischen 0.25 und 3.0 angeben."
|
||||
),
|
||||
"needvoice": "<b>[TTS]</b> Dieser Befehl benötigt eine Sprachnachricht.",
|
||||
"no_reply": "<b>[TTS]</b> Sie müssen auf eine Sprachnachricht antworten.",
|
||||
"no_speed": (
|
||||
"<b>[TTS]</b> Ihre Eingabe war ein nicht unterstützter"
|
||||
" Geschwindigkeitswert."
|
||||
),
|
||||
"processing": "<b>[TTS]</b> Nachricht wird verarbeitet ...",
|
||||
"tts_needs_text": (
|
||||
"<b>[TTS]</b> Ich brauche Text, um ihn in Sprache umzuwandeln!"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_tts_lang": "Установите ваш код страны для TTS здесь.",
|
||||
"_cfg_tts_speed": "Установите желаемую скорость речи.",
|
||||
"_cmd_doc_ctts": "Это откроет конфиг для модуля.",
|
||||
"needspeed": "Вам нужно предоставить значение скорости между 0.25 и 3.0",
|
||||
"needvoice": "<b>[TTS]</b> Этой команде нужно голосовое сообщение.",
|
||||
"no_reply": "<b>[TTS]</b> Вам нужно сделать реплай на голосовое сообщение.",
|
||||
"no_speed": (
|
||||
"<b>[TTS]</b> Ваш ввод является неподдерживаемым значением скорости."
|
||||
),
|
||||
"processing": "<b>[TTS]</b> Сообщение обрабатывается...",
|
||||
"tts_needs_text": "<b>[TTS]</b> Мне нужен текст для преобразования в речь!",
|
||||
}
|
||||
|
||||
all_strings = {
|
||||
"strings": strings,
|
||||
"strings_en": strings,
|
||||
"strings_de": strings_de,
|
||||
"strings_ru": strings_ru,
|
||||
}
|
||||
|
||||
changes = {
|
||||
"migration1": {
|
||||
"name": {
|
||||
"old": "Apo TextToSpeech",
|
||||
"new": "Apo-TextToSpeech",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._ratelimit = []
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"tts_lang",
|
||||
"en",
|
||||
doc=lambda: self.strings("_cfg_tts_lang"),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"tts_speed",
|
||||
1,
|
||||
doc=lambda: self.strings("_cfg_tts_speed"),
|
||||
validator=loader.validators.Float(minimum=0.25, maximum=3),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"auto_migrate",
|
||||
True,
|
||||
doc=lambda: self.strings("_cfg_cst_auto_migrate"),
|
||||
validator=loader.validators.Boolean(),
|
||||
), # for MigratorClass
|
||||
)
|
||||
|
||||
async def client_ready(self):
|
||||
self.apo_lib = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/anon97945/hikka-libs/master/apodiktum_library.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
await self.apo_lib.migrator.auto_migrate_handler(
|
||||
self.__class__.__name__,
|
||||
self.strings("name"),
|
||||
self.changes,
|
||||
self.config["auto_migrate"],
|
||||
)
|
||||
|
||||
async def cttscmd(self, message: Message):
|
||||
"""
|
||||
This will open the config for the module.
|
||||
"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
async def ttscmd(self, message: Message):
|
||||
"""Convert text to speech with Google APIs"""
|
||||
speed = self.config["tts_speed"]
|
||||
text = utils.get_args_raw(message)
|
||||
if not text:
|
||||
if message.is_reply:
|
||||
text = (await message.get_reply_message()).message
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str(
|
||||
"tts_needs_text", self.all_strings, message
|
||||
),
|
||||
)
|
||||
return
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("processing", self.all_strings, message),
|
||||
)
|
||||
tts = await utils.run_sync(gTTS, text, lang=self.config["tts_lang"])
|
||||
voice = io.BytesIO()
|
||||
await utils.run_sync(tts.write_to_fp, voice)
|
||||
voice.seek(0)
|
||||
voice.name = "voice.mp3"
|
||||
filename, file_ext = os.path.splitext(voice.name)
|
||||
voice, filename, file_ext = await audiohandler(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await speedup(
|
||||
voice, filename, file_ext, float(speed)
|
||||
)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await audionormalizer(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await makewaves(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice.name = filename + file_ext
|
||||
await utils.answer(msg, voice, voice_note=True)
|
||||
if msg.out:
|
||||
await msg.delete()
|
||||
|
||||
async def speedvccmd(self, message: Message):
|
||||
"""Speed up voice by x"""
|
||||
speed = utils.get_args_raw(message)
|
||||
if not message.is_reply:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_reply", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
replymsg = await message.get_reply_message()
|
||||
if not replymsg.voice:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("needvoice", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if len(speed) == 0:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("needspeed", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
if not represents_speed(speed):
|
||||
await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("no_speed", self.all_strings, message),
|
||||
)
|
||||
return
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.apo_lib.utils.get_str("processing", self.all_strings, message),
|
||||
)
|
||||
ext = replymsg.file.ext
|
||||
voice = io.BytesIO()
|
||||
voice.name = replymsg.file.name
|
||||
await replymsg.client.download_file(replymsg, voice)
|
||||
voice.name = f"voice{ext}"
|
||||
filename, file_ext = os.path.splitext(voice.name)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await audiohandler(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await speedup(
|
||||
voice, filename, file_ext, float(speed)
|
||||
)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await audionormalizer(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice, filename, file_ext = await makewaves(voice, filename, file_ext)
|
||||
voice.seek(0)
|
||||
voice.name = filename + file_ext
|
||||
await utils.answer(msg, voice, voice_note=True)
|
||||
if msg.out:
|
||||
await msg.delete()
|
||||
1685
anon97945/hikka-mods/voicetools.py
Normal file
1685
anon97945/hikka-mods/voicetools.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user