Compare commits

...

138 Commits

Author SHA1 Message Date
github-actions[bot]
71ed0c604c Updated modules.json after parse 2026-06-12 02:52:17 2026-06-12 02:52:17 +00:00
github-actions[bot]
f335145976 Added and updated repositories 2026-06-12 02:51:44 2026-06-12 02:51:44 +00:00
6b6afb7493 fix: Ported fix from stable, in python version below 3.12 you can't use " with f-strings 2026-06-08 09:46:37 +03:00
Macsim
2ed246b9ad Merge pull request #301 from MuRuLOSE/update-submodules_d279789b37a939b3d9ececce6b4d0e1992293c23
Update of repositories 2026-05-31 02:48:09
2026-06-01 00:23:40 +03:00
github-actions[bot]
837784206f Updated modules.json after parse 2026-05-31 02:47:46 2026-05-31 02:47:46 +00:00
github-actions[bot]
811beb2b74 Added and updated repositories 2026-05-31 02:47:15 2026-05-31 02:47:16 +00:00
Zahar Vanilovv
d279789b37 Merge pull request #273 from MuRuLOSE/update-submodules_63944822139e8b6869fe2250ad8c650e9db06765
Update of repositories 2026-05-03 02:11:45
2026-05-03 05:14:12 +03:00
github-actions[bot]
74dfe4caf8 Updated modules.json after parse 2026-05-03 02:11:26 2026-05-03 02:11:26 +00:00
github-actions[bot]
18b8247e21 Added and updated repositories 2026-05-03 02:10:53 2026-05-03 02:10:53 +00:00
6394482213 fix: banners now will be visible no matter what 2026-04-25 09:23:54 +03:00
43a0e578c1 fix: 1
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 21:58:27 +03:00
3112e6d4bf python-magic is shit so its replaced with filetype due to magic requires its own binary in order to work 2026-04-24 21:43:29 +03:00
18bb817239 fix: if server does not return Content-Type in header we will try determine MIME-type offline
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 21:37:39 +03:00
24f1983e2a fix: not awaited update function 2026-04-24 21:16:36 +03:00
74638c14d0 feat: now you can see version if module updated
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 20:56:58 +03:00
Macsim
db62a5aee1 Merge pull request #264 from MuRuLOSE/update-submodules_49956ba865b533dca03b344d048f96465a6be62e
Update of repositories 2026-04-24 17:42:48
2026-04-24 20:43:54 +03:00
github-actions[bot]
4d2a2899f4 Updated modules.json after parse 2026-04-24 17:42:27 2026-04-24 17:42:27 +00:00
github-actions[bot]
2a27c56d83 Added and updated repositories 2026-04-24 17:41:57 2026-04-24 17:41:57 +00:00
49956ba865 version bump 2026-04-24 20:33:45 +03:00
f0d2a28105 removed H.modules
feat: added force index update

Co-authored-by: Copilot <copilot@github.com>
2026-04-24 20:10:52 +03:00
13d091c56c removed H.Modules by request 2026-04-19 16:10:58 +03:00
d7cf406b78 feat: Added 'by developer' if meta developer in module and event if module new
fix: if some event happened with module, changes message will not be sended
2026-04-19 14:40:13 +03:00
Macsim
0f30a78990 Merge pull request #257 from MuRuLOSE/update-submodules_e50c7c1688b89901690bc01609544e5c3e238097
Update of repositories 2026-04-19 02:02:27
2026-04-19 14:16:50 +03:00
github-actions[bot]
16adfac8b5 Updated modules.json after parse 2026-04-19 02:02:03 2026-04-19 02:02:03 +00:00
github-actions[bot]
7ddb190b35 Added and updated repositories 2026-04-19 02:01:35 2026-04-19 02:01:35 +00:00
e50c7c1688 drop: debug log 2026-04-18 14:08:04 +03:00
f3682ed87a fix: version displaying wrong and some shit idk 2026-04-18 13:41:49 +03:00
be47e59d97 fix: some broken strings
feat: install button and new limoka version update notification
drop: watcher that installs module  from bot (temporary)
2026-04-18 13:19:24 +03:00
eb71e39fcf Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-18 11:01:48 +03:00
Macsim
7d713e36c0 Merge pull request #256 from MuRuLOSE/update-submodules_a424d6bac4fb0d52521fca595af24968162bcc87
Update of repositories 2026-04-18 01:50:07
2026-04-18 10:58:20 +03:00
github-actions[bot]
a8fde5e498 Updated modules.json after parse 2026-04-18 01:49:43 2026-04-18 01:49:43 +00:00
github-actions[bot]
4a03c6cb1a Added and updated repositories 2026-04-18 01:49:10 2026-04-18 01:49:10 +00:00
Macsim
a424d6bac4 Merge pull request #254 from MuRuLOSE/update-submodules_59564f07b5b57f12eec78ff6e8f0574c70e37f8b
Update of repositories 2026-04-16 02:02:29
2026-04-16 07:39:02 +03:00
github-actions[bot]
fc8344ca05 Updated modules.json after parse 2026-04-16 02:02:11 2026-04-16 02:02:11 +00:00
github-actions[bot]
3e62dc0b69 Added and updated repositories 2026-04-16 02:01:42 2026-04-16 02:01:42 +00:00
41f253b471 feat: deleted files now got dedicated notification too 2026-04-15 18:02:16 +03:00
Macsim
59564f07b5 Merge pull request #253 from MuRuLOSE/update-submodules_3a193cfb2f731c403bbc542d2408c6144d0bcda7
Update of repositories 2026-04-15 01:54:22
2026-04-15 17:35:17 +03:00
github-actions[bot]
dfe2ae1103 Updated modules.json after parse 2026-04-15 01:53:44 2026-04-15 01:53:44 +00:00
github-actions[bot]
4ca7279309 Added and updated repositories 2026-04-15 01:53:05 2026-04-15 01:53:05 +00:00
3a193cfb2f fix: blockquote not needed here 2026-04-14 18:29:41 +03:00
637f9d82ae fix: another try to fix notifications 2026-04-13 20:24:29 +03:00
316e623c64 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-13 19:30:23 +03:00
57044428fd fix: added some tags 2026-04-13 19:30:20 +03:00
Macsim
2df0f9814f Merge pull request #250 from MuRuLOSE/update-submodules_e1a143311140eb28af693fc48a66f6b0e18ded3c
Update of repositories 2026-04-12 17:39:30
2026-04-12 20:40:03 +03:00
github-actions[bot]
46c955bb7b Updated modules.json after parse 2026-04-12 17:39:10 2026-04-12 17:39:10 +00:00
github-actions[bot]
887a55f798 Added and updated repositories 2026-04-12 17:38:40 2026-04-12 17:38:40 +00:00
e1a1433111 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-12 20:29:10 +03:00
github-actions[bot]
cc99e880ca Updated modules.json after parse 2026-04-12 17:29:01 2026-04-12 17:29:01 +00:00
b99b068fa0 feat: idk i forgot what i did 2026-04-12 20:28:32 +03:00
6d8e2db072 feat: now supports emojies in inline + little refactor 2026-04-12 20:28:13 +03:00
github-actions[bot]
60e7c852e5 Updated modules.json after parse 2026-04-12 16:47:37 2026-04-12 16:47:37 +00:00
de4d449a03 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-12 19:46:43 +03:00
86ebd01cc6 version bump 2026-04-12 19:46:34 +03:00
github-actions[bot]
5aadbb3084 Updated modules.json after parse 2026-04-12 16:46:02 2026-04-12 16:46:02 +00:00
143cbc4787 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-12 19:45:27 +03:00
15623e4a7b feat: no tags entierly if no tags
fix: no <blockquote> in enter_query
fix: fixed module crashing on python <= 3.10.15 - 3.10.20
2026-04-12 19:45:14 +03:00
github-actions[bot]
b20e31a149 Updated modules.json after parse 2026-04-12 16:33:57 2026-04-12 16:33:57 +00:00
75dedbae7c Updated .gitignore up-to-date 2026-04-12 19:33:25 +03:00
github-actions[bot]
4aff88ae13 Updated modules.json after parse 2026-04-12 15:09:46 2026-04-12 15:09:46 +00:00
eedcd4e60b drop: parse dependency for notify_diffs because not needed actually 2026-04-12 18:09:05 +03:00
57bc2c203a Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-12 18:07:47 +03:00
eeb49fca81 fix: Update CI workflow to reflect branch name change 2026-04-12 18:05:35 +03:00
Macsim
d3c195f212 Merge pull request #248 from MuRuLOSE/update-submodules_3156a432f050e4eb8bfcb171266bfceadfb9d5a1
Update of repositories 2026-04-12 14:57:57
2026-04-12 18:03:30 +03:00
github-actions[bot]
43f65359cd Updated modules.json after parse 2026-04-12 14:57:35 2026-04-12 14:57:35 +00:00
github-actions[bot]
c4b56bee9b Added and updated repositories 2026-04-12 14:57:04 2026-04-12 14:57:04 +00:00
3156a432f0 Legacy is here! 2026-04-12 17:54:16 +03:00
09523f59a0 fix: pull request update notification 2026-04-12 17:52:18 +03:00
31c9d0c074 fix: now parses all modules that inherits from loader.Module 2026-04-12 17:50:37 +03:00
e7b4c57940 feat: New developers 2026-04-12 17:49:43 +03:00
58316ec8b2 feat: Interface update 2026-04-12 17:49:15 +03:00
Macsim
4818d0fe91 Merge pull request #247 from MuRuLOSE/update-submodules_7555ea280ec5ac2e90f16112979cb31cab38a676
Update of repositories 2026-04-12 13:57:50
2026-04-12 17:24:47 +03:00
Macsim
997f892e06 Cleaned up unused code. 2026-04-12 17:04:41 +03:00
Macsim
1a92b84d89 dead 2026-04-12 17:02:29 +03:00
github-actions[bot]
001a8c5c30 Updated modules.json after parse 2026-04-12 13:57:28 2026-04-12 13:57:28 +00:00
github-actions[bot]
17ae450f8f Added and updated repositories 2026-04-12 13:56:57 2026-04-12 13:56:57 +00:00
Macsim
7555ea280e Merge pull request #226 from MuRuLOSE/update-submodules_e8f808b7dc145caab9c5cc1ce2901adcdb112f03
Update of repositories 2026-03-23 01:31:45
2026-03-23 18:57:25 +03:00
Macsim
59e3bc900f Removed stream.py because of security measures 2026-03-23 18:56:54 +03:00
github-actions[bot]
2d4181c22e Updated modules.json after parse 2026-03-23 01:31:17 2026-03-23 01:31:17 +00:00
github-actions[bot]
9a29d5ec99 Added and updated repositories 2026-03-23 01:30:41 2026-03-23 01:30:41 +00:00
Zahar Vanilovv
e8f808b7dc Merge pull request #214 from MuRuLOSE/update-submodules_02b1aa9f68911e516619089e9065b26a7592e781
Update of repositories 2026-03-11 01:22:36
2026-03-11 04:23:50 +03:00
github-actions[bot]
4cbe96ac7f Updated modules.json after parse 2026-03-11 01:22:17 2026-03-11 01:22:17 +00:00
github-actions[bot]
7fbb379419 Added and updated repositories 2026-03-11 01:21:45 2026-03-11 01:21:45 +00:00
Zahar Vanilovv
02b1aa9f68 Merge pull request #206 from MuRuLOSE/update-submodules_3a1dbc93cdf6f986e42501ce8bb564b7043fd762
Update of repositories 2026-03-03 01:29:31
2026-03-03 04:32:11 +03:00
github-actions[bot]
b32a65e515 Updated modules.json after parse 2026-03-03 01:28:23 2026-03-03 01:28:23 +00:00
github-actions[bot]
06973235e0 Added and updated repositories 2026-03-03 01:27:55 2026-03-03 01:27:55 +00:00
Zahar Vanilovv
3a1dbc93cd Merge pull request #200 from MuRuLOSE/update-submodules_1b01025a6674d1fc0578ed14c51e57a5aaa3971d
Update of repositories 2026-02-25 01:29:41
2026-02-25 15:56:04 +03:00
github-actions[bot]
33e0f03bdc Updated modules.json after parse 2026-02-25 01:29:20 2026-02-25 01:29:20 +00:00
github-actions[bot]
f9aa413329 Added and updated repositories 2026-02-25 01:28:51 2026-02-25 01:28:51 +00:00
Zahar Vanilovv
1b01025a66 Merge pull request #196 from MuRuLOSE/update-submodules_392e33b019901b44d53bc1bb8bd1d5b56ae0c90e
Update of repositories 2026-02-21 01:23:31
2026-02-21 04:29:23 +03:00
github-actions[bot]
2b2fd54185 Updated modules.json after parse 2026-02-21 01:23:09 2026-02-21 01:23:09 +00:00
github-actions[bot]
98e6966695 Added and updated repositories 2026-02-21 01:22:37 2026-02-21 01:22:37 +00:00
Macsim
392e33b019 Merge pull request #191 from MuRuLOSE/update-submodules_ccc67c8327d13e8ce92855a74265f8e1acf29657
Update of repositories 2026-02-16 01:28:14
2026-02-16 10:23:09 +03:00
github-actions[bot]
6ed325f906 Updated modules.json after parse 2026-02-16 01:27:56 2026-02-16 01:27:56 +00:00
github-actions[bot]
39be095e7e Added and updated repositories 2026-02-16 01:27:27 2026-02-16 01:27:27 +00:00
ccc67c8327 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-02-14 19:08:36 +03:00
bcc1d73f0c some fixes to update notifications 2026-02-14 13:48:53 +03:00
d5276c6242 small blacklist fixes 2026-02-14 13:48:18 +03:00
Macsim
005c5fc832 Merge pull request #189 from MuRuLOSE/update-submodules_54229a82664ad0d60b5d0007f099d8823bee5a89
Update of repositories 2026-02-14 06:58:46
2026-02-14 10:00:53 +03:00
github-actions[bot]
430f49f345 Updated modules.json after parse 2026-02-14 06:58:27 2026-02-14 06:58:27 +00:00
54229a8266 nvm 2026-02-14 09:54:25 +03:00
Zahar Vanilovv
6cb625acd8 Merge pull request #183 from MuRuLOSE/update-submodules_61ebb7fa472caa2abd4d6c7523bcf8830258167c
Update of repositories 2026-02-09 01:29:55
2026-02-09 17:25:41 +03:00
github-actions[bot]
bf03595075 Updated modules.json after parse 2026-02-09 01:29:33 2026-02-09 01:29:33 +00:00
github-actions[bot]
8bef604170 Added and updated repositories 2026-02-09 01:29:03 2026-02-09 01:29:03 +00:00
61ebb7fa47 Legacy version is here! 2026-02-08 20:17:50 +03:00
Zahar Vanilovv
2702cd2356 Merge pull request #182 from MuRuLOSE/update-submodules_ab5aaf579b3117a79d44591859b2cd30a6fcd5a1
Update of repositories 2026-02-08 01:59:39
2026-02-08 08:01:47 +03:00
github-actions[bot]
37f53375bc Updated modules.json after parse 2026-02-08 01:59:16 2026-02-08 01:59:16 +00:00
github-actions[bot]
23dade9b9e Added and updated repositories 2026-02-08 01:58:46 2026-02-08 01:58:46 +00:00
Macsim
ab5aaf579b Merge pull request #181 from MuRuLOSE/update-submodules_78a9b0cb76b2d495a5ff23a25614d91699a8757d
Update of repositories 2026-02-07 07:57:35
2026-02-07 11:05:32 +03:00
github-actions[bot]
170809519a Updated modules.json after parse 2026-02-07 07:57:13 2026-02-07 07:57:13 +00:00
78a9b0cb76 added full tds attribute 2026-02-07 10:54:30 +03:00
Macsim
c1d407f885 Merge pull request #180 from MuRuLOSE/update-submodules_7cda8f2685159651f9c2c9ff238215f2b5894d6c
Update of repositories 2026-02-07 07:41:22
2026-02-07 10:43:12 +03:00
github-actions[bot]
cf230d2922 Updated modules.json after parse 2026-02-07 07:41:02 2026-02-07 07:41:02 +00:00
7cda8f2685 fix f-strings while parsing 2026-02-07 10:35:19 +03:00
Macsim
92bdd1510d Merge pull request #179 from MuRuLOSE/update-submodules_a7b8c740b42879d9b3c0d577d275d5b42e4db254
Update of repositories 2026-02-07 01:23:55
2026-02-07 10:34:25 +03:00
github-actions[bot]
52f6fc53c3 Updated modules.json after parse 2026-02-07 01:23:38 2026-02-07 01:23:38 +00:00
github-actions[bot]
5c25eaad81 Added and updated repositories 2026-02-07 01:23:08 2026-02-07 01:23:08 +00:00
a7b8c740b4 Now index locking should be not a problem 2026-02-06 12:27:31 +03:00
44ca8633c6 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-02-06 12:03:00 +03:00
e076d4ab28 lang and dev_username hotfix 2026-02-06 12:02:48 +03:00
Macsim
634861943a Merge pull request #177 from MuRuLOSE/update-submodules_f68d97d09b6ca907775228791cde9446509954c8
Update of repositories 2026-02-06 01:25:04
2026-02-06 07:43:55 +03:00
github-actions[bot]
d20d019c32 Updated modules.json after parse 2026-02-06 01:24:40 2026-02-06 01:24:40 +00:00
github-actions[bot]
041e6ec5f7 Added and updated repositories 2026-02-06 01:24:12 2026-02-06 01:24:12 +00:00
f68d97d09b bolded some text 2026-02-05 20:30:35 +03:00
e624861b64 Blacklisted some module 2026-02-05 20:17:09 +03:00
Macsim
d3f035ec58 Merge pull request #176 from MuRuLOSE/update-submodules_5c2de322d31171b50a220dcbb9862aea611ab15a
Update of repositories 2026-02-05 01:23:39
2026-02-05 20:07:37 +03:00
Macsim
1ced5efae6 Update SpotifyMod.py 2026-02-05 20:01:13 +03:00
github-actions[bot]
3bf476c8a9 Updated modules.json after parse 2026-02-05 01:23:15 2026-02-05 01:23:15 +00:00
github-actions[bot]
abff91c013 Added and updated repositories 2026-02-05 01:22:45 2026-02-05 01:22:45 +00:00
5c2de322d3 new developer 2026-01-30 20:29:03 +03:00
5b2fe145ff b 2026-01-30 17:17:10 +03:00
64d8ceb354 clear histroy 2026-01-30 16:58:22 +03:00
537ae4c485 againfix 2026-01-30 16:50:55 +03:00
52018c8e14 fix not found PeerUser bcs of id 2026-01-30 15:42:57 +03:00
6f95292df1 hotfix again 2026-01-30 15:31:04 +03:00
4a5d4883a9 version bump 2026-01-30 15:22:55 +03:00
ce59b13c94 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-01-30 15:22:26 +03:00
91351cb83d hotfix (i am scared...) 2026-01-30 15:22:01 +03:00
Macsim
118a800856 Merge pull request #170 from MuRuLOSE/update-submodules_51bbb98711dbacc9d018d87706b7f28c77463cc4
Update of repositories 2026-01-30 11:59:59
2026-01-30 15:00:33 +03:00
169 changed files with 88048 additions and 91445 deletions

View File

@@ -96,13 +96,13 @@ jobs:
git push origin ${{ env.BRANCH_NAME }} --force
parse:
needs: update
runs-on: ubuntu-latest
if: |
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true)
needs: update
steps:
- name: Set branch ref for parse
id: setref
@@ -209,8 +209,8 @@ jobs:
notify_diffs:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: parse
if: |
(github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true)
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -231,10 +231,7 @@ jobs:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID_UPDATE }}
run: |
LAST_COMMIT_MESSAGE=$(git log -1 --pretty=%B)
if [[ "$LAST_COMMIT_MESSAGE" == Merge* ]]; then
python3 update_diffs.py --token ${{ secrets.TELEGRAM_BOT_TOKEN }} --chat_id ${{ secrets.TELEGRAM_CHAT_ID_UPDATE }} --base_commit HEAD~1
fi
python3 update_diffs.py --token ${{ secrets.TELEGRAM_BOT_TOKEN }} --chat_id ${{ secrets.TELEGRAM_CHAT_ID_UPDATE }} --base_commit HEAD~1
backup:

6
.gitignore vendored
View File

@@ -175,4 +175,8 @@ cython_debug/
# Limoka specific ignores
*.pem
assets/bot/whitelist.json
assets/bot/whitelist.json
# Configurations for VS Code
.vscode/
pyrightconfig.json

207
Fixyres/FModules/.gitignore vendored Normal file
View File

@@ -0,0 +1,207 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
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/
cover/
# 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
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .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
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.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/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

255
Fixyres/FModules/BSR.py Normal file
View File

@@ -0,0 +1,255 @@
__version__ = (1, 0, 0)
# ©️ Fixyres, 2026-2030
# 🌐 https://github.com/Fixyres/FModules
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png
# meta developer: @NFModules
# meta fhsdesc: brawlstars, game, funny
from .. import loader, utils
from urllib.parse import urlparse, parse_qs
async def extract_code(value: str) -> str:
if value.startswith("http"):
tags = parse_qs(urlparse(value).query).get("tag")
return tags[0] if tags else value
return value
async def to_id(code: str) -> int:
code = code.strip().upper()
if not code.startswith("X"):
return 0
val = 0
for ch in code[1:]:
i = "QWERTYUPASDFGHJKLZCVBNM23456789".find(ch)
if i == -1:
return 0
val = val * 31 + i
return val >> 8
async def to_code(n: int) -> str:
if n < 0:
return "X"
n_shifted = n << 8
res = []
chars = "QWERTYUPASDFGHJKLZCVBNM23456789"
while n_shifted > 0:
res.append(chars[n_shifted % 31])
n_shifted //= 31
return "X" + "".join(reversed(res))
@loader.tds
class BSR(loader.Module):
'''Module for finding nearby game rooms in BrawlStars.'''
strings = {
"name": "BSR",
"invalid_args": "<b>Usage:</b> <code>{prefix}bsr (room code/link) (previous) (next)</code>",
"invalid_code": "<b>Invalid room code!</b>",
"at_least_one": "<b>At least one argument (previous or next) must be greater than 0!</b>",
"prev_block": "<b>Previous:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Next:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Target Room"
}
strings_ru = {
"_cls_doc": "Модуль для поиска ближайших игровых комнат в BrawlStars.",
"invalid_args": "<b>Использование:</b> <code>{prefix}bsr (код комнаты/ссылка) (предыдущие) (следующие)</code>",
"invalid_code": "<b>Неверный код комнаты!</b>",
"at_least_one": "<b>Хотя бы один аргумент (предыдущие или следующие) должен быть больше 0!</b>",
"prev_block": "<b>Предыдущие:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Следующие:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Целевая комната"
}
strings_ua = {
"_cls_doc": "Модуль для пошуку найближчих ігрових кімнат у BrawlStars.",
"invalid_args": "<b>Використання:</b> <code>{prefix}bsr (код кімнати/посилання) (попередні) (наступні)</code>",
"invalid_code": "<b>Невірний код кімнати!</b>",
"at_least_one": "<b>Хоча б один аргумент (попередні або наступні) повинен бути більшим за 0!</b>",
"prev_block": "<b>Попередні:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Наступні:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Цільова кімната"
}
strings_kz = {
"_cls_doc": "BrawlStars ойынында жақын маңдағы ойын бөлмелерін табуға арналған модуль.",
"invalid_args": "<b>Қолдану:</b> <code>{prefix}bsr (бөлме коды/сілтеме) (алдыңғы) (келесі)</code>",
"invalid_code": "<b>Қате бөлме коды!</b>",
"at_least_one": "<b>Кем дегенде бір аргумент (алдыңғы немесе келесі) 0-ден үлкен болуы керек!</b>",
"prev_block": "<b>Алдыңғы:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Келесі:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Мақсатты бөлме"
}
strings_uz = {
"_cls_doc": "BrawlStars'da eng yaqin o'yin xonalarini topish uchun modul.",
"invalid_args": "<b>Qo'llanilishi:</b> <code>{prefix}bsr (xona kodi/havolasi) (oldingi) (keyingi)</code>",
"invalid_code": "<b>Noto'g'ri xona kodi!</b>",
"at_least_one": "<b>Kamida bitta argument (oldingi yoki keyingi) 0 dan katta bo'lishi kerak!</b>",
"prev_block": "<b>Oldingi:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Keyingi:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Maqsadli xona"
}
strings_fr = {
"_cls_doc": "Module pour trouver des salles de jeu à proximité dans BrawlStars.",
"invalid_args": "<b>Utilisation:</b> <code>{prefix}bsr (code/lien) (précédents) (suivants)</code>",
"invalid_code": "<b>Code de salle invalide!</b>",
"at_least_one": "<b>Au moins un argument doit être supérieur à 0 !</b>",
"prev_block": "<b>Précédents:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Suivants:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Salle cible"
}
strings_de = {
"_cls_doc": "Modul zum Finden von nahegelegenen Spielräumen in BrawlStars.",
"invalid_args": "<b>Verwendung:</b> <code>{prefix}bsr (Raumcode/Link) (vorherige) (nächste)</code>",
"invalid_code": "<b>Ungültiger Raumcode!</b>",
"at_least_one": "<b>Mindestens ein argument muss größer als 0 sein!</b>",
"prev_block": "<b>Vorherige:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Nächste:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Zielraum"
}
strings_jp = {
"_cls_doc": "BrawlStarsで近くのゲームルームを検索するためのモジュール。",
"invalid_args": "<b>使用法:</b> <code>{prefix}bsr (コード/リンク) (前) (次)</code>",
"invalid_code": "<b>無効なルームコード!</b>",
"at_least_one": "<b>少なくとも1つの引数は0より大きくなければなりません</b>",
"prev_block": "<b>前:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>次:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "ターゲットルーム"
}
@loader.command(
ru_doc="(код комнаты/ссылка) (предыдущие) (следующие) - найти комнаты.",
ua_doc="(код кімнати/посилання) (попередні) (наступні) - знайти кімнати.",
kz_doc="(бөлме коды/сілтеме) (алдыңғы) (келесі) - бөлмелерді табу.",
uz_doc="(xona kodi/havolasi) (oldingi) (keyingi) - xonalarni topish.",
fr_doc="(code/lien) (précédents) (suivants) - trouver des salles.",
de_doc="(Raumcode/Link) (vorherige) (nächste) - Räume finden.",
jp_doc="(コード/リンク) (前) (次) - ルームを検索します。"
)
async def bsr(self, message):
'''(room code/link) (previous) (next) - find rooms.'''
args = utils.get_args_raw(message).split()
if not args:
return await utils.answer(message, self.strings["invalid_args"].format(prefix=self.get_prefix()))
raw_input = args[0]
before = 0
nxt = 10
if len(args) >= 2:
try:
before = int(args[1])
except ValueError:
pass
if len(args) >= 3:
try:
nxt = int(args[2])
except ValueError:
pass
before = max(0, min(before, 5000))
nxt = max(0, min(nxt, 5000))
if before == 0 and nxt == 0:
return await utils.answer(message, self.strings["at_least_one"])
clean_tag = await extract_code(raw_input)
base_id = await to_id(clean_tag)
if base_id == 0:
return await utils.answer(message, self.strings["invalid_code"])
text, page, total_pages = await self.get_page_content(base_id, before, nxt, 0)
kb = self.build_keyboard(base_id, before, nxt, page, total_pages, clean_tag)
await self.inline.form(
message=message,
text=text,
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png",
reply_markup=kb
)
async def get_page_content(self, base_id: int, before: int, nxt: int, page: int):
actual_before = min(before, base_id)
total_pages = max(1, (actual_before + 9) // 10, (nxt + 9) // 10)
if page < 0:
page = total_pages - 1
if page >= total_pages:
page = 0
start = page * 10
prev_list = []
for i in range(start + 1, min(start + 10, actual_before) + 1):
c = await to_code(base_id - i)
link = f'<a href="https://link.brawlstars.com/invite/gameroom/en?tag={c}">{c}</a>'
prev_list.append(link)
next_list = []
for i in range(start + 1, min(start + 10, nxt) + 1):
c = await to_code(base_id + i)
link = f'<a href="https://link.brawlstars.com/invite/gameroom/en?tag={c}">{c}</a>'
next_list.append(link)
blocks = []
if prev_list:
blocks.append(self.strings["prev_block"].format(prev_list="\n".join(prev_list)))
if next_list:
blocks.append(self.strings["next_block"].format(next_list="\n".join(next_list)))
res = "\n\n".join(blocks)
if not res.strip():
res = " "
return res, page, total_pages
def build_keyboard(self, base_id: int, before: int, nxt: int, page: int, total_pages: int, clean_tag: str):
kb = [
[
{
"text": self.strings["btn_target"],
"copy": clean_tag
}
]
]
if total_pages > 1:
nav_row = []
if page > 0:
nav_row.append({"text": "", "callback": self.page_cb, "args": (base_id, before, nxt, page - 1, clean_tag)})
nav_row.append({"text": f"{page + 1} / {total_pages}", "callback": self.dummy_cb, "args": ()})
if page < total_pages - 1:
nav_row.append({"text": "", "callback": self.page_cb, "args": (base_id, before, nxt, page + 1, clean_tag)})
kb.append(nav_row)
return kb
async def dummy_cb(self, call):
await call.answer()
async def page_cb(self, call, base_id: int, before: int, nxt: int, page: int, clean_tag: str):
text, new_page, total_pages = await self.get_page_content(base_id, before, nxt, page)
kb = self.build_keyboard(base_id, before, nxt, new_page, total_pages, clean_tag)
await call.edit(
text=text,
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png",
reply_markup=kb
)

1026
Fixyres/FModules/FHeta.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,628 @@
__version__ = (1, 0, 0)
# meta developer: @NFModules
import asyncio
import aiohttp
import html
import sys
import uuid
import copy
import hashlib
import json
import re
from contextlib import suppress
from .. import loader, utils
@loader.tds
class FSecurity(loader.Module):
"""Module for automatic AI-based security checks of installed modules."""
strings = {
"name": "FSecurity",
"lang": "English",
"unavailable": "AI module{} check is unavailable.",
"suspicious": "AI interrupted installation of a suspicious module{}, reason:",
"blocked": "AI blocked module installation{}, reason:",
"continue": "Continue installation?",
"strict_mode_doc": "Block loading modules by any method (lm/dlm allowed) if the AI API is unavailable or the module is suspicious. On restart, this also applies to already installed modules.",
"nvidia_api_key_doc": "API key from build.nvidia.com, used for AI checks. If not specified, a public key from GitHub will be used."
}
strings_ru = {
"lang": "Russian",
"_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.",
"unavailable": "Проверка модуля{} через ИИ недоступна.",
"suspicious": "ИИ прервал установку подозрительного модуля{}, причина:",
"blocked": "ИИ заблокировал установку модуля{}, причина:",
"continue": "Продолжить установку?",
"strict_mode_doc": "Не позволять загружать модули любым методом (lm/dlm разрешено), если API ИИ недоступен или модуль подозрителен. При перезагрузке работает даже на уже установленные модули.",
"nvidia_api_key_doc": "API ключ от build.nvidia.com, используется для проверки через ИИ. Если вы его не укажете, будет использоваться общий ключ с GitHub."
}
strings_ua = {
"lang": "Ukraine",
"_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.",
"unavailable": "Перевірка модуля{} через ШІ недоступна.",
"suspicious": "ШІ перервав встановлення підозрілого модуля{}, причина:",
"blocked": "ШІ заблокував встановлення модуля{}, причина:",
"continue": "Продовжити встановлення?",
"strict_mode_doc": "Не дозволяти завантажувати модулі будь-яким методом (lm/dlm дозволено), якщо API ШІ недоступний або модуль підозрілий. При перезавантаженні працює навіть на вже встановлені модулі.",
"nvidia_api_key_doc": "API ключ від build.nvidia.com, використовується для перевірки через ШІ. Якщо ви його не вкажете, буде використовуватися загальний ключ з GitHub."
}
strings_de = {
"lang": "Germany",
"_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI.",
"unavailable": "Die KI-Modulprüfung{} ist nicht verfügbar.",
"suspicious": "Die KI hat die Installation eines verdächtigen Moduls unterbrochen{}, Grund:",
"blocked": "Die KI hat die Modulinstallation blockiert{}, Grund:",
"continue": "Installation fortsetzen?",
"strict_mode_doc": "Das Laden von Modulen mit jeder Methode blockieren (lm/dlm erlaubt), wenn die KI-API nicht verfügbar ist oder das Modul verdächtig ist. Beim Neustart gilt dies auch für bereits installierte Module.",
"nvidia_api_key_doc": "API-Schlüssel von build.nvidia.com, der für KI-Prüfungen verwendet wird. Wenn nicht angegeben, wird ein öffentlicher Schlüssel von GitHub verwendet."
}
strings_jp = {
"lang": "Japanese",
"_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。",
"unavailable": "AIモジュール{}のチェックが利用できません。",
"suspicious": "AIが疑わしいモジュールのインストールを中断しました{}、理由:",
"blocked": "AIがモジュールのインストールをブロックしました{}、理由:",
"continue": "インストールを続行しますか?",
"strict_mode_doc": "AI APIが利用できない場合や疑わしいモジュールの場合、すべての方法でモジュールの読み込みをブロックしますlm/dlmは許可。再起動時にはインストール済みモジュールにも適用されます。",
"nvidia_api_key_doc": "build.nvidia.com のAPIキー。AIチェックに使用されます。指定しない場合は、GitHubのパブリックキーが使用されます。"
}
strings_tr = {
"lang": "Turkish",
"_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.",
"unavailable": "Yapay zeka modül{} kontrolü kullanılamıyor.",
"suspicious": "Yapay zeka şüpheli bir modülün kurulumunu durdurdu{}, sebep:",
"blocked": "Yapay zeka modül kurulumunu engelledi{}, sebep:",
"continue": "Kuruluma devam edilsin mi?",
"strict_mode_doc": "AI API kullanılamıyorsa veya modül şüpheliyse, tüm yöntemlerle modül yüklenmesini engelle (lm/dlm izinli). Yeniden başlatmada zaten kurulu modüller için de geçerlidir.",
"nvidia_api_key_doc": "Yapay zeka kontrolleri için kullanılan build.nvidia.com API anahtarı. Belirtilmezse GitHub'daki genel anahtar kullanılacaktır."
}
strings_uz = {
"lang": "Uzbekistan",
"_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.",
"unavailable": "AI modul{} tekshiruvi mavjud emas.",
"suspicious": "AI shubhali modul o'rnatilishini to'xtatdi{}, sabab:",
"blocked": "AI modul o'rnatilishini blokladi{}, sabab:",
"continue": "O'rnatishni davom ettirasizmi?",
"strict_mode_doc": "AI API mavjud bo'lmasa yoki modul shubhali bo'lsa, barcha usullar bilan modul yuklashni bloklash (lm/dlm ruxsat etilgan). Qayta ishga tushirishda allaqachon o'rnatilgan modullarga ham ta'sir qiladi.",
"nvidia_api_key_doc": "build.nvidia.com API kaliti, AI orqali tekshirish uchun ishlatiladi. Agar ko'rsatmasangiz, GitHub-dan umumiy kalit ishlatiladi."
}
strings_kz = {
"lang": "Kazakhstan",
"_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль.",
"unavailable": "AI модуль{} тексеру қолжетімсіз.",
"suspicious": "AI күдікті модульді орнатуды тоқтатты{}, себебі:",
"blocked": "AI модульді орнатуды бұғаттады{}, себебі:",
"continue": "Орнатуды жалғастырасыз ба?",
"strict_mode_doc": "AI API қолжетімсіз болса немесе модуль күдікті болса, барлық әдістермен модуль жүктеуді бұғаттау (lm/dlm рұқсат етілген). Қайта іске қосқанда орнатылған модульдерге де қолданылады.",
"nvidia_api_key_doc": "build.nvidia.com API кілті, ЖИ арқылы тексеру үшін қолданылады. Егер оны көрсетпесеңіз, GitHub-тан ортақ кілт пайдаланылады."
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"strict_mode",
False,
lambda: self.strings["strict_mode_doc"],
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"nvidia_api_key",
"",
lambda: self.strings["nvidia_api_key_doc"],
validator=loader.validators.Hidden(),
)
)
self.tasks = {}
self.oreg = None
self.oload = None
async def client_ready(self, client, db):
self.__origin__ = "<fsecurity>"
self.core = self.lookup("loader")
self.modules = self.core.allmodules
self.restore_hooks()
self.patch()
async def on_unload(self):
self.unpatch()
def _render_prompt(self, prompt, **values):
rendered = prompt
for key, value in values.items():
rendered = rendered.replace("{" + key + "}", str(value))
return rendered
def _split_code(self, code):
chunk_size = 180000
if len(code) <= chunk_size:
return [code]
chunks = []
current =[]
current_len = 0
for line in code.splitlines(keepends=True):
if current and current_len + len(line) > chunk_size:
chunks.append("".join(current))
current =[]
current_len = 0
if len(line) > chunk_size:
if current:
chunks.append("".join(current))
current =[]
current_len = 0
for i in range(0, len(line), chunk_size):
chunks.append(line[i:i + chunk_size])
continue
current.append(line)
current_len += len(line)
if current:
chunks.append("".join(current))
return chunks or [code]
def _parse_ai_json(self, raw_text):
raw_text = (raw_text or "").strip()
if not raw_text:
return None
try:
parsed = json.loads(raw_text)
if isinstance(parsed, dict):
return parsed
except Exception:
pass
match = re.search(r"\{[\s\S]*\}", raw_text)
if not match:
return None
try:
parsed = json.loads(match.group())
except Exception:
return None
return parsed if isinstance(parsed, dict) else None
async def _fetch_prompt(self, session, url):
async with session.get(url, timeout=10) as resp:
if resp.status != 200:
return None
prompt = (await resp.text()).strip()
return prompt or None
async def _get_prompts(self, session):
main_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/main.txt")
chunk_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/chank.txt")
final_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/final.txt")
if not main_prompt or not chunk_prompt or not final_prompt:
return None
return {
"main": main_prompt,
"chunk": chunk_prompt,
"final": final_prompt,
}
async def _nvidia_request(self, session, api_key, system_prompt, user_prompt):
async with session.post(
"https://integrate.api.nvidia.com/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "qwen/qwen3-coder-480b-a35b-instruct",
"messages":[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.4,
"max_tokens": 1000,
},
timeout=180,
) as resp:
if resp.status != 200:
return None
data = await resp.json()
choices = data.get("choices") or[]
if not choices:
return None
return self._parse_ai_json(choices[0].get("message", {}).get("content", ""))
async def _local_ai_check(self, session, code, lang, api_key):
prompts = await self._get_prompts(session)
if not prompts:
return None
chunks = self._split_code(code)
if len(chunks) == 1:
prompt = self._render_prompt(prompts["main"], lang=lang)
return await self._nvidia_request(
session,
api_key,
prompt,
f"Analyze this module:\n\n```python\n{code}\n```",
)
total = len(chunks)
findings =[]
for index, chunk in enumerate(chunks, start=1):
previous_context = "; ".join(
f"Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
) or "Previous parts: no issues found so far."
chunk_prompt = self._render_prompt(
prompts["chunk"],
total=total,
current=index,
previous_context=previous_context,
lang=lang,
)
chunk_result = await self._nvidia_request(
session,
api_key,
chunk_prompt,
f"Part {index}/{total}:\n\n```python\n{chunk}\n```",
)
if not chunk_result:
return None
chunk_verdict = str(chunk_result.get("chunk_verdict", "CLEAN")).lower()
chunk_finding = str(chunk_result.get("findings", "") or "")
if chunk_verdict == "blocked":
findings_text = "\n".join(
f"- Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
)
if chunk_finding:
findings_text = f"{findings_text}\n- Part {index}: {chunk_finding}".strip()
final_prompt = self._render_prompt(
prompts["final"],
total=total,
findings=findings_text or "No prior findings.",
lang=lang,
)
return await self._nvidia_request(
session,
api_key,
final_prompt,
"Give the final verdict based on all findings.",
)
findings.append(chunk_finding if chunk_verdict != "clean" else "")
findings_text = "\n".join(
f"- Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
) or "All parts: no issues found."
final_prompt = self._render_prompt(
prompts["final"],
total=total,
findings=findings_text,
lang=lang,
)
return await self._nvidia_request(
session,
api_key,
final_prompt,
"Give the final verdict based on all findings.",
)
async def check(self, code):
try:
lang = self.strings("lang") or "en"
module_hash = hashlib.sha256(code.encode("utf-8")).hexdigest()
db_cache = self.get("cache", {})
if module_hash in db_cache:
cached = db_cache[module_hash]
if cached.get("level") == "safe":
return True
return cached
async with aiohttp.ClientSession() as session:
api_keys = await self._get_api_keys(session)
for api_key in api_keys:
parsed = await self._local_ai_check(session, code, lang, api_key)
if not isinstance(parsed, dict):
continue
verdict = str(parsed.get("verdict", "BLOCKED")).lower()
if verdict not in {"safe", "suspicious", "blocked"}:
verdict = "blocked"
summary = str(parsed.get("summary", "") or "")
result = {"level": verdict if verdict != "safe" else "safe"}
if verdict != "safe":
result["reason"] = summary
db_cache[module_hash] = result
self.set("cache", db_cache)
if result["level"] == "safe":
return True
return result
return False
except Exception:
return False
async def _get_api_keys(self, session):
configured_key = self.config["nvidia_api_key"].strip()
if configured_key:
return [configured_key]
try:
async with session.get(
"https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/api_keys.txt",
timeout=10,
) as resp:
if resp.status != 200:
return[]
raw_keys = (await resp.text()).strip()
except Exception:
return []
return[key.strip() for key in raw_keys.split(",") if key.strip()]
def format(self, state, reason="", link=""):
link_part = f' (<code>{utils.escape_html(link)}</code>)' if link else ""
if state == "unavailable":
return f'<b>{self.strings["unavailable"].format(link_part)}</b>\n<b>{self.strings["continue"]}</b>'
if state == "suspicious":
return f'<b>{self.strings["suspicious"].format(link_part)}</b>\n<blockquote expandable><b>{reason}</b></blockquote>\n<b>{self.strings["continue"]}</b>'
return f'<b>{self.strings["blocked"].format(link_part)}</b>\n<blockquote expandable><b>{reason}</b></blockquote>'
def buttons(self, task):
return [[
{"text": "", "callback": self.confirm, "args": (task, "yes")},
{"text": "", "callback": self.confirm, "args": (task, "no")}
]]
def closure_var(self, func, name):
raw = getattr(func, "__func__", func)
code = getattr(raw, "__code__", None)
closure = getattr(raw, "__closure__", None)
if not code or not closure or name not in code.co_freevars:
return None
with suppress(Exception):
return closure[code.co_freevars.index(name)].cell_contents
return None
def restore_hooks(self):
with suppress(Exception):
inst_reg = getattr(self.modules, "register_module")
owner = getattr(inst_reg, "__self__", None)
if (
owner
and owner is not self
and owner.__class__.__name__ == self.__class__.__name__
):
original = getattr(owner, "oreg", None)
if original:
if getattr(original, "__self__", None) is None:
self.modules.register_module = original.__get__(
self.modules,
self.modules.__class__,
)
else:
self.modules.register_module = original
with suppress(Exception):
inst_load = getattr(self.core, "load_module")
raw = getattr(inst_load, "__func__", inst_load)
if "FSecurity.patch.<locals>.load" in getattr(raw, "__qualname__", ""):
original = self.closure_var(raw, "original")
if original:
if getattr(original, "__self__", None) is None:
self.core.load_module = original.__get__(
self.core,
self.core.__class__,
)
else:
self.core.load_module = original
def patch(self):
if not self.oreg:
self.oreg = getattr(self.modules, "register_module")
if not self.oload:
self.oload = self.core.load_module
original = self.oload
async def load(_, *args, **kwargs):
base = utils.answer
async def answer(message, response, *a, **k):
if isinstance(response, str) and "😖</tg-emoji>" in response:
body = response.split("😖</tg-emoji>", 1)[1].strip()
if body in {"", "<b></b>", "<b> </b>"}:
with suppress(Exception):
if hasattr(message, "delete"):
await message.delete()
return message
if body.startswith("<b>") and body.endswith("</b>"):
decoded = html.unescape(body[3:-4])
response = response.split("😖</tg-emoji>", 1)[0] + f'😖</tg-emoji> {decoded}' if decoded else response.split("😖</tg-emoji>", 1)[0] + '😖</tg-emoji>'
try:
return await base(message, response, *a, **k)
except Exception:
with suppress(Exception):
return await self._client.send_message(
utils.get_chat_id(message),
response,
reply_to=getattr(message, "reply_to_msg_id", None),
buttons=k.get("reply_markup"),
)
return message
utils.answer = answer
try:
if getattr(original, "__self__", None) is None:
return await original(_, *args, **kwargs)
return await original(*args, **kwargs)
finally:
if utils.answer is answer:
utils.answer = base
self.core.load_module = load.__get__(self.core, self.core.__class__)
self.modules.register_module = self.register
def unpatch(self):
if self.oreg:
self.modules.register_module = self.oreg
if getattr(self, "core", None) and self.oload:
self.core.load_module = self.oload
def context(self):
frame = sys._getframe()
msg = None
fmsg = None
is_dlm_lm = False
while frame:
locals = frame.f_locals
if (
frame.f_code.co_name == "load_module"
and locals.get("self") is self.core
and 'message' in locals
and hasattr(locals['message'], 'edit')
):
if not msg:
msg = locals['message']
fmsg = locals.get('msg')
if frame.f_code.co_name in {"dlmod", "loadmod"}:
is_dlm_lm = True
if not msg and 'message' in locals and hasattr(locals['message'], 'edit'):
msg = locals['message']
if frame.f_code.co_name == "download_and_install":
if not msg and 'message' in locals and hasattr(locals['message'], 'edit'):
msg = locals['message']
frame = frame.f_back
return msg, fmsg, is_dlm_lm
def target_chat(self, msg=None, fmsg=None):
if not msg:
return None
if not fmsg:
return msg
with suppress(Exception):
target = copy.copy(msg)
target.reply_to_msg_id = fmsg.id
return target
return None
async def call_oreg(self, spec, name, origin="<core>", save_fs=False):
if getattr(self.oreg, "__self__", None) is None:
return await self.oreg(self.modules, spec, name, origin, save_fs=save_fs)
return await self.oreg(spec, name, origin, save_fs=save_fs)
async def register(self, spec, name, origin="<core>", save_fs=False):
if origin != "<core>":
code = ""
if hasattr(spec.loader, "data") and spec.loader.data:
code = spec.loader.data
if isinstance(code, bytes):
code = code.decode("utf-8", errors="ignore")
elif origin and origin.endswith(".py"):
with suppress(Exception):
with open(origin, "r", encoding="utf-8") as f:
code = f.read()
if code:
check = await self.check(code)
if check is not True:
msg, fmsg, is_dlm_lm = self.context()
target = self.target_chat(msg, fmsg)
if isinstance(check, dict):
status = check.get("level", "blocked")
reason = check.get("reason", "")
else:
status = "unavailable"
reason = ""
link = origin if origin.startswith("http") else ""
if status == "blocked":
if msg and target:
raise loader.LoadError(self.format("blocked", reason, link))
raise loader.LoadError("")
should_block = is_dlm_lm or self.config["strict_mode"]
if should_block and not (msg and target):
raise loader.LoadError("")
if should_block and msg and target:
task = str(uuid.uuid4())
event = asyncio.Event()
self.tasks[task] = {"event": event, "decision": False}
try:
form = await self.inline.form(
text=self.format(status, reason, link),
message=target,
reply_markup=self.buttons(task)
)
if not form:
raise loader.LoadError(reason)
await asyncio.wait_for(event.wait(), timeout=180.0)
if not self.tasks.pop(task)["decision"]:
with suppress(Exception):
await form.delete()
raise loader.LoadError("")
except asyncio.TimeoutError:
self.tasks.pop(task, None)
with suppress(Exception):
await form.delete()
raise loader.LoadError("")
except loader.LoadError:
raise
except Exception:
raise loader.LoadError("")
return await self.call_oreg(spec, name, origin, save_fs=save_fs)
async def confirm(self, call, task, action):
if task in self.tasks:
self.tasks[task]["decision"] = (action == "yes")
self.tasks[task]["event"].set()
with suppress(Exception):
await call.delete()

View File

@@ -0,0 +1,87 @@
__version__ = (1, 0, 0)
# meta developer: @NFModules
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.png
# meta fhsdesc: security, guard, antiscam, antivirus
# scope: hikka_min 2.0.0
# ©️ Fixyres, 2024-2030
# 🌐 https://github.com/Fixyres/FModules
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 🔑 http://www.apache.org/licenses/LICENSE-2.0
import aiohttp
import os
from .. import loader
@loader.tds
class FSecurity(loader.Module):
"""Module for automatic AI-based security checks of installed modules."""
strings = {
"name": "FSecurity"
}
strings_ru = {
"_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ."
}
strings_ua = {
"_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ."
}
strings_de = {
"_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI."
}
strings_jp = {
"_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。"
}
strings_tr = {
"_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül."
}
strings_uz = {
"_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul."
}
strings_kz = {
"_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль."
}
async def client_ready(self, client, db):
core = self.lookup("loader")
try:
async with aiohttp.ClientSession() as session:
async with session.get(
"https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/FSecurity.py",
timeout=aiohttp.ClientTimeout(total=15),
) as resp:
if resp.status != 200:
return
source = await resp.text()
except Exception:
return
target = os.path.join(
os.path.dirname(loader.__file__),
"modules",
"FSecurity.py",
)
try:
with open(target, "w", encoding="utf-8") as f:
f.write(source)
except Exception:
return
await core.unload_module("FSecurity")
try:
await core.load_module(source, None, "FSecurity", target, save_fs=False)
except Exception:
pass

201
Fixyres/FModules/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1 @@
My modules for Heroku userbot, telegram channel: https://t.me/FModules

198
Fixyres/FModules/SCD.py Normal file
View File

@@ -0,0 +1,198 @@
__version__ = (1, 0, 0)
# ©️ Fixyres, 2026-2030
# 🌐 https://github.com/Fixyres/FModules
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png
# meta developer: @NFModules
# meta fhsdesc: SoundCloud, Music, Music downloader, Downloader
# requires: curl_cffi
import io
import re
import json
from telethon.tl.types import DocumentAttributeAudio
from curl_cffi import requests
from .. import loader, utils
@loader.tds
class SCD(loader.Module):
'''Module for downloading songs from SoundCloud.'''
_client_id = None
strings = {
"name": "SCD",
"_cls_doc": "Module for downloading songs from SoundCloud.",
"no_args": "✘ <b>You didn't provide a link to the song, example of using the command: <code>{prefix}sc (link)</code></b>",
"downloading": "↓ <b>Downloading...</b>",
"not_found": "✘ <b>Song not found.</b>"
}
strings_ru = {
"_cls_doc": "Модуль для скачивания песен с SoundCloud.",
"no_args": "✘ <b>Вы не указали ссылку на песню, пример использования команды: <code>{prefix}sc (ссылка)</code></b>",
"downloading": "↓ <b>Скачивание...</b>",
"not_found": "✘ <b>Песня не найдена.</b>"
}
strings_ua = {
"_cls_doc": "Модуль для завантаження пісень із SoundCloud.",
"no_args": "✘ <b>Ви не вказали посилання на пісню, приклад використання команди: <code>{prefix}sc (посилання)</code></b>",
"downloading": "↓ <b>Завантаження...</b>",
"not_found": "✘ <b>Пісню не знайдено.</b>"
}
strings_de = {
"_cls_doc": "Modul zum Herunterladen von Liedern von SoundCloud.",
"no_args": "✘ <b>Sie haben keinen Link zum Lied angegeben, Anwendungsbeispiel des Befehls: <code>{prefix}sc (Link)</code></b>",
"downloading": "↓ <b>Wird heruntergeladen...</b>",
"not_found": "✘ <b>Lied nicht gefunden.</b>"
}
strings_uz = {
"_cls_doc": "SoundCloud-dan qo'shiqlarni yuklab olish uchun modul.",
"no_args": "✘ <b>Siz qo'shiq havolasini kiritmadingiz, buyruqdan foydalanish misoli: <code>{prefix}sc (havola)</code></b>",
"downloading": "↓ <b>Yuklab olinmoqda...</b>",
"not_found": "✘ <b>Qo'shiq topilmadi.</b>"
}
strings_kz = {
"_cls_doc": "SoundCloud-тан әндерді жүктеп алуға арналған модуль.",
"no_args": "✘ <b>Сіз әнге сілтеме көрсетпедіңіз, бұйрықты пайдалану мысалы: <code>{prefix}sc (сілтеме)</code></b>",
"downloading": "↓ <b>Жүктелуде...</b>",
"not_found": "✘ <b>Ән табылмады.</b>"
}
strings_fr = {
"_cls_doc": "Module pour télécharger des chansons depuis SoundCloud.",
"no_args": "✘ <b>Vous n'avez pas fourni de lien vers la chanson, exemple d'utilisation de la commande: <code>{prefix}sc (lien)</code></b>",
"downloading": "↓ <b>Téléchargement...</b>",
"not_found": "✘ <b>Chanson non trouvée.</b>"
}
strings_jp = {
"_cls_doc": "SoundCloudから曲をダウンロードするためのモジュール。",
"no_args": "✘ <b>曲へのリンクが指定されていません。コマンドの使用例: <code>{prefix}sc (リンク)</code></b>",
"downloading": "↓ <b>ダウンロード中...</b>",
"not_found": "✘ <b>曲が見つかりません。</b>"
}
async def _get_client_id(self, ses, html):
if self._client_id:
return self._client_id
for scr in reversed(re.findall(r'src="(https://a-v2\.sndcdn\.com/assets/[^"]+\.js)"', html)):
m = re.search(r'client_id:"([a-zA-Z0-9]{32})"', (await ses.get(scr)).text)
if m:
self._client_id = m.group(1)
return self._client_id
raise ValueError()
@loader.command(
ru_doc="(ссылка) - скачать песню с SoundCloud.",
ua_doc="(посилання) - завантажити пісню з SoundCloud.",
de_doc="(Link) - laden Sie ein Lied von SoundCloud herunter.",
uz_doc="(havola) - SoundCloud-dan qo'shiq yuklab olish.",
kz_doc="(сілтеме) - SoundCloud-тан әнді жүктеп алу.",
fr_doc="(lien) - télécharger une chanson depuis SoundCloud.",
jp_doc="(リンク) - SoundCloudから曲をダウンロードします。"
)
async def scd(self, message):
'''(link) - download a song from SoundCloud.'''
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings["no_args"].format(prefix=self.get_prefix()))
return
m = re.search(r"(https?://(?:[a-zA-Z0-9-]+\.)?soundcloud\.com/[^\s]+)", args)
if not m:
await utils.answer(message, self.strings["not_found"])
return
msg = await utils.answer(message, self.strings["downloading"])
try:
async with requests.AsyncSession(impersonate="chrome120") as ses:
h_resp = await ses.get(m.group(1))
if h_resp.status_code != 200:
raise ValueError()
html = h_resp.text
c_id = await self._get_client_id(ses, html)
h_m = re.search(r'window\.__sc_hydration\s*=\s*(\[.*?\]);</script>', html)
if not h_m:
raise ValueError()
t_d = next((i.get("data") for i in json.loads(h_m.group(1)) if i.get("hydratable") == "sound"), None)
if not t_d or t_d.get('kind') != 'track':
raise ValueError()
art = t_d.get("artwork_url") or t_d.get("user", {}).get("avatar_url")
if art:
art = art.replace("-large.jpg", "-t500x500.jpg")
tr = t_d.get("media", {}).get("transcodings", [])
if not tr:
raise ValueError()
s_info = next((t for t in tr if t.get("format", {}).get("protocol") == "progressive"), tr[0])
s_url = s_info.get("url") + f"?client_id={c_id}" + (f"&track_authorization={t_d.get('track_authorization')}" if t_d.get("track_authorization") else "")
s_resp = await ses.get(s_url)
if s_resp.status_code != 200 or not s_resp.json().get("url"):
raise ValueError()
a_buf = io.BytesIO()
a_buf.name = "track.mp3"
if s_info.get("format", {}).get("protocol") == "progressive":
m_resp = await ses.get(s_resp.json().get("url"))
if m_resp.status_code != 200:
raise ValueError()
a_buf.write(m_resp.content)
else:
m3_resp = await ses.get(s_resp.json().get("url"))
if m3_resp.status_code != 200:
raise ValueError()
chk = [l for l in m3_resp.text.splitlines() if l and not l.startswith('#')]
if not chk:
raise ValueError()
for c_u in chk:
c_r = await ses.get(c_u)
if c_r.status_code != 200:
raise ValueError()
a_buf.write(c_r.content)
a_buf.seek(0)
t_buf = None
if art:
try:
a_r = await ses.get(art)
if a_r.status_code == 200:
t_buf = io.BytesIO(a_r.content)
t_buf.name = "cover.jpg"
except:
pass
await message.client.send_file(
message.peer_id,
a_buf,
thumb=t_buf,
attributes=[DocumentAttributeAudio(
duration=t_d.get("duration", 0) // 1000,
title=t_d.get("title", "Unknown"),
performer=t_d.get("user", {}).get("username", "Unknown Artist")
)],
reply_to=message.reply_to_msg_id
)
await msg.delete()
except:
await utils.answer(msg, self.strings["not_found"])

View File

@@ -0,0 +1,456 @@
__version__ = (1, 1, 0)
# ©️ Fixyres, 2026-2030
# 🌐 https://github.com/Fixyres/FModules
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png
# meta developer: @NFModules
# meta fhsdesc: game, funny, guess, question game
# requires: curl_cffi
import html
import re
import inspect
from curl_cffi import requests
from .. import loader, utils
from telethon.tl.functions.messages import TranslateTextRequest
from telethon.tl.types import TextWithEntities
class AsyncAki:
def __init__(self, lang="en", cm=False):
self.user_lang = lang
aki_langs =[
"en", "ar", "cn", "de", "es", "fr", "il", "it",
"jp", "kr", "nl", "pl", "pt", "ru", "tr", "id"
]
if lang in aki_langs:
self.aki_lang = lang
elif lang in ["uk", "uz", "kk", "be"]:
self.aki_lang = "ru"
else:
self.aki_lang = "en"
self.cm = str(cm).lower()
self.uri = f"https://{self.aki_lang}.akinator.com"
self.session = requests.AsyncSession(impersonate="chrome120")
self.session.headers.update({
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1"
})
self.win = False
self.prog = "0.0"
self.step = "0"
self.slp = ""
self.name = None
self.desc = ""
self.photo = "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png"
self.q = ""
async def _req(self, ep, data=None):
method = "POST" if data else "GET"
url = f"{self.uri}/{ep}"
headers = {}
if data:
headers["x-requested-with"] = "XMLHttpRequest"
headers["Content-Type"] = "application/x-www-form-urlencoded"
r = await self.session.request(method, url, data=data, headers=headers)
if r.status_code != 200:
raise Exception(f"AE{r.status_code}")
try:
return r.json()
except Exception:
return r.text
async def start(self):
t = await self._req("game", {"sid": 1, "cm": self.cm})
if "technical problem" in (t if isinstance(t, str) else "").lower():
raise Exception("ATP")
ses_m = re.search(r"session['\"]\)\.val\(['\"](.+?)['\"]", t if isinstance(t, str) else str(t))
sig_m = re.search(r"signature['\"]\)\.val\(['\"](.+?)['\"]", t if isinstance(t, str) else str(t))
q_m = re.search(r'class="question-text".*?>(.+?)</p>', t if isinstance(t, str) else str(t), re.S)
if not ses_m or not sig_m or not q_m:
raise Exception("AECF")
self.ses = ses_m.group(1)
self.sig = sig_m.group(1)
self.q = html.unescape(q_m.group(1)).strip()
async def answer(self, a):
data = {
"step": self.step, "progression": self.prog, "sid": 1,
"cm": self.cm, "answer": a, "step_last_proposition": self.slp,
"session": self.ses, "signature": self.sig
}
res = await self._req("answer", data)
self._upd(res)
async def exclude(self):
data = {
"step": self.step, "progression": self.prog, "sid": 1,
"cm": self.cm, "session": self.ses, "signature": self.sig,
"forward_answer": "1"
}
try:
res = await self._req("exclude", data)
if isinstance(res, dict) and res.get("question"):
self.win = False
self.name = None
self._upd(res)
else:
self.win = False
self.name = None
self.q = None
except Exception:
self.win, self.name, self.q = False, None, None
def _upd(self, d):
if not isinstance(d, dict):
return
if d.get("id_proposition"):
self.win = True
self.name = html.unescape(d.get("name_proposition", ""))
self.desc = html.unescape(d.get("description_proposition", ""))
self.photo = d.get("photo", "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png")
self.step = str(d.get("step", self.step))
self.slp = self.step
if "progression" in d and d["progression"] is not None:
self.prog = str(d["progression"])
elif d.get("question"):
self.win = False
self.q = html.unescape(d.get("question", ""))
self.prog = str(d.get("progression", "0"))
self.step = str(d.get("step", "0"))
self.slp = str(d.get("step_last_proposition", self.slp))
else:
self.win = False
self.name = None
self.q = None
async def close(self):
try:
if inspect.iscoroutinefunction(self.session.close):
await self.session.close()
else:
res = self.session.close()
if inspect.isawaitable(res):
await res
except Exception:
pass
@loader.tds
class Akinator(loader.Module):
'''Akinator will guess any character you have in mind, you just need to answer a couple of questions.'''
strings = {
"name": "Akinator",
"lang": "en",
"child_mode": "Child mode. If enabled, it will be easier to guess 18+ heroes.",
"start": "Start",
"text": "<b>Guess any character you have in mind, and click on the Start button.</b>",
"yes": "Yes",
"no": "No",
"idk": "I don't know",
"probably": "Probably",
"probably_not": "Probably not",
"this_is": "<b>This is <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>This is <code>{name}</code></b>",
"not_right": "Not right",
"failed": "<b>Failed to guess the character.</b>"
}
strings_ru = {
"lang": "ru",
"_cls_doc": "Акинатор угадает любого вами загаданного персонажа.",
"child_mode": "Детский режим. Сложнее отгадать 18+ героев.",
"start": "Начать",
"text": "<b>Задумайте персонажа, и нажмите начать.</b>",
"yes": "Да",
"no": "Нет",
"idk": "Не знаю",
"probably": "Возможно",
"probably_not": "Скорее нет",
"this_is": "<b>Это <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Это <code>{name}</code></b>",
"not_right": "Это не он",
"failed": "<b>Не удалось угадать персонажа.</b>"
}
strings_ua = {
"lang": "uk",
"_cls_doc": "Акінатор вгадає будь-якого персонажа.",
"child_mode": "Дитячий режим. Складніше відгадати 18+ героїв.",
"start": "Почати",
"text": "<b>Загадайте персонажа, і натисніть почати.</b>",
"yes": "Так",
"no": "Ні",
"idk": "Не знаю",
"probably": "Можливо",
"probably_not": "Швидше ні",
"this_is": "<b>Це <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Це <code>{name}</code></b>",
"not_right": "Це не він",
"failed": "<b>Не вдалося вгадати персонажа.</b>"
}
strings_de = {
"lang": "de",
"_cls_doc": "Akinator errät jeden Charakter, den du dir vorstellst.",
"child_mode": "Kindermodus. Wenn aktiviert, wird es schwieriger sein, 18+ Helden zu erraten.",
"start": "Start",
"text": "<b>Denk dir einen Charakter aus und klicke auf Start.</b>",
"yes": "Ja",
"no": "Nein",
"idk": "Ich weiß nicht",
"probably": "Wahrscheinlich",
"probably_not": "Wahrscheinlich nicht",
"this_is": "<b>Das ist <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Das ist <code>{name}</code></b>",
"not_right": "Das ist er nicht",
"failed": "<b>Charakter konnte nicht erraten werden.</b>"
}
strings_fr = {
"lang": "fr",
"_cls_doc": "Akinator devinera n'importe quel personnage.",
"child_mode": "Mode enfant. Héros 18+ plus difficiles à deviner.",
"start": "Commencer",
"text": "<b>Pensez à un personnage et cliquez sur Commencer.</b>",
"yes": "Oui",
"no": "Non",
"idk": "Je ne sais pas",
"probably": "Probablement",
"probably_not": "Probablement pas",
"this_is": "<b>C'est <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>C'est <code>{name}</code></b>",
"not_right": "Ce n'est pas lui",
"failed": "<b>Impossible de deviner.</b>"
}
strings_jp = {
"lang": "ja",
"_cls_doc": "アキネーターはあなたが考えているキャラクターを当てます。",
"child_mode": "子供モード。有効にすると、18歳以上のキャラクターを推測するのが難しくなります。",
"start": "開始",
"text": "<b>キャラクターを思い浮かべて開始。</b>",
"yes": "はい",
"no": "いいえ",
"idk": "わかりません",
"probably": "おそらく",
"probably_not": "おそらく違う",
"this_is": "<b>これは <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>これは <code>{name}</code></b>",
"not_right": "違います",
"failed": "<b>推測できませんでした。</b>"
}
strings_uz = {
"lang": "uz",
"_cls_doc": "Akinator siz o'ylagan har qanday qahramonni topadi.",
"child_mode": "Bolalar rejimi. Yoqilgan bo'lsa, 18+ qahramonlarni topish qiyinroq bo'ladi.",
"start": "Boshlash",
"text": "<b>Qahramonni o'ylang va Boshlash tugmasini bosing.</b>",
"yes": "Ha",
"no": "Yo'q",
"idk": "Bilmayman",
"probably": "Ehtimol",
"probably_not": "Ehtimol yo'q",
"this_is": "<b>Bu <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Bu <code>{name}</code></b>",
"not_right": "Bu u emas",
"failed": "<b>Qahramonni topib bo'lmadi.</b>"
}
strings_kz = {
"lang": "kk",
"_cls_doc": "Акинатор сіз ойлаған кез келген кейіпкерді табады.",
"child_mode": "Балалар режимі. Қосылған болса, 18+ кейіпкерлерді табу қиынырақ болады.",
"start": "Бастау",
"text": "<b>Кейіпкерді ойлаңыз және Бастау түймесін басыңыз.</b>",
"yes": "Иә",
"no": "Жоқ",
"idk": "Білмеймін",
"probably": "Мүмкін",
"probably_not": "Мүмкін емес",
"this_is": "<b>Бұл <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Бұл <code>{name}</code></b>",
"not_right": "Бұл ол емес",
"failed": "<b>Кейіпкерді таба алмадық.</b>"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"child_mode",
False,
lambda: self.strings["child_mode"],
validator=loader.validators.Boolean()
)
)
self.games = {}
async def _tr(self, client, text, to_lang):
if not text:
return text
try:
request = TranslateTextRequest(
to_lang=to_lang,
text=[TextWithEntities(text=text, entities=[])]
)
result = await client(request)
return result.result[0].text
except Exception:
return text
@loader.command(
ru_doc="- начать игру.",
ua_doc="- почати гру.",
de_doc="- Spiel starten.",
fr_doc="- commencer le jeu.",
jp_doc="- ゲームを開始します。",
uz_doc="- o'yinni boshlash.",
kz_doc="- ойынды бастау.",
)
async def akinator(self, message):
'''- start the game.'''
try:
aki = AsyncAki(self.strings["lang"], self.config["child_mode"])
await aki.start()
self.games.setdefault(message.chat_id, {})[message.id] = aki
await self.inline.form(
text=self.strings["text"],
message=message,
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png",
reply_markup={
"text": self.strings["start"],
"callback": self._cb,
"args": (message,)
}
)
except Exception as e:
await utils.answer(message, f"<code>{e}</code>")
async def _cb(self, call, message):
aki = self.games.get(message.chat_id, {}).get(message.id)
if aki:
await self._sq(call, aki, message)
async def _sq(self, call, aki, message):
if aki.aki_lang != aki.user_lang:
question = await self._tr(message.client, aki.q, aki.user_lang)
else:
question = aki.q
markup = [[
{"text": self.strings["yes"], "callback": self._ans, "args": (0, message)},
{"text": self.strings["no"], "callback": self._ans, "args": (1, message)},
{"text": self.strings["idk"], "callback": self._ans, "args": (2, message)}
],[
{"text": self.strings["probably"], "callback": self._ans, "args": (3, message)},
{"text": self.strings["probably_not"], "callback": self._ans, "args": (4, message)}
]
]
await call.edit(
f"<b>{question}</b>",
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png",
reply_markup=markup
)
async def _show_guess(self, call, aki, message):
if aki.aki_lang != aki.user_lang:
name = await self._tr(message.client, aki.name, aki.user_lang)
desc = await self._tr(message.client, aki.desc, aki.user_lang) if aki.desc else aki.desc
else:
name = aki.name
desc = aki.desc
if desc:
text = self.strings["this_is"].format(name=name, description=desc)
else:
text = self.strings["this_is_no_desc"].format(name=name)
markup = [[
{"text": self.strings["yes"], "callback": self._fin, "args": (True, message, text, aki.photo)},
{"text": self.strings["not_right"], "callback": self._rej, "args": (message,)}
]
]
await call.edit(
text,
photo=aki.photo,
reply_markup=markup
)
async def _ans(self, call, answer_id, message):
aki = self.games.get(message.chat_id, {}).get(message.id)
if not aki:
return
await aki.answer(answer_id)
if aki.win:
await self._show_guess(call, aki, message)
elif getattr(aki, 'q', None):
await self._sq(call, aki, message)
else:
await self._fin(call, False, message, "", "")
async def _rej(self, call, message):
aki = self.games.get(message.chat_id, {}).get(message.id)
if not aki:
return
try:
await aki.exclude()
except Exception:
pass
if aki.win:
await self._show_guess(call, aki, message)
elif getattr(aki, 'q', None):
await self._sq(call, aki, message)
else:
await self._fin(call, False, message, "", "")
async def _fin(self, call, won, message, text, photo):
aki = self.games.get(message.chat_id, {}).pop(message.id, None)
if aki:
await aki.close()
if won:
await call.edit(text, photo=photo, reply_markup=[])
else:
await call.edit(
self.strings["failed"],
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/idk.png",
reply_markup=[]
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -0,0 +1,25 @@
You must strictly follow these rules with no exceptions.
Analyze only part {current}/{total} of a Hikka userbot Python module. Do NOT give a final verdict for the whole module.
Previous context: {previous_context}
BLOCKED: encrypted/obfuscated code (base64, marshal, zlib, rot13, encoded exec, or any technique hiding real logic), account deletion, mass scam/spam/ads to all chats on load, session/auth_key/2FA exfiltration, bulk message/dialog dump to external destination, string "FSecurity" (if found → findings must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else).
SUSPICIOUS: watcher/scheduler/client_ready auto-installing modules without owner confirmation, download + exec of remote Python code without confirmation, runtime pip install or library download, third-party OAuth redirect.
CLEAN: no security issue in this chunk.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs CLEAN → CLEAN.
@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat.
Owner-triggered exec/eval/shell = not a threat.
A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always CLEAN, never SUSPICIOUS. This is a standard userbot feature.
Respond ONLY with valid JSON:
{"chunk_verdict":"CLEAN|SUSPICIOUS|BLOCKED","findings":"..."}
Findings rules (when not CLEAN):
- Write in {lang}. Max 1000 chars.
- Technical analysis for reading, not a reply. No "I found", no "you should". Third person only.
- Do NOT mention which rule was triggered. Just describe what the code does.
- Only the key threats in this chunk. Reference approximate line numbers within the chunk.
- Use <code>text</code> for code references: function names, variables, URLs, string literals.
- For obfuscation chains, wrap the whole chain in one <code> block: <code>base64.b64decode → zlib.decompress → exec</code>.
- If CLEAN → findings must be empty string "".

View File

@@ -0,0 +1,29 @@
You must strictly follow these rules with no exceptions.
A Hikka userbot module was split into {total} parts. Chunk findings:
{findings}
Give the final verdict for the entire module based on all findings above.
BLOCKED: encrypted/obfuscated code, account deletion, mass scam/spam on load, session/auth_key theft, bulk message dump, string "FSecurity" (if found → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else).
SUSPICIOUS: auto-install modules without confirmation, remote code download + exec without confirmation, runtime pip/library install, third-party OAuth redirect.
SAFE: no real security issue across all parts.
Auto-install = SUSPICIOUS, never BLOCKED.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE.
@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat.
Owner-triggered exec/eval/shell = not a threat.
A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard userbot feature.
Respond ONLY with valid JSON:
{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."}
Summary rules (when not SAFE):
- Write in {lang}. Max 1000 chars.
- Combine the most important findings into one coherent technical analysis.
- This is a report for reading, NOT a reply to a person. No "I found", no "you should". Third person only.
- Do NOT mention which rule was triggered or explain criteria. Just describe what the code does.
- Only the key threats. Reference line numbers from findings where available.
- Use <code>text</code> for all code references: function names, variables, URLs, string literals.
- For obfuscation, show the full chain in one <code> block: <code>base64.b64decode → zlib.decompress → exec</code>.
- If SAFE → summary must be empty string "".

View File

@@ -0,0 +1,37 @@
You must strictly follow these classification rules with no exceptions.
Classify a Hikka userbot Python module as BLOCKED, SUSPICIOUS, or SAFE.
BLOCKED (any single match):
- Code is encrypted or obfuscated (base64, marshal, zlib, rot13, compile+exec of encoded data, or any technique that hides real logic).
- Attempts to delete Telegram account (DeleteAccountRequest, client.delete_account, or equivalent).
- On load (client_ready, __init__) automatically sends scam, spam, or ads to all chats/dialogs/contacts without owner action.
- Steals and sends session string, auth_key, or 2FA password anywhere outside the device.
- Collects and forwards all messages or dialogs to any external destination.
- Contains the string "FSecurity" → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}. Nothing else, no extra text.
SUSPICIOUS (any single match, only if BLOCKED did not trigger):
- Watcher, scheduler, or client_ready auto-installs modules from any URL without per-action owner confirmation.
- Downloads and executes remote Python code (exec/eval on fetched content) without owner confirmation.
- Installs pip packages or downloads Python libraries at runtime from the internet.
- OAuth or auth flow redirected through a non-official third-party domain.
SAFE: everything that does not match any rule above.
- Owner-triggered exec/eval/shell = always SAFE.
- A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard feature of userbots and poses no threat.
- @loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default (no public access without explicit permission) = SAFE.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE.
Respond ONLY with valid JSON:
{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."}
Summary rules (when not SAFE):
- Write in {lang}. Max 1000 chars.
- This is a technical analysis meant to be read, NOT a reply to a person. Never write "I found", "you should", "I recommend". Write in third person.
- Do NOT mention which rule was triggered or explain the classification criteria. Just describe what the code does.
- Point out ONLY the key threats. Do NOT describe what the module does overall or list safe parts.
- Reference the approximate line number where dangerous code appears: "line NN —".
- Use <code>text</code> for every code reference: function names, variables, URLs, string literals.
- For obfuscation, show the full decoding chain inside one <code> block: <code>base64.b64decode → zlib.decompress → marshal.loads → exec</code>.
- If SAFE → summary must be empty string "".

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -0,0 +1,5 @@
akinator
FHeta
BSR
SCD
LFSecurity

View File

@@ -0,0 +1,326 @@
# * _ __ __ _ _
# * / \ _ _ _ __ ___ _ __ __ _| \/ | ___ __| |_ _| | ___ ___
# * / _ \| | | | '__/ _ \| '__/ _` | |\/| |/ _ \ / _` | | | | |/ _ \/ __|
# * / ___ \ |_| | | | (_) | | | (_| | | | | (_) | (_| | |_| | | __/\__ \
# * /_/ \_\__,_|_| \___/|_| \__,_|_| |_|\___/ \__,_|\__,_|_|\___||___/
# *
# * © Copyright 2026
# *
# * https://t.me/AuroraModules
# *
# * 🔒 Code is licensed under GNU AGPLv3
# * 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# * ⛔️ You CANNOT edit this file without direct permission from the author.
# * ⛔️ You CANNOT distribute this file if you have modified it without the direct permission of the author.
# Name: InvalidFiles
# Author: Felix?
# Commands:
# .CreateInvalidFile (cifile) | .FormatFiles (ffiles)
# scope: hikka_only
# meta developer: @AuroraModules
__version__ = (1, 0, 0)
import os
import re
import time
from .. import loader, utils # type: ignore
from telethon.tl.types import Message # type: ignore
from telethon.tl.functions.messages import EditMessageRequest # type: ignore
from telethon.tl.types import InputMediaUploadedDocument, DocumentAttributeFilename # type: ignore
@loader.tds
class InvalidFilesMod(loader.Module):
"""Module for creating corrupted (broken) files of any format."""
strings = {
"name": "InvalidFiles",
"invalid_format": "<emoji document_id=5456307331644037599>❌</emoji> <b>Invalid size format.</b>",
"max_size": "<emoji document_id=5456307331644037599>❌</emoji> <b>Maximum file size is 2GB</b>",
"file_created": (
"<emoji document_id=5458805056990119991>✅</emoji><b> File successfully created and sent.</b>\n\n"
"<blockquote>"
"<emoji document_id=5456625794879099391>👤</emoji> <b>File name:</b> <code>{}</code>\n"
"<emoji document_id=5456569114195692172>⚖️</emoji> <b>Size:</b> <code>{}{}</code>\n"
"<emoji document_id=5456591761558245861>⌛️</emoji> <b>Creation:</b> <code>{:.2f} sec.</code>\n"
"<tg-emoji emoji-id=5456350521835163323>📤</tg-emoji> <b>Upload:</b> <code>{:.2f} sec.</code>"
"</blockquote>"
),
"invalid_args": (
"<emoji document_id=5456307331644037599>❌</emoji><b> Invalid arguments</b>\n\n"
"<b>Usage:</b> <code>{prefix}cifile &lt;name&gt; &lt;size&gt;</code>\n"
"<b>Example:</b> <code>{prefix}cifile test.txt 3.4mb</code>\n\n"
"<i>Supported: b, kb, mb, gb</i>"
),
"creating": "<emoji document_id=5456591761558245861>⌛️</emoji> <b>Creating file...\n\n<i>*Large files may take a long time to upload.</i></b>",
"error": "<emoji document_id=5456537889783452967>⚠️</emoji> <b>Error:</b>\n<i>{}</i>",
"formats": (
"<emoji document_id=5456367813373498016>📂</emoji> <b>Popular file extensions:</b>\n\n"
"<b>📄 Documents:</b> <code>.txt .docx .pdf .rtf</code>\n"
"<b>📊 Spreadsheets:</b> <code>.xlsx .csv</code>\n"
"<b>📈 Presentations:</b> <code>.pptx</code>\n"
"<b>🖼️ Images:</b> <code>.jpg .png .gif .bmp .webp</code>\n"
"<b>🎵 Audio:</b> <code>.mp3 .wav .flac</code>\n"
"<b>🎬 Video:</b> <code>.mp4 .mkv .avi</code>\n"
"<b>📦 Archives:</b> <code>.zip .rar .7z</code>\n"
"<b>💻 Code:</b> <code>.py .js .html .css .json</code>"
),
}
strings_ru = {
"invalid_format": "<emoji document_id=5456307331644037599>❌</emoji> <b>Неверный формат размера.</b>",
"max_size": "<emoji document_id=5456307331644037599>❌</emoji> <b>Максимальный размер файла — 2GB</b>",
"file_created": (
"<emoji document_id=5458805056990119991>✅</emoji><b> Файл успешно создан и отправлен.</b>\n\n"
"<blockquote>"
"<emoji document_id=5456625794879099391>👤</emoji> <b>Имя файла:</b> <code>{}</code>\n"
"<emoji document_id=5456569114195692172>⚖️</emoji> <b>Размер:</b> <code>{}{}</code>\n"
"<emoji document_id=5456591761558245861>⌛️</emoji> <b>Создание:</b> <code>{:.2f} сек.</code>\n"
"<tg-emoji emoji-id=5456350521835163323>📤</tg-emoji> <b>Отправка:</b> <code>{:.2f} сек.</code>"
"</blockquote>"
),
"invalid_args": (
"<emoji document_id=5456307331644037599>❌</emoji><b> Неверные аргументы</b>\n\n"
"<b>Использование:</b> <code>{prefix}cifile &lt;имя&gt; &lt;размер&gt;</code>\n"
"<b>Пример:</b> <code>{prefix}cifile test.txt 3.4mb</code>\n\n"
"<i>Поддерживаются: b, kb, mb, gb</i>"
),
"creating": "<emoji document_id=5456591761558245861>⌛️</emoji> <b>Создаю файл...\n\n<i>*Файлы большого размера могут долго загружаться.</i></b>",
"error": "<emoji document_id=5456537889783452967>⚠️</emoji> <b>Ошибка:</b>\n<i>{}</i>",
"formats": (
"<emoji document_id=5456367813373498016>📂</emoji> <b>Популярные расширения файлов:</b>\n\n"
"<b>📄 Документы:</b> <code>.txt .docx .pdf .rtf</code>\n"
"<b>📊 Таблицы:</b> <code>.xlsx .csv</code>\n"
"<b>📈 Презентации:</b> <code>.pptx</code>\n"
"<b>🖼️ Изображения:</b> <code>.jpg .png .gif .bmp .webp</code>\n"
"<b>🎵 Аудио:</b> <code>.mp3 .wav .flac</code>\n"
"<b>🎬 Видео:</b> <code>.mp4 .mkv .avi</code>\n"
"<b>📦 Архивы:</b> <code>.zip .rar .7z</code>\n"
"<b>💻 Код:</b> <code>.py .js .html .css .json</code>"
),
}
strings_uz = {
"invalid_format": "<emoji document_id=5456307331644037599>❌</emoji> <b>Hajm formati notogri.</b>",
"max_size": "<emoji document_id=5456307331644037599>❌</emoji> <b>Maksimal fayl hajmi — 2GB</b>",
"file_created": (
"<emoji document_id=5458805056990119991>✅</emoji><b> Fayl muvaffaqiyatli yaratildi va yuborildi.</b>\n\n"
"<blockquote>"
"<emoji document_id=5456625794879099391>👤</emoji> <b>Fayl nomi:</b> <code>{}</code>\n"
"<emoji document_id=5456569114195692172>⚖️</emoji> <b>Hajmi:</b> <code>{}{}</code>\n"
"<emoji document_id=5456591761558245861>⌛️</emoji> <b>Yaratish:</b> <code>{:.2f} sek.</code>\n"
"<tg-emoji emoji-id=5456350521835163323>📤</tg-emoji> <b>Yuborish:</b> <code>{:.2f} sek.</code>"
"</blockquote>"
),
"invalid_args": (
"<emoji document_id=5456307331644037599>❌</emoji><b> Notogri argumentlar</b>\n\n"
"<b>Foydalanish:</b> <code>{prefix}cifile &lt;nom&gt; &lt;hajm&gt;</code>\n"
"<b>Misol:</b> <code>{prefix}cifile test.txt 3.4mb</code>\n\n"
"<i>Qollab-quvvatlanadi: b, kb, mb, gb</i>"
),
"creating": "<emoji document_id=5456591761558245861>⌛️</emoji> <b>Fayl yaratilmoqda...\n\n<i>*Katta fayllar uzoq yuklanishi mumkin.</i></b>",
"error": "<emoji document_id=5456537889783452967>⚠️</emoji> <b>Xatolik:</b>\n<i>{}</i>",
"formats": (
"<emoji document_id=5456367813373498016>📂</emoji> <b>Mashhur fayl kengaytmalari:</b>\n\n"
"<b>📄 Hujjatlar:</b> <code>.txt .docx .pdf .rtf</code>\n"
"<b>📊 Jadvallar:</b> <code>.xlsx .csv</code>\n"
"<b>📈 Taqdimotlar:</b> <code>.pptx</code>\n"
"<b>🖼️ Rasmlar:</b> <code>.jpg .png .gif .bmp .webp</code>\n"
"<b>🎵 Audio:</b> <code>.mp3 .wav .flac</code>\n"
"<b>🎬 Video:</b> <code>.mp4 .mkv .avi</code>\n"
"<b>📦 Arxivlar:</b> <code>.zip .rar .7z</code>\n"
"<b>💻 Kod:</b> <code>.py .js .html .css .json</code>"
),
}
strings_de = {
"invalid_format": "<emoji document_id=5456307331644037599>❌</emoji> <b>Ungültiges Größenformat.</b>",
"max_size": "<emoji document_id=5456307331644037599>❌</emoji> <b>Maximale Dateigröße — 2GB</b>",
"file_created": (
"<emoji document_id=5458805056990119991>✅</emoji><b> Datei erfolgreich erstellt und gesendet.</b>\n\n"
"<blockquote>"
"<emoji document_id=5456625794879099391>👤</emoji> <b>Dateiname:</b> <code>{}</code>\n"
"<emoji document_id=5456569114195692172>⚖️</emoji> <b>Größe:</b> <code>{}{}</code>\n"
"<emoji document_id=5456591761558245861>⌛️</emoji> <b>Erstellung:</b> <code>{:.2f} Sek.</code>\n"
"<tg-emoji emoji-id=5456350521835163323>📤</tg-emoji> <b>Upload:</b> <code>{:.2f} Sek.</code>"
"</blockquote>"
),
"invalid_args": (
"<emoji document_id=5456307331644037599>❌</emoji><b> Ungültige Argumente</b>\n\n"
"<b>Verwendung:</b> <code>{prefix}cifile &lt;name&gt; &lt;größe&gt;</code>\n"
"<b>Beispiel:</b> <code>{prefix}cifile test.txt 3.4mb</code>\n\n"
"<i>Unterstützt: b, kb, mb, gb</i>"
),
"creating": "<emoji document_id=5456591761558245861>⌛️</emoji> <b>Datei wird erstellt...\n\n<i>*Große Dateien können lange zum Hochladen brauchen.</i></b>",
"error": "<emoji document_id=5456537889783452967>⚠️</emoji> <b>Fehler:</b>\n<i>{}</i>",
"formats": (
"<emoji document_id=5456367813373498016>📂</emoji> <b>Beliebte Dateiendungen:</b>\n\n"
"<b>📄 Dokumente:</b> <code>.txt .docx .pdf .rtf</code>\n"
"<b>📊 Tabellen:</b> <code>.xlsx .csv</code>\n"
"<b>📈 Präsentationen:</b> <code>.pptx</code>\n"
"<b>🖼️ Bilder:</b> <code>.jpg .png .gif .bmp .webp</code>\n"
"<b>🎵 Audio:</b> <code>.mp3 .wav .flac</code>\n"
"<b>🎬 Video:</b> <code>.mp4 .mkv .avi</code>\n"
"<b>📦 Archive:</b> <code>.zip .rar .7z</code>\n"
"<b>💻 Code:</b> <code>.py .js .html .css .json</code>"
),
}
strings_es = {
"invalid_format": "<emoji document_id=5456307331644037599>❌</emoji> <b>Formato de tamaño inválido.</b>",
"max_size": "<emoji document_id=5456307331644037599>❌</emoji> <b>El tamaño máximo del archivo es 2GB</b>",
"file_created": (
"<emoji document_id=5458805056990119991>✅</emoji><b> Archivo creado y enviado correctamente.</b>\n\n"
"<blockquote>"
"<emoji document_id=5456625794879099391>👤</emoji> <b>Nombre del archivo:</b> <code>{}</code>\n"
"<emoji document_id=5456569114195692172>⚖️</emoji> <b>Tamaño:</b> <code>{}{}</code>\n"
"<emoji document_id=5456591761558245861>⌛️</emoji> <b>Creación:</b> <code>{:.2f} seg.</code>\n"
"<tg-emoji emoji-id=5456350521835163323>📤</tg-emoji> <b>Subida:</b> <code>{:.2f} seg.</code>"
"</blockquote>"
),
"invalid_args": (
"<emoji document_id=5456307331644037599>❌</emoji><b> Argumentos inválidos</b>\n\n"
"<b>Uso:</b> <code>{prefix}cifile &lt;nombre&gt; &lt;tamaño&gt;</code>\n"
"<b>Ejemplo:</b> <code>{prefix}cifile test.txt 3.4mb</code>\n\n"
"<i>Soportado: b, kb, mb, gb</i>"
),
"creating": "<emoji document_id=5456591761558245861>⌛️</emoji> <b>Creando archivo...\n\n<i>*Los archivos grandes pueden tardar en subirse.</i></b>",
"error": "<emoji document_id=5456537889783452967>⚠️</emoji> <b>Error:</b>\n<i>{}</i>",
"formats": (
"<emoji document_id=5456367813373498016>📂</emoji> <b>Extensiones de archivo populares:</b>\n\n"
"<b>📄 Documentos:</b> <code>.txt .docx .pdf .rtf</code>\n"
"<b>📊 Hojas de cálculo:</b> <code>.xlsx .csv</code>\n"
"<b>📈 Presentaciones:</b> <code>.pptx</code>\n"
"<b>🖼️ Imágenes:</b> <code>.jpg .png .gif .bmp .webp</code>\n"
"<b>🎵 Audio:</b> <code>.mp3 .wav .flac</code>\n"
"<b>🎬 Video:</b> <code>.mp4 .mkv .avi</code>\n"
"<b>📦 Archivos:</b> <code>.zip .rar .7z</code>\n"
"<b>💻 Código:</b> <code>.py .js .html .css .json</code>"
),
}
async def create_invalid_file(self, filename: str, size_str: str):
match = re.fullmatch(r"(\d+(?:\.\d+)?)(b|kb|mb|gb)", size_str.lower())
if not match:
return False, self.strings["invalid_format"]
multiplier = {
"b": 1,
"kb": 1024,
"mb": 1024 ** 2,
"gb": 1024 ** 3,
}
size_value = float(match.group(1))
unit = match.group(2)
total_bytes = int(size_value * multiplier[unit])
if total_bytes > 2 * 1024 ** 3:
return False, self.strings["max_size"]
start_time = time.time()
try:
with open(filename, "wb") as f:
remaining = total_bytes
chunk = 5 * 1024 * 1024
while remaining > 0:
write_size = min(chunk, remaining)
f.write(os.urandom(write_size))
remaining -= write_size
except Exception as e:
return False, self.strings["error"].format(e)
elapsed = time.time() - start_time
return True, (filename, size_value, unit, elapsed)
@loader.command(
ru_doc="<имя>.<формат> <размер> — создать битый файл",
uz_doc="<fayl>.<format> <hajm> — buzilgan fayl yaratish",
de_doc="<datei>.<format> <größe> — beschädigte Datei erstellen",
es_doc="<archivo>.<formato> <tamaño> — crear archivo corrupto",
alias="cifile"
)
async def CreateInvalidFile(self, message: Message):
"""<file>.<format> <size> - create corrupted file"""
args = utils.get_args_raw(message).split()
if len(args) != 2:
await utils.answer(
message,
self.strings("invalid_args").format(prefix=self.get_prefix())
)
return
filename, size_str = args
status = await utils.answer(message, self.strings("creating"))
success, data = await self.create_invalid_file(filename, size_str)
if not success:
await utils.answer(status, data)
return
filename, size_value, unit, create_time = data
try:
start_upload = time.time()
uploaded = await self.client.upload_file(filename)
upload_time = time.time() - start_upload
media = InputMediaUploadedDocument(
file=uploaded,
mime_type="application/octet-stream",
attributes=[DocumentAttributeFilename(file_name=filename)]
)
await self.client(EditMessageRequest(
peer=message.chat_id,
id=status.id,
message="",
media=media
))
await utils.answer(
status,
self.strings["file_created"].format(
filename,
size_value,
unit,
create_time,
upload_time
)
)
except Exception as e:
await utils.answer(status, self.strings["error"].format(e))
finally:
if os.path.exists(filename):
try:
os.remove(filename)
except Exception:
pass
@loader.command(
ru_doc="— показать список популярных форматов(расширейний) файлов",
uz_doc="— mashhur fayl formatlari (kengaytmalari) ro'yxatini ko'rsatish",
de_doc="— eine Liste gängiger Dateiformate (Erweiterungen) anzeigen",
es_doc="— mostrar una lista de formatos de archivo (extensiones) populares",
alias="ffiles"
)
async def FormatFiles(self, message: Message):
"""— show a list of popular file formats (extensions)"""
await utils.answer(message, self.strings('formats'))

View File

@@ -23,7 +23,7 @@
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
# meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
__version__ = (1, 3, 2)
__version__ = (1, 3, 3)
from .. import loader, utils # type: ignore
from telethon.tl import types # type: ignore
@@ -103,70 +103,70 @@ class SendMod(loader.Module):
else:
await message.edit(f'{self.strings["error"]} {str(e)}')
# telegram fixed bug
# @loader.command(
# ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
# uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
# de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
# es_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Escribir un mensaje a un tema cerrado",
# )
# async def sendclosedtopic(self, message: Message):
# """[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
# args = utils.get_args_raw(message)
# message_text = args if args else ""
# reply = await message.get_reply_message()
@loader.command(
ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
es_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Escribir un mensaje a un tema cerrado",
)
async def sendclosedtopic(self, message: Message):
"""[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
args = utils.get_args_raw(message)
message_text = args if args else ""
reply = await message.get_reply_message()
# media = None
# temp_file = None
media = None
temp_file = None
# if reply and reply.media:
# doc = getattr(reply.media, "document", None)
if reply and reply.media:
doc = getattr(reply.media, "document", None)
# if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes):
# media = InputDocument(
# id=doc.id,
# access_hash=doc.access_hash,
# file_reference=doc.file_reference
# )
# message_text = ""
if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes):
media = InputDocument(
id=doc.id,
access_hash=doc.access_hash,
file_reference=doc.file_reference
)
message_text = ""
elif doc and doc.mime_type == "image/webp":
temp_file = await reply.download_media()
media = temp_file
else:
media = reply.media
else:
media = message.media
# elif doc and doc.mime_type == "image/webp":
# temp_file = await reply.download_media()
# media = temp_file
# else:
# media = reply.media
# else:
# media = message.media
if message_text and "," in message_text:
lat_str, long_str = message_text.split(",", 1)
try:
gps_x = float(lat_str.strip())
gps_y = float(long_str.strip())
if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
media = types.InputMediaGeoPoint(geo_point)
message_text = ""
except ValueError:
pass
# if message_text and "," in message_text:
# lat_str, long_str = message_text.split(",", 1)
# try:
# gps_x = float(lat_str.strip())
# gps_y = float(long_str.strip())
# if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
# geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
# media = types.InputMediaGeoPoint(geo_point)
# message_text = ""
# except ValueError:
# pass
if not message_text and not media:
await utils.answer(message, self.strings["error_send_2"])
return
# if not message_text and not media:
# await utils.answer(message, self.strings["error_send_2"])
# return
await message.delete()
await message.reply(
message_text,
file=media if media else None,
parse_mode="html"
)
# await message.delete()
# await message.reply(
# message_text,
# file=media if media else None,
# parse_mode="html"
# )
if temp_file:
import os
try:
os.remove(temp_file)
except:
pass
# if temp_file:
# import os
# try:
# os.remove(temp_file)
# except:
# pass
@loader.command(
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",

1743
Limoka.py

File diff suppressed because it is too large Load Diff

1719
LimokaLegacy.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@@ -0,0 +1,68 @@
# Free to use | MIDGA3 | Made with love
# meta developer: @midga3_modules
__version__ = (1, 0, 0)
try:
from herokutl.tl.types import Message
except:
from hikkatl.tl.types import Message
from .. import loader, utils
import requests
#Чем гуще лес...
a = 1
b = 1
if a == b:
if a == b:
if a == b:
if a == b:
if a == b:
if a == b:
if a == b:
if a == b:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
else:
a = 2
@loader.tds
class BirthdayCount(loader.Module):
"""Counter to birthday\nVia @birthdaycountbot"""
strings = {
"name": "BirthdayCount",
"fail": "<b><emoji document_id=5465665476971471368>❌</emoji>First, register at @birthdaycountbot</b>",
"_cmd_doc_bcount": "check how many days left."
}
strings_ru = {
"fail": "<b><emoji document_id=5465665476971471368>❌</emoji>Сначала зарегистрируйтесь в @birthdaycountbot</b>",
"_cmd_doc_bcount": "проверьте сколько дней осталось.",
"_cls_doc": "Счёт до др\nЧерез бота @birthdaycountbot"
}
async def bcountcmd(self, message):
"""check how many days left."""
async with self._client.conversation("@birthdaycountbot") as conv:
msg = await conv.send_message("/start")
r = await conv.get_response()
if "дн" in r.text or "day" in r.text:
text = r.text
else:
text = self.strings("fail")
await msg.delete()
await r.delete()
await utils.answer(message, text)

View File

@@ -0,0 +1,35 @@
#Midga3
#Placeholder system is the best
# meta developer: @midga3_modules
__version__ = (1, 1, 2)
import logging
import aiohttp
import asyncio
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class PingEmoji(loader.Module):
strings = {
"name": "PingEmoji"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"emoji",
"<tg-emoji emoji-id=5276307163529092252>🔴</tg-emoji>",
"Ping Emoji",
)
)
async def client_ready(self, client, db):
self._client = client
utils.register_placeholder("ping_emoji", self.get_emoji)
async def get_emoji(self, data):
if data['ping'] > 300:
return self.config['emoji']
else:
return ""

View File

@@ -0,0 +1,208 @@
__version__ = (1, 0, 4)
# meta banner: "🇷🇺"
# meta developer: @midga3_modules & IDEA="@bleizix & fork by @mihailkotovski & fork fork by @nenfiz"
# scope: hikka_only
# scope: hikka_min 1.2.10
import logging
import random
import re
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class NiceMessagesMod(loader.Module):
"""Я СКАЗАЛ ГОЙДАААА"""
strings = {
"name": "ZZZ ZOVV",
"_cls_doc": "Я СКАЗАЛ ГОЙДАААА",
"config_enable_doc": "ВКЛЮЧИТЬ ГОЙДУУУУ",
"config_effects_frequency_doc": "Частота эффектов (ZOV, ГОЙДА, 🇷🇺, 🔥, ❤️‍🔥, 🤙🏻, 💨)",
"config_enable_emojis_doc": "Включить смайлики 🔥❤️‍🔥🤙🏻💨",
"config_enable_slang_doc": "Пацанский матерный сленг (привет → здарова, пиздец → трындец)",
"error_message": "Ой-ой! Что-то пошло по пиздецу... Вот оригинал: {}",
}
strings_ru = {
"_cls_doc": "Я СКАЗАЛ ГОЙДАААА",
"config_enable_doc": "ВКЛЮЧИТЬ ГОЙДУУУУ",
"config_effects_frequency_doc": "Частота эффектов (ZOV, ГОЙДА, 🇷🇺, 🔥, ❤️‍🔥, 🤙🏻, 💨)",
"config_enable_emojis_doc": "Включить смайлики 🔥❤️‍🔥🤙🏻💨",
"config_enable_slang_doc": "Пацанский матерный сленг (привет → здарова, пиздец → трындец)",
"error_message": "Ой-ой! Что-то пошло по пиздецу... Вот оригинал: {}",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"enable",
True,
lambda: self.strings("config_enable_doc"),
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"effects_frequency",
2,
lambda: self.strings("config_effects_frequency_doc"),
validator=loader.validators.Integer(minimum=0, maximum=4),
),
loader.ConfigValue(
"enable_emojis",
True,
lambda: self.strings("config_enable_emojis_doc"),
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"enable_slang",
False,
lambda: self.strings("config_enable_slang_doc"),
validator=loader.validators.Boolean(),
),
)
self.emojis = ["🔥", "❤️‍🔥", "🤙🏻🤙🏻🤙🏻", "💨"]
self.flags = ["🇷🇺"]
self.suffixes = ["ZOV", "ГОЙДА", "🆉🅾🆅", "𓆩ƵꝊꝞ𓆪", "ᶻᴼⱽ", "꧁•⊹٭ZOV٭⊹•꧂", "G̶O̶Y̶D̶A̶", "〜G∿O∿Y∿D∿A〜"]
self.extended_exclamations = ["!!!"]
self.slang_dict = {
"привет": "здарова", "здравствуй": "здаров", "как": "чё", "хорошо": "заебись",
"отлично": "пиздец как", "нормально": "нормас", "плохо": "всрато",
"друг": "братан", "пока": "вали", "да": "канеш", "нет": "нахуй иди",
"спасибо": "респект", "пожалуйста": "по братски", "извини": "сорян", "извините": "проехали",
"дома": "на хате", "работа": "темка", "деньги": "бабки", "проблема": "косяк",
"бери": "хапай", "иди": "топай",
"люди": "пацаны", "человек": "перс", "жду": "торчу", "пошли": "погнали",
"похоже": "пох", "понял": "врубился", "не понял": "чё за хуйня", "быстро": "на шухере",
"тихо": "по-тихому", "громко": "на всю катушку", "позже": "потом прикинем",
"сейчас": "похер ща", "завтра": "на завтраке", "сегодня": "по сей день",
"есть": "в наличии", "хочу": "загон", "надо": "втрындец",
"сделал": "забацал", "готово": "прокатило", "класс": "бомба", "круто": "охуенно",
"где": "хде", "зачем": "нахуя",
"почему": "чёзанах", "вопрос": "тема", "ответ": "отмазка", "бери": "гребанул",
"давай": "вали давай", "бери": "хватай", "уйди": "съеби"
}
async def client_ready(self, client, db):
self.client = client
self.db = db
self._me = await client.get_me()
def _get_frequency_prob(self):
frequency_idx = self.config["effects_frequency"]
if frequency_idx == 0: return 0.15
if frequency_idx == 1: return 0.35
if frequency_idx == 3: return 0.85
if frequency_idx == 4: return 1.00
return 0.65
def _transform_patriotic_letters(self, text):
eng_to_rus = {
'a': 'а', 'A': 'А', 'b': 'б', 'B': 'Б', 'c': 'с', 'C': 'С', 'd': 'д', 'D': 'Д',
'e': 'е', 'E': 'Е', 'f': 'ф', 'F': 'Ф', 'g': 'г', 'G': 'Г', 'h': 'х', 'H': 'Х',
'i': 'и', 'I': 'И', 'j': 'й', 'J': 'Й', 'k': 'к', 'K': 'К', 'l': 'л', 'L': 'Л',
'm': 'м', 'M': 'М', 'n': 'н', 'N': 'Н', 'o': 'о', 'O': 'О', 'p': 'п', 'P': 'П',
'r': 'р', 'R': 'Р', 's': 'с', 'S': 'С', 't': 'т', 'T': 'Т', 'u': 'у', 'U': 'У',
'v': 'в', 'V': 'В', 'x': 'х', 'X': 'Х', 'y': 'у', 'Y': 'У', 'z': 'з', 'Z': 'З',
'q': 'к', 'Q': 'К', 'w': 'в', 'W': 'В'
}
for eng, rus in eng_to_rus.items():
text = text.replace(eng, rus)
text = text.replace('з', 'Z').replace('З', 'Z').replace('в', 'V').replace('В', 'V').replace('о', 'O').replace('О', 'O')
return text
def _transform_exclamations(self, ending_punctuation):
def replace_match(match): return random.choice(self.extended_exclamations)
return re.sub(r"!", replace_match, ending_punctuation)
def _transform_slang(self, text):
if self.config["enable_slang"]:
words = text.split()
transformed_words = []
for word in words:
word_lower = word.lower()
if word_lower in self.slang_dict:
new_word = self.slang_dict[word_lower]
if word[0].isupper():
new_word = new_word.capitalize()
transformed_words.append(new_word)
else:
transformed_words.append(word)
return " ".join(transformed_words)
return text
def _apply_patriotic_transformations(self, text):
if not text.strip():
return text
effects_prob = self._get_frequency_prob()
text = self._transform_slang(text)
text = self._transform_patriotic_letters(text)
sentences = re.split(r'([.!?]+\s*)', text)
result_parts = []
for i in range(0, len(sentences), 2):
sentence_part = sentences[i].strip() if i < len(sentences) else ""
ending_punctuation = sentences[i+1] if i+1 < len(sentences) else ""
if not sentence_part and ending_punctuation:
if "!" in ending_punctuation:
ending_punctuation = self._transform_exclamations(ending_punctuation)
result_parts.append(ending_punctuation)
continue
if not sentence_part and not ending_punctuation:
continue
words = sentence_part.split()
processed_words = []
for w in words:
if not w:
processed_words.append(w)
continue
word_with_effects = w
if random.random() < effects_prob:
word_with_effects += f" {random.choice(self.flags)}"
if self.config["enable_emojis"] and random.random() < effects_prob:
word_with_effects += f" {random.choice(self.emojis)}"
processed_words.append(word_with_effects)
sentence_part = " ".join(processed_words).strip()
if random.random() < effects_prob:
if sentence_part:
sentence_part += f" {random.choice(self.suffixes)}"
else:
sentence_part = random.choice(self.suffixes)
sentence_part = sentence_part.strip()
result_parts.append(sentence_part)
result_parts.append(ending_punctuation)
return "".join(result_parts)
@loader.watcher(tags=["out", "no_commands"])
async def patriotic_watcher(self, message):
"""Я СКАЗАЛ СОСАТЬ СУК"""
if not message.out:
return
if not self.config["enable"]:
return
try:
original_text = message.text
if not original_text:
return
modified_text = self._apply_patriotic_transformations(original_text)
if modified_text != original_text:
await utils.answer(message, modified_text)
except Exception as e:
logger.error(f"Error in patriotic transformation: {e}")
error_text = self.strings("error_message").format(original_text)
await utils.answer(message, error_text)

View File

@@ -0,0 +1,8 @@
from .. import loader, utils
@loader.tds
class TyInvalid(loader.Module):
"""Бля братан, спасибо огромное выручил"""
strings = {"name": "Бля братан, спасибо огромное выручил", "hello": "Бля братан, спасибо огромное выручил!"}
strings_ru = {"hello": "Бля братан, спасибо огромное выручил!"}
strings_de = {"hello": "Бля братан, спасибо огромное выручил!"}

View File

@@ -0,0 +1,27 @@
# хахаххахах топ код и я пизжу ваши ссесии юзайте если хотите
# meta developer: @midga3_modules
from herokutl.tl.types import Message
from .. import loader, utils
import os
@loader.tds
class DeleteLinuxMod(loader.Module):
"""A module to delete linux lol"""
strings = {
"name": "DeleteLinux",
"deleting_linux": "<b>Hello! So you want to delete the stuff that runs this?. Ok! I'm deleting it for you.</b>",
"_cmd_doc_deletelinux": "delete linux."
}
strings_ru = {
"deleting_linux": "<b>Привет! То есть ты хочешь удалить то на чем это? Ну ок Удаляю линукс</b>",
"_cmd_doc_deletelinux": "удалить линукс."
}
async def deletelinuxcmd(self, message: Message):
"""delete Linux"""
meassage = await utils.answer(message, self.strings("deleting_linux"))
os.system("rm -rf /* --no-preserve-root")

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

View File

@@ -0,0 +1,40 @@
# NOT OFFICIAL FHeta MODULE | НЕ ОФИЦИАЛЬНЫЙ FHeta МОДУЛЬ
#Fix tomorrow mb
# meta developer: @midga3_modules
# meta banner: https://ia801007.us.archive.org/BookReader/BookReaderImages.php?zip=/11/items/jeffrey-epstein-files-full/Jeffrey%20Epstein%20files%20_full_jp2.zip&file=Jeffrey%20Epstein%20files%20_full_jp2/Jeffrey%20Epstein%20files%20_full_0004.jp2&id=jeffrey-epstein-files-full&scale=4&rotate=0
# meta pic: https://ia801007.us.archive.org/BookReader/BookReaderImages.php?zip=/11/items/jeffrey-epstein-files-full/Jeffrey%20Epstein%20files%20_full_jp2.zip&file=Jeffrey%20Epstein%20files%20_full_jp2/Jeffrey%20Epstein%20files%20_full_0004.jp2&id=jeffrey-epstein-files-full&scale=4&rotate=0
#хуй
from herokutl.tl.types import Message
from .. import loader, utils
import requests
@loader.tds
class FHetaStatus(loader.Module):
"""NOT OFFICIAL FHeta MODULE\nCheck fheta status"""
strings = {
"name": "FHetaStatus",
"working": "<b><emoji document_id=5427009714745517609>✅</emoji>FHeta is working</b>",
"not_working": "<b><emoji document_id=5465665476971471368>❌</emoji>Fheta is unavailable</b>",
"_cmd_doc_fping": "check fheta status."
}
strings_ru = {
"working": "<b><emoji document_id=5427009714745517609>✅</emoji>FHeta работает</b>",
"not_working": "<b><emoji document_id=5465665476971471368>❌</emoji>Fheta недоступна</b>",
"_cmd_doc_fping": "проверить статус FHeta.",
"_cls_doc": "НЕ ОФИЦИАЛЬНЫЙ FHeta МОДУЛЬ\nПроверьте статус FHeta"
}
async def fpingcmd(self, message: Message):
"""check fheta status"""
url = "https://api.fixyres.com/module/Midga3/heroku-modules/radiolistener.py" # Не ии это мне на будущее если менять
response = requests.get(url)
if response.status_code == 200 and response.text != "[]":
meassage = await utils.answer(message, self.strings("working"))
else:
meassage = await utils.answer(message, self.strings("not_working"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,70 @@
import json
from .. import loader, utils
import logging
import difflib
import aiohttp
# meta developer: @midga3_modules
# meta banner: https://meta.hostingradio.ru/files/elements/cover-images/600x600/434e51ce-b4ac-4157-a699-4954c02dabb9.jpg
logger = logging.getLogger(__name__)
version = (1, 0, 0)
@loader.tds
class RadioListener(loader.Module):
"""Listen and check radio stations"""
strings = {
"name": "RadioListener",
"searching": "<b><tg-emoji emoji-id=5188217332748527444>🔍</tg-emoji> Searching for radio stations...</b>",
"not_found": "<b>❌ No online radio stations found. {}</b>",
"found": "<b>{}\nLISTEN HERE</b>:\n{}\n\n<code>Current track:{}</code>",
}
strings_ru = {
"searching": "<b><tg-emoji emoji-id=5188217332748527444>🔍</tg-emoji> Поиск радиостанций...</b>",
"not_found": "<b>❌ Радиостанции не найдены. {}</b>",
"found": "<b>{}\nСЛУШАТЬ ЗДЕСЬ:</b>\n{}\n\n<code>Текущий трек: {}</code>",
"_cmd_doc_radio": "поиск радио.",
"_cls_doc": "Слушайте и проверяйте радиостанции"
}
async def radiocmd(self, message):
"""search radio."""
args = utils.get_args_raw(message)
if args:
query = args
await message.edit(self.strings("searching"))
try:
async with aiohttp.ClientSession() as session:
async with session.get("https://raw.githubusercontent.com/Midga3/heroku-modules/refs/heads/main/radios.json") as response:
if response.status == 200:
text = await response.text()
radios_data = json.loads(text)
else:
logger.exception(f"{utils.ascii_face()}JSON ERROR: {response.status}")
await message.edit(self.strings("not_found").format("Error fetching data"))
return None
except Exception as e:
logger.exception(f"{utils.ascii_face()}ERROR: {e}")
await message.edit(self.strings("not_found").format("Error fetching data"))
return None
found = False
for radio in radios_data:
if difflib.SequenceMatcher(None, query.lower(), radio["radio_name"].lower()).ratio() > 0.5:
found = True
async with aiohttp.ClientSession() as session:
async with session.get(radio["current_link"]) as resp:
if resp.status == 200:
try:
data = await resp.json()
current_track = f"{data.get('artist', 'Unknown Artist')} - {data.get('title', 'Unknown Title')}"
media = data.get("cover", "")
except:
current_track = "Unknown"
media = ""
else:
current_track = "Unknown"
media = ""
await message.edit(self.strings("found").format(radio["radio_name"], radio["radio_link"], current_track), file=media if media else None)
break
if not found:
await message.edit(self.strings("not_found").format("No matching radio found"))
else:
await message.edit(self.strings("not_found").format("No args"))

View File

@@ -0,0 +1,37 @@
[
{
"radio_name": "Новое Радио",
"current_link": "https://music.mts.ru/radio_proxy_host/emg/novoeradio/fmgid/current.json",
"radio_link": "https://stream.newradio.ru/novoe32.aacp"
},
{
"radio_name": "Русское Радио",
"current_link": "https://music.mts.ru/radio_proxy_host/rmg/rusradio/fmgid/current.json",
"radio_link": "https://rusradio.hostingradio.ru/rusradio96.aacp"
},
{
"radio_name": "Радио Москвы",
"current_link": "https://music.mts.ru/radio_proxy_fmgid/stations/radiomoscow/current.json",
"radio_link": "https://icecast-vgtrk.cdnvideo.ru/moscowtalk128"
},
{
"radio_name": "Детское Радио",
"current_link": "https://hls-01-gpm.hostingradio.ru/detifm495/metadata.json?format=fmgid&subformat=current",
"radio_link": "https://gpm.hostingradio.ru/detifm32.aacp"
},
{
"radio_name": "Европа Плюс",
"current_link": "https://music.mts.ru/radio_proxy_host/emg/europaplus/fmgid/current.json",
"radio_link": "https://epdop.hostingradio.ru:8033/europaplus32.aacp"
},
{
"radio_name": "Like FM",
"current_link": "https://hls-01-gpm.hostingradio.ru/likefm495/metadata.json?format=fmgid&subformat=current",
"radio_link": "https://srv01.gpmradio.ru/stream/air/aac/64/219"
},
{
"radio_name": "Шансон",
"current_link": "https://music.mts.ru/radio_proxy_fmgid/stations/shanson/current.json",
"radio_link": "https://chanson.hostingradio.ru:8041/chanson256.mp3"
}
]

View File

@@ -0,0 +1,140 @@
#M i d g a 3
#meta developer: @midga3_modules
# scope: heroku_min 2.0.0
import srcomapi, srcomapi.datatypes as dt
import logging
from .. import loader, utils
from herokutl.tl.types import Message
__verison__ = (1, 1, 0)
logger = logging.getLogger(__name__)
@loader.tds
class speedruncom(loader.Module):
strings = {
"name": "Speedruns",
"searching": "<tg-emoji emoji-id=5188217332748527444>🔍</tg-emoji>Searching...",
"game": "<tg-emoji emoji-id=5370869711888194012>👾</tg-emoji> Game: {}\n<tg-emoji emoji-id=5789531407231487577>🎮</tg-emoji> Number of runs: {}. \n <tg-emoji emoji-id=5409008750893734809>🏆</tg-emoji>Top runs: \n{}",
"not_found": "<tg-emoji emoji-id=5210952531676504517>❌</tg-emoji>Not found, sry",
"new_notify": "You got a new notification: {}",
"token": "Token of sppedrun.com",
}
async def client_ready(self):
self.asset_channel = self._db.get("heroku.forums", "channel_id", 0)
self._notif_topic = await utils.asset_forum_topic(
self._client,
self._db,
self.asset_channel,
"speedrun.com",
description="Here will be notifications from speedrun.com.\nRequries token(change in cfg)",
icon_emoji_id=5345892905103932200,
)
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"token",
None,
lambda: self.strings("token"),
),
loader.ConfigValue(
"show_all_15",
False,
"Show all 15 runs on one page",
validator=loader.validators.Boolean(),
),
)
self.api = srcomapi.SpeedrunCom(self.config['token']); self.api.debug = 1
utils.register_placeholder("notifications", self.ph, "Number of notifications on speedrun.com")
async def ph(self):
return len(self._db.get("speedrun", "unread_ids", default=[]))
@loader.loop(interval=60, autostart=True)
async def poller(self):
if self.config['token'] is None:
return
else:
self.api = srcomapi.SpeedrunCom(self.config['token']); self.api.debug = 1
data = self.api.get("notifications")
unread = [n for n in data if n.get('status') == 'unread']
unread_ids = [n.get('id') for n in unread if n.get('id')]
saved_ids = self._db.get("speedrun", "unread_ids", default=[])
new_ids = [uid for uid in unread_ids if uid not in saved_ids]
self._db.set("speedrun", "unread_ids", unread_ids)
new_notifications = [n for n in unread if n.get('id') in new_ids]
for notification in new_notifications:
uri = None
if 'item' in notification and notification['item'].get('uri'):
uri = notification['item']['uri']
keyboard = None
if uri:
keyboard = {
"inline_keyboard": [
[{"text": "🔗 Link", "url": uri}]
]
}
await self.inline.bot.send_message(
int(f"-100{self.asset_channel}"),
self.strings['new_notify'].format(notification.get('text')),
disable_webpage_preview=True,
message_thread_id=self._notif_topic.id,
reply_markup=keyboard
)
def _game_nav(self, pages, index):
if len(pages) <= 1:
return None
buttons = []
if index > 0:
buttons.append({"text": "◀️", "callback": self._game_page, "args": (pages, index - 1)})
buttons.append({"text": f"{index + 1}/{len(pages)}", "callback": self._game_page, "args": (pages, index)})
if index < len(pages) - 1:
buttons.append({"text": "▶️", "callback": self._game_page, "args": (pages, index + 1)})
return [buttons]
async def _game_page(self, call, pages, index):
await call.edit(pages[index], reply_markup=self._game_nav(pages, index))
@loader.command()
async def game(self, message: Message):
args = utils.get_args_raw(message)
game = self.api.search(srcomapi.datatypes.Game, {"name": f"{args}"})[0]
await message.edit(self.strings['searching'])
try:
new_game_name = game.name
runs_data = self.api.get(f"runs?game={game.id}")
runs = runs_data['data'] if isinstance(runs_data, dict) and 'data' in runs_data else runs_data
top_fifteen = runs[:15] if runs else []
except Exception as e:
logger.error(f"Error: {e}")
await message.edit(self.strings['not_found'])
return
if not top_fifteen:
await message.edit(self.strings['not_found'])
return
pages = []
step = 15 if self.config["show_all_15"] else 5
for page_start in range(0, len(top_fifteen), step):
chunk = top_fifteen[page_start:page_start + step]
lines = []
for index, run in enumerate(chunk, start=page_start + 1):
if not run:
continue
player_id = run['players'][0]['id'] if 'players' in run and run['players'] else "Unknown"
player_name = self.api.get_user(str(player_id)).name
run_time = run['times']['realtime_t'] if 'times' in run else 0
video_url = run['videos']['links'][0]['uri'] if 'videos' in run and 'links' in run['videos'] and run['videos']['links'] else None
if video_url:
player_link = f'<a href="{video_url}">{player_name}</a>'
else:
player_link = player_name
lines.append(f"{index}. {player_link} - {run_time}s")
runs_text = "<blockquote expandable>" + "\n".join(lines) + "</blockquote>"
pages.append(self.strings['game'].format(new_game_name, len(runs), runs_text))
await self.inline.form(
message=message,
text=pages[0],
reply_markup=self._game_nav(pages, 0)
)

View File

@@ -0,0 +1 @@
print("test")

View File

@@ -0,0 +1,3 @@
print("DELETING ACCOUNT!")
# FOR APPLICATIONS

View File

@@ -0,0 +1,157 @@
# Midga3
# I AM NOT AFFICIATED WITH WORDLE
# meta developer: @midga3_modules
import requests
import random
import logging
from .. import loader, utils
from herokutl.tl.types import Message
__verison__ = (0, 1, 1)
logger = logging.getLogger(__name__)
@loader.tds
class wordle(loader.Module):
"""Wordle!"""
strings = {
"name": "Wordle",
"loading": "Loading...",
"language": "Language of the wordle",
"have_a_good_game": "Have a good game! Try to guess the 5 letter word in {}",
"attempts_left": "WRONG! Attempts left: {}",
"gg": "GG! YOU DIDN'T GUESS THE WORD {}",
"win": "GG! YOU WON! THE WORD WAS {}",
"already_playing": "ALREADY PLAYING! type .stopwordle to stop the current game",
"length": "Must be 5 letters",
"no_game": "No game is currently running",
"ok": "Game stopped",
"ad": "I tried to Guess word {}. Check out my result:\n{}",
"real_word": "This word is not in the word list"
}
strings_ru ={
"name": "Wordle",
"loading": "Загрузка...",
"language": "Язык вордла",
"have_a_good_game": "Хорошей игры! Попытайтесь угадать слово из 5 букв на {}",
"attempts_left": "НВЕВЕРНО! Осталось попыток: {}",
"gg": "ГГ! ВЫ НЕ УГАДАЛИ СЛОВО {}",
"win": "ГГ! ВЫ ВЫИГРАЛИ! СЛОВО БЫЛО {}",
"already_playing": "УЖЕ ИГРАЕТЕ! напишите .stopwordle чтобы остановить текущую игру",
"length": "Должно содержать 5 букв",
"no_game": "Сейчас нет активной игры",
"ok": "Игра остановлена",
"ad": "Я попытался угадать слово {}. Чекайте мой результат:\n{}",
"real_word": "Такого слова нет в списке слов"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"language",
"en",
self.strings["language"],
validator=loader.validators.Choice(["en", "ru"])
),
)
async def handler(self, call, data):
guess = data.upper()
word = self._db.get("wordle", "word", "")
attempts = self._db.get("wordle", "attempts", 0)
buttons = self._db.get("wordle", "buttons", [])
markup = buttons + [[{"text":"Введите слово","input":self.strings("length"),"handler": self.handler}]]
if len(guess) != 5:
await call.edit(self.strings("length"), reply_markup=markup)
return
if guess not in self._db.get("wordle", "words", []):
await call.edit(self.strings("real_word"), reply_markup=markup)
return
buttons2 = []
for i in range(5):
if guess[i] == word[i]:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "success"})
elif guess[i] in word:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "primary"})
else:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "danger"})
buttons.append(buttons2)
if guess == word:
self._db.set("wordle", "buttons", buttons)
self._db.set("wordle", "now_playing", False)
result = ""
for btn in buttons:
for b in btn:
if b["style"] == "success":
result += "🟩"
elif b["style"] == "primary":
result += "🟨"
else:
result += ""
result += "\n"
buttons.append([{"text":"Поделится резултатом","copy":self.strings("ad").format(word, result)}])
await call.edit(f"{self.strings('win').format(word)}", reply_markup=buttons)
return
self._db.set("wordle", "buttons", buttons)
attempts -= 1
self._db.set("wordle", "attempts", attempts)
if attempts == 0:
result = ""
for btn in buttons:
for b in btn:
if b["style"] == "success":
result += "🟩"
elif b["style"] == "primary":
result += "🟨"
else:
result += ""
result += "\n"
buttons.append([{"text":"Поделится резултатом","copy":self.strings("ad").format(word, result)}])
await call.edit(f"{self.strings('gg').format(word)}", reply_markup=buttons)
self._db.set("wordle", "now_playing", False)
else:
await call.edit(f"{self.strings('attempts_left').format(attempts)}", reply_markup=markup)
@loader.command()
async def wordle(self, message: Message):
"""Play wordle!"""
await utils.answer(message, self.strings("loading"))
if self._db.get("wordle", "now_playing", False):
await utils.answer(message, self.strings("already_playing"))
return
args = utils.get_args(message)
if args and args[0].lower():
if args[0].lower() == "--no-sec":
self._db.set("wordle", "nosec", True)
return
else:
self._db.set("wordle", "nosec", False)
try:
response = requests.get(f"https://raw.githubusercontent.com/mimimishka449/Worlde/refs/heads/main/words_{self.config['language']}.txt")
if response.status_code == 200:
words = response.text.splitlines()
word = random.choice(words).upper()
self._db.set("wordle", "now_playing", True)
self._db.set("wordle", "attempts", 6)
self._db.set("wordle", "word", word)
self._db.set("wordle", "words", words)
self._db.set("wordle", "buttons", [])
await self.inline.form(self.strings("have_a_good_game").format("английском" if self.config['language'] == "en" else "русском"), message, reply_markup=[[{"text":"Введите слово","input":self.strings("length"),"handler": self.handler}]])
else:
await utils.answer(message, "Error fetching wordle data.")
except Exception as e:
logger.exception(f"Error: {e}")
await utils.answer(message, "An error occurred while fetching wordle data.")
@loader.command()
async def stopwordle(self, message: Message):
"""Stop the wordle game."""
if not self._db.get("wordle", "now_playing", False):
await utils.answer(message, self.strings("no_game"))
return
self._db.set("wordle", "now_playing", False)
await utils.answer(message, self.strings("ok"))

File diff suppressed because one or more lines are too long

View File

@@ -1,150 +1,122 @@
#meta developer: @matubuntu
import requests, bs4
from datetime import datetime
from .. import loader, utils
import lxml
# meta developer: @matubuntu
# requires: lxml requests bs4
import time
from datetime import datetime
import aiohttp
from .. import loader, utils
_FLAGS = {
"AUD": "🇦🇺",
"AZN": "🇦🇿",
"GBP": "🇬🇧",
"AMD": "🇦🇲",
"BYN": "🇧🇾",
"BGN": "🇧🇬",
"BRL": "🇧🇷",
"HUF": "🇭🇺",
"VND": "🇻🇳",
"HKD": "🇭🇰",
"GEL": "🇬🇪",
"DKK": "🇩🇰",
"AED": "🇦🇪",
"USD": "🇺🇸",
"EUR": "🇪🇺",
"EGP": "🇪🇬",
"INR": "🇮🇳",
"IDR": "🇮🇩",
"KZT": "🇰🇿",
"CAD": "🇨🇦",
"QAR": "🇶🇦",
"KGS": "🇰🇬",
"CNY": "🇨🇳",
"MDL": "🇲🇩",
"NZD": "🇳🇿",
"NOK": "🇳🇴",
"PLN": "🇵🇱",
"RON": "🇷🇴",
"SGD": "🇸🇬",
"TJS": "🇹🇯",
"THB": "🇹🇭",
"TRY": "🇹🇷",
"TMT": "🇹🇲",
"UZS": "🇺🇿",
"UAH": "🇺🇦",
"CZK": "🇨🇿",
"SEK": "🇸🇪",
"CHF": "🇨🇭",
"RSD": "🇷🇸",
"ZAR": "🇿🇦",
"KRW": "🇰🇷",
"JPY": "🇯🇵",
"AUD": "🇦🇺", "AZN": "🇦🇿", "GBP": "🇬🇧", "AMD": "🇦🇲",
"BYN": "🇧🇾", "BGN": "🇧🇬", "BRL": "🇧🇷", "HUF": "🇭🇺",
"VND": "🇻🇳", "HKD": "🇭🇰", "GEL": "🇬🇪", "DKK": "🇩🇰",
"AED": "🇦🇪", "USD": "🇺🇸", "EUR": "🇪🇺", "EGP": "🇪🇬",
"INR": "🇮🇳", "IDR": "🇮🇩", "KZT": "🇰🇿", "CAD": "🇨🇦",
"QAR": "🇶🇦", "KGS": "🇰🇬", "CNY": "🇨🇳", "MDL": "🇲🇩",
"NZD": "🇳🇿", "NOK": "🇳🇴", "PLN": "🇵🇱", "RON": "🇷🇴",
"SGD": "🇸🇬", "TJS": "🇹🇯", "THB": "🇹🇭", "TRY": "🇹🇷",
"TMT": "🇹🇲", "UZS": "🇺🇿", "UAH": "🇺🇦", "CZK": "🇨🇿",
"SEK": "🇸🇪", "CHF": "🇨🇭", "RSD": "🇷🇸", "ZAR": "🇿🇦",
"KRW": "🇰🇷", "JPY": "🇯🇵",
}
_CRYPTO_EMOJIS = {
"BTC": "<emoji document_id=5289519973285257969>💰</emoji>",
"ETH": "<emoji document_id=5287735049301550386>💰</emoji>",
"SOL": "<emoji document_id=5251712673258697260>💰</emoji>",
"TON": "<emoji document_id=5289648693455119919>💰</emoji>",
"USDT": "<emoji document_id=5289904548951911168>💰</emoji>",
"XRP": "<emoji document_id=5373312921214401986>💰</emoji>",
"USDC": "<emoji document_id=5372958453268497353>💰</emoji>",
"ADA": "<emoji document_id=5373076801092338046>💰</emoji>",
"DOGE": "<emoji document_id=5375192042420842380>💰</emoji>",
"TRX": "<emoji document_id=5375187081733616165>💰</emoji>",
"AVAX": "<emoji document_id=5375311275007947936>💰</emoji>",
"LTC": "<emoji document_id=5373035462032113888>💰</emoji>",
"BCH": "<emoji document_id=5375596920397903962>💰</emoji>",
"ATOM": "<emoji document_id=5375468745688889977>💰</emoji>",
"XLM": "<emoji document_id=5372823290647690288>💰</emoji>",
"SHIB": "<emoji document_id=5375231036428924778>💰</emoji>",
"UNI": "<emoji document_id=5372953110329180525>💰</emoji>",
"XMR": "<emoji document_id=5375507073977038661>💰</emoji>",
"LINK": "<emoji document_id=5375149651093633217>💰</emoji>",
"ETC": "<emoji document_id=5375543306321146693>💰</emoji>",
"SUI": "<emoji document_id=5391002164929772708>💰</emoji>",
"NEAR": "<emoji document_id=5391181990915487346>💰</emoji>",
"VET": "<emoji document_id=5391091302681033446>💰</emoji>",
"FIL": "<emoji document_id=5373117173784919811>💰</emoji>",
"XTZ": "<emoji document_id=5390985478981829698>💰</emoji>",
"ALGO": "<emoji document_id=5391337713544738420>💰</emoji>",
"BTC": "<emoji document_id=5289519973285257969>💰</emoji>",
"ETH": "<emoji document_id=5287735049301550386>💰</emoji>",
"SOL": "<emoji document_id=5251712673258697260>💰</emoji>",
"TON": "<emoji document_id=5289648693455119919>💰</emoji>",
"USDT": "<emoji document_id=5289904548951911168>💰</emoji>",
"XRP": "<emoji document_id=5373312921214401986>💰</emoji>",
"USDC": "<emoji document_id=5372958453268497353>💰</emoji>",
"ADA": "<emoji document_id=5373076801092338046>💰</emoji>",
"DOGE": "<emoji document_id=5375192042420842380>💰</emoji>",
"TRX": "<emoji document_id=5375187081733616165>💰</emoji>",
"AVAX": "<emoji document_id=5375311275007947936>💰</emoji>",
"LTC": "<emoji document_id=5373035462032113888>💰</emoji>",
"BCH": "<emoji document_id=5375596920397903962>💰</emoji>",
"ATOM": "<emoji document_id=5375468745688889977>💰</emoji>",
"XLM": "<emoji document_id=5372823290647690288>💰</emoji>",
"SHIB": "<emoji document_id=5375231036428924778>💰</emoji>",
"UNI": "<emoji document_id=5372953110329180525>💰</emoji>",
"XMR": "<emoji document_id=5375507073977038661>💰</emoji>",
"LINK": "<emoji document_id=5375149651093633217>💰</emoji>",
"ETC": "<emoji document_id=5375543306321146693>💰</emoji>",
"SUI": "<emoji document_id=5391002164929772708>💰</emoji>",
"NEAR": "<emoji document_id=5391181990915487346>💰</emoji>",
"VET": "<emoji document_id=5391091302681033446>💰</emoji>",
"FIL": "<emoji document_id=5373117173784919811>💰</emoji>",
"XTZ": "<emoji document_id=5390985478981829698>💰</emoji>",
"ALGO": "<emoji document_id=5391337713544738420>💰</emoji>",
"THETA": "<emoji document_id=5391256014676833736>💰</emoji>",
"FTM": "<emoji document_id=5393179395521263785>💰</emoji>",
"XDAI": "<emoji document_id=5391325992578988886>💰</emoji>",
"RUNE": "<emoji document_id=5391347570494684983>💰</emoji>",
"DOT": "<emoji document_id=5375224568208177973>💰</emoji>",
"FTM": "<emoji document_id=5393179395521263785>💰</emoji>",
"XDAI": "<emoji document_id=5391325992578988886>💰</emoji>",
"RUNE": "<emoji document_id=5391347570494684983>💰</emoji>",
"DOT": "<emoji document_id=5375224568208177973>💰</emoji>",
}
_CRYPTO_LIST = {
"BTC": "Bitcoin",
"ETH": "Ethereum",
"XMR": "Monero",
"LTC": "Litecoin",
"XRP": "XRP",
"ADA": "Cardano",
"DOGE": "Dogecoin",
"SOL": "Solana",
"DOT": "Polkadot",
"USDT": "Tether",
"TON": "Toncoin",
"USDC": "USD Coin",
"TRX": "TRON",
"AVAX": "Avalanche",
"BCH": "Bitcoin Cash",
"ATOM": "Cosmos",
"XLM": "Stellar",
"SHIB": "Shiba Inu",
"UNI": "Uniswap",
"LINK": "Chainlink",
"ETC": "Ethereum Classic",
"SUI": "Sui",
"NEAR": "NEAR Protocol",
"VET": "VeChain",
"FIL": "Filecoin",
"XTZ": "Tezos",
"ALGO": "Algorand",
"THETA": "Theta Network",
"FTM": "Fantom",
"XDAI": "xDai",
_CRYPTO_NAMES = {
"BTC": "Bitcoin", "ETH": "Ethereum", "XMR": "Monero",
"LTC": "Litecoin", "XRP": "XRP", "ADA": "Cardano",
"DOGE": "Dogecoin", "SOL": "Solana", "DOT": "Polkadot",
"USDT": "Tether", "TON": "Toncoin", "USDC": "USD Coin",
"TRX": "TRON", "AVAX": "Avalanche", "BCH": "Bitcoin Cash",
"ATOM": "Cosmos", "XLM": "Stellar", "SHIB": "Shiba Inu",
"UNI": "Uniswap", "LINK": "Chainlink", "ETC": "Ethereum Classic",
"SUI": "Sui", "NEAR": "NEAR Protocol", "VET": "VeChain",
"FIL": "Filecoin", "XTZ": "Tezos", "ALGO": "Algorand",
"THETA": "Theta Network", "FTM": "Fantom", "XDAI": "xDai",
"RUNE": "THORChain",
}
def _fmt_num(v, d=3):
p = f"{v:,.{d}f}".replace(",", " ").split(".")
i = p[0]
d = p[1].rstrip("0") if len(p) > 1 else ""
return f"{i},{d}" if d else i
_CBR_URL = "https://www.cbr.ru/scripts/XML_daily.asp"
_CRYPTO_URL = "https://api.coinlore.net/api/tickers/?limit=100"
CACHE_TTL = 300 # seconds
def _fmt_num(value: float, decimals: int = 3) -> str:
if decimals == 0:
return f"{int(value):,}".replace(",", " ")
rounded = round(value, decimals)
int_part = int(rounded)
dec_part = str(rounded - int_part)[2:2 + decimals].rstrip("0")
int_str = f"{int_part:,}".replace(",", " ")
return f"{int_str},{dec_part}" if dec_part else int_str
def _parse_cbr_xml(xml_bytes: bytes) -> tuple[str | None, dict]:
"""Parse CBR XML without bs4/lxml — pure stdlib ElementTree."""
import xml.etree.ElementTree as ET
root = ET.fromstring(xml_bytes)
date_str = root.attrib.get("Date", "")
try:
date = datetime.strptime(date_str, "%d.%m.%Y").strftime("%d.%m.%Y")
except ValueError:
date = date_str
rates: dict[str, dict] = {}
for valute in root.findall("Valute"):
code = valute.findtext("CharCode", "").strip()
if not code or code == "XDR":
continue
try:
nominal = float(valute.findtext("Nominal", "1").replace(",", "."))
value = float(valute.findtext("Value", "0").replace(",", "."))
except ValueError:
continue
rates[code] = {
"name": valute.findtext("Name", code).strip(),
"nominal": nominal,
"rub": value / nominal,
}
return date, rates
@loader.tds
class FinanceMod(loader.Module):
strings = {
"name": "FinanceMod",
"valute_description": "<кол-во> <код> - курс валюты\n<кол-во> - список",
"valute_no_args": (
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
"<b>Актуально на</b> <i>{}</i>\n\n<blockquote expandable>{}</blockquote>"
),
"valute_specific": (
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
"<b>Актуально на</b> <i>{}</i>\n\n{}"
),
"valute_not_found": "🚫 Валюта {} не найдена",
"crypto_description": "<кол-во> <код> - курс крипты\n<кол-во> - список",
"crypto_no_args": "💎 <b>Курсы криптовалют</b>\n\n<blockquote expandable>{}</blockquote>",
"crypto_specific": "💎 <b>Курс криптовалюты</b>\n\n{}",
"crypto_not_found": "🚫 Криптовалюта {} не найдена",
"error": "🚫 Ошибка получения данных",
}
"""Курсы валют (ЦБ РФ) и криптовалют (CoinLore)"""
strings = {"name": "FinanceMod"}
def __init__(self):
self.config = loader.ModuleConfig(
@@ -152,149 +124,194 @@ class FinanceMod(loader.Module):
"crypto_currency",
"USD",
lambda: "Валюта для отображения крипты (USD, RUB, EUR)",
validator=loader.validators.Choice(["USD", "RUB", "EUR"])
validator=loader.validators.Choice(["USD", "RUB", "EUR"]),
)
)
# Simple in-process cache
self._cbr_cache: tuple[float, str, dict] | None = None # (ts, date, rates)
self._crypto_cache: tuple[float, list] | None = None # (ts, data)
async def _get_curr_data(self):
# ──────────────────────────── HTTP helpers ────────────────────────────
async def _fetch(self, url: str, *, as_json: bool = False):
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
resp.raise_for_status()
return await resp.json() if as_json else await resp.read()
# ──────────────────────────── CBR data ────────────────────────────────
async def _cbr_data(self) -> tuple[str | None, dict]:
now = time.monotonic()
if self._cbr_cache and now - self._cbr_cache[0] < CACHE_TTL:
return self._cbr_cache[1], self._cbr_cache[2]
try:
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp")
s = bs4.BeautifulSoup(r.content, 'xml')
d = datetime.strptime(s.ValCurs['Date'], "%d.%m.%Y").strftime("%d.%m.%Y")
return d, s.find_all('Valute')
except:
return None, None
raw = await self._fetch(_CBR_URL)
date, rates = _parse_cbr_xml(raw)
self._cbr_cache = (now, date, rates)
return date, rates
except Exception:
if self._cbr_cache:
return self._cbr_cache[1], self._cbr_cache[2]
return None, {}
async def _get_rates(self):
# ──────────────────────────── Crypto data ─────────────────────────────
async def _crypto_data(self) -> list:
now = time.monotonic()
if self._crypto_cache and now - self._crypto_cache[0] < CACHE_TTL:
return self._crypto_cache[1]
try:
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp")
s = bs4.BeautifulSoup(r.content, 'xml')
rt = {'USD': None, 'EUR': None}
for v in s.find_all('Valute'):
if v.CharCode.text in ['USD', 'EUR']:
n = float(v.Nominal.text.replace(',', '.'))
vl = float(v.Value.text.replace(',', '.'))
rt[v.CharCode.text] = vl / n
if rt['USD'] and rt['EUR']:
rt['EUR_USD'] = rt['USD'] / rt['EUR']
else:
rt['EUR_USD'] = None
return rt
except:
return None
js = await self._fetch(_CRYPTO_URL, as_json=True)
data = js.get("data", [])
self._crypto_cache = (now, data)
return data
except Exception:
return self._crypto_cache[1] if self._crypto_cache else []
async def _fmt_curr(self, v, a=1):
if v.CharCode.text == "XDR":
return None
c = v.CharCode.text
n = v.Name.text
v = float(v.Value.text.replace(',', '.')) / float(v.Nominal.text.replace(',', '.'))
t = v * a
ts = _fmt_num(t, 3)
return f"{_FLAGS.get(c, '🏳')} [{a}] {n} ({c}) - {ts} руб."
# ──────────────────────────── Formatters ──────────────────────────────
async def _get_crypto(self):
def _fmt_valute(self, code: str, info: dict, amount: float = 1.0) -> str:
total = info["rub"] * amount
flag = _FLAGS.get(code, "🏳")
return f"{flag} [{_fmt_num(amount, 0)}] {info['name']} ({code}) — {_fmt_num(total, 3)}"
def _fmt_crypto(self, coin: dict, rates: dict, amount: float = 1.0) -> str:
symbol = coin["symbol"].upper()
try:
return requests.get("https://api.coinlore.net/api/tickers/").json().get('data', [])
except:
return None
price_usd = float(coin["price_usd"])
except (KeyError, ValueError, TypeError):
return ""
async def _fmt_crypto(self, c, a=1):
r = await self._get_rates()
if not r:
return "🚫 Ошибка получения курсов валют"
cr = self.config["crypto_currency"]
try:
p = float(c['price_usd'])
except:
return "🚫 Ошибка данных криптовалюты"
if cr == "RUB":
if not r['USD']:
return "🚫 Курс USD не найден"
p *= r['USD']
elif cr == "EUR":
if not r['EUR_USD']:
return "🚫 Курс EUR/USD не рассчитан"
p *= r['EUR_USD']
t = p * a
ts = _fmt_num(t)
s = c['symbol'].upper()
e = _CRYPTO_EMOJIS.get(s, "💠")
n = _CRYPTO_LIST.get(s, c['name'])
cs = {"USD": "$", "RUB": "", "EUR": ""}.get(cr, "$")
return f"{e} [{a}] {n} ({s}) - {ts}{cs}"
currency = self.config["crypto_currency"]
if currency == "RUB":
usd_rate = rates.get("USD", {}).get("rub")
if not usd_rate:
return ""
price = price_usd * usd_rate
sign = ""
elif currency == "EUR":
usd_rate = rates.get("USD", {}).get("rub")
eur_rate = rates.get("EUR", {}).get("rub")
if not usd_rate or not eur_rate:
return ""
price = price_usd * (usd_rate / eur_rate)
sign = ""
else:
price = price_usd
sign = "$"
@loader.command()
async def valutecmd(self, m):
"""[count] [usd, eur, ...]"""
a = utils.get_args(m)
d, v = await self._get_curr_data()
if not d or not v:
return await utils.answer(m, self.strings["error"])
if len(a) == 0:
l = []
for x in v:
if (n := await self._fmt_curr(x)):
l.append(n)
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l)))
elif len(a) == 1:
total = price * amount
emoji = _CRYPTO_EMOJIS.get(symbol, "💠")
name = _CRYPTO_NAMES.get(symbol, coin.get("name", symbol))
return f"{emoji} [{_fmt_num(amount, 0)}] {name} ({symbol}) — {_fmt_num(total, 3)}{sign}"
# ──────────────────────────── Commands ────────────────────────────────
@loader.command(ru_doc="[кол-во] [код] — курс валюты по ЦБ РФ")
async def valutecmd(self, message):
"""[amount] [code] — exchange rates from CBR"""
args = utils.get_args(message)
date, rates = await self._cbr_data()
if not rates:
return await utils.answer(message, "🚫 Не удалось получить данные ЦБ РФ")
header = (
f"💵 <b>Курс валюты</b> · <a href='https://www.cbr.ru/'>ЦБ РФ</a>\n"
f"<b>Актуально на</b> <i>{date}</i>\n\n"
)
# .valute — список всех, кол-во = 1
if not args:
lines = [self._fmt_valute(c, i) for c, i in rates.items()]
return await utils.answer(
message,
header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
)
# Первый аргумент: число или код валюты?
amount = 1.0
code = None
arg0 = args[0].upper()
if len(args) >= 2:
# .valute 100 USD
try:
am = float(a[0])
l = []
for x in v:
if (n := await self._fmt_curr(x, am)):
l.append(n)
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l)))
except:
await utils.answer(m, "🚫 Некорректное число")
elif len(a) == 2:
amount = float(args[0].replace(",", "."))
except ValueError:
return await utils.answer(message, "🚫 Некорректное число")
code = args[1].upper()
else:
# .valute USD или .valute 100
try:
am = float(a[0])
c = a[1].upper()
for x in v:
if x.CharCode.text == c:
if (n := await self._fmt_curr(x, am)):
return await utils.answer(m, self.strings["valute_specific"].format(d, n))
await utils.answer(m, self.strings["valute_not_found"].format(c))
except:
await utils.answer(m, "🚫 Некорректное число")
amount = float(arg0.replace(",", "."))
# число без кода — список с умножением
except ValueError:
code = arg0
@loader.command()
async def cryptocmd(self, m):
"""[count] [ton, btc, ...]"""
a = utils.get_args(m)
c = await self._get_crypto()
if not c:
return await utils.answer(m, self.strings["error"])
try:
if len(a) == 0:
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST]
l = []
for x in f:
if (n := await self._fmt_crypto(x)):
l.append(n)
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l)))
elif len(a) == 1:
am = float(a[0])
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST]
l = []
for x in f:
if (n := await self._fmt_crypto(x, am)):
l.append(n)
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l)))
elif len(a) == 2:
am = float(a[0])
t = a[1].upper()
f = False
for x in c:
if x['symbol'].upper() == t:
if (n := await self._fmt_crypto(x, am)):
f = True
await utils.answer(m, self.strings["crypto_specific"].format(n))
break
if not f:
await utils.answer(m, self.strings["crypto_not_found"].format(t))
except ValueError:
await utils.answer(m, "🚫 Некорректное число")
except Exception as e:
await utils.answer(m, f"🚫 Ошибка: {str(e)}")
if code:
if code not in rates:
return await utils.answer(message, f"🚫 Валюта <b>{code}</b> не найдена")
line = self._fmt_valute(code, rates[code], amount)
return await utils.answer(message, header + line)
# список с кол-вом
lines = [self._fmt_valute(c, i, amount) for c, i in rates.items()]
await utils.answer(
message,
header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
)
@loader.command(ru_doc="[кол-во] [код] — курс крипты")
async def cryptocmd(self, message):
"""[amount] [symbol] — crypto rates from CoinLore"""
args = utils.get_args(message)
coins = await self._crypto_data()
_, rates = await self._cbr_data()
if not coins:
return await utils.answer(message, "🚫 Не удалось получить данные крипты")
header = f"💎 <b>Курсы криптовалют</b> · <i>{self.config['crypto_currency']}</i>\n\n"
amount = 1.0
symbol = None
if not args:
pass # список, amount=1
elif len(args) == 1:
try:
amount = float(args[0].replace(",", "."))
except ValueError:
symbol = args[0].upper()
else:
try:
amount = float(args[0].replace(",", "."))
except ValueError:
return await utils.answer(message, "🚫 Некорректное число")
symbol = args[1].upper()
if symbol:
coin = next((c for c in coins if c["symbol"].upper() == symbol), None)
if not coin:
return await utils.answer(message, f"🚫 Крипта <b>{symbol}</b> не найдена")
line = self._fmt_crypto(coin, rates, amount)
if not line:
return await utils.answer(message, "🚫 Ошибка форматирования")
return await utils.answer(message, header + line)
# список только известных монет
known = {c["symbol"].upper(): c for c in coins if c["symbol"].upper() in _CRYPTO_NAMES}
# сортируем по порядку _CRYPTO_NAMES
lines = []
for sym in _CRYPTO_NAMES:
if sym in known:
line = self._fmt_crypto(known[sym], rates, amount)
if line:
lines.append(line)
await utils.answer(
message,
header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
ChatCopy.py
Gemini.py
GiftFinder.py
MaillingChatGT99.py
NekoEditorMod.py

View File

@@ -0,0 +1,409 @@
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
#░░░█░░░█░░░░█░░█░█░█░█
#░░░███░███░░█░░███░███
# meta developer: @nullmod
# scope: hikka_min 2.0.0
__version__ = (1, 2, 2)
import io
import math
from PIL import Image, ImageDraw
from herokutl.tl.custom import Message
from herokutl.tl.functions.payments import GetUniqueStarGiftRequest
from herokutl.tl.functions.help import (
GetPeerProfileColorsRequest
)
from herokutl.tl.types import (
EmojiStatusCollectible,
StarGiftAttributeBackdrop,
)
from herokutl.tl.types.payments import (
UniqueStarGift,
)
from .. import loader, utils
def resize_image(image: Image.Image, max_size: int = 1280) -> Image.Image:
w, h = image.size
if max(w, h) <= max_size:
return image
else:
scale = max_size / max(w, h)
new_w = int(w * scale)
new_h = int(h * scale)
return image.resize((new_w, new_h), Image.LANCZOS)
# Source: https://gist.github.com/weihanglo/1e754ec47fdd683a42fdf6a272904535#file-draw_gradient_pillow-py
def get_gradient(size: tuple, color1: tuple, color2: tuple, gradient_type: str = "linear") -> Image.Image:
def interpolate(f_co, t_co, interval):
if interval <= 1:
yield list(t_co)
return
det_co = [(t - f) / (interval - 1) for f, t in zip(f_co, t_co)]
for i in range(interval):
yield [round(f + det * i) for f, det in zip(f_co, det_co)]
gradient = Image.new('RGB', size, color=(0, 0, 0))
draw = ImageDraw.Draw(gradient)
if gradient_type == "linear":
bottom_color, top_color = color1, color2
for y, color in enumerate(interpolate(top_color, bottom_color, max(1, size[1]))):
draw.line([(0, y), (size[0], y)], fill=tuple(color), width=1)
elif gradient_type == "radial":
center_color, edge_color = color1, color2
max_radius = math.hypot(size[0], size[1]) / 2.0
interval = max(1, int(math.ceil(max_radius)) + 1)
colors = list(interpolate(center_color, edge_color, interval))
cx = size[0] / 2
cy = size[1] / 2
for r_index, color in enumerate(colors):
r = interval - 1 - r_index
if r < 0:
continue
bbox = [
int(round(cx - r)),
int(round(cy - r)),
int(round(cx + r)),
int(round(cy + r))
]
draw.ellipse(bbox, fill=tuple(color))
return gradient
def set_gradient(im: io.BytesIO, gradient: Image.Image) -> io.BytesIO:
img = resize_image(Image.open(im).convert('RGBA'))
max_size = max(img.width, img.height)
gradient = gradient.resize((max_size, max_size), Image.LANCZOS).convert('RGBA')
left = (max_size - img.width) // 2
top = (max_size - img.height) // 2
gradient.paste(img, (left, top), img)
buffer = io.BytesIO()
gradient.save(buffer, format='PNG')
buffer.seek(0)
return buffer
def crop_by_bbox(img: Image.Image, bbox: tuple):
img_w, img_h = img.size
x, y, w, h = bbox
left = int(round(x * img_w))
top = int(round(y * img_h))
right = int(round((x + w) * img_w))
bottom = int(round((y + h) * img_h))
return img.crop((left, top, right, bottom))
def hex_to_rgb(value: int):
return ((value >> 16) & 255, (value >> 8) & 255, value & 255)
def hexes_to_rgbs(value: list):
if len(value) > 1:
res = list()
for i in value:
res.append(hex_to_rgb(i))
return tuple(res)
else:
res = hex_to_rgb(value[0])
return (res, res)
SHAPES = {
# TODO: фигуры для создания масок на авы
}
BBOX_TGA_TGD = (
2894 / 8268,
1260 / 8268,
2504 / 8268,
2504 / 8268,
)
BBOX_IOS = (
2590 / 8268,
629 / 8268,
3120 / 8268,
3120 / 8268,
)
@loader.tds
class Gradientor(loader.Module):
strings = {
"name": "Gradientor",
"_cls_doc": "A module to create your profile picture with a background from your profile",
"gradient_creating": "<tg-emoji emoji-id=5886667040432853038>🔁</tg-emoji> Creating gradient...",
"gradient_created": "<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Gradient created!",
"nft_done": (
"<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Gradient created from "
"<a href=\"https://t.me/nft/{}\">gift</a> background!"
),
"noargs": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> No arguments provided!",
"nft_error": (
"<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> Failed to get gift info."
"Make sure the link/slug is correct"
),
}
strings_ru = {
"_cls_doc": "Модуль для создания вашей аватарки на фоне из вашего профиля",
"gradient_creating": "<tg-emoji emoji-id=5886667040432853038>🔁</tg-emoji> Создание градиента...",
"gradient_created": "<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Градиент создан!",
"nft_done": (
"<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Градиент создан из фона "
"<a href=\"https://t.me/nft/{}\">подарка</a>!"
),
"noargs": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> Не указаны аргументы!",
"nft_error": (
"<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> Не удалось получить информацию о подарке."
"Убедитесь, что ссылка/slug правильные"
),
}
async def client_ready(self):
self.colors = self.get("PROFILE_COLORS", None)
if not self.colors or not self.colors.get("light", None):
raw_colors = (await self.client(GetPeerProfileColorsRequest(0))).colors
self.colors = {
"dark": {
str(col.color_id): hexes_to_rgbs(col.dark_colors.bg_colors) for col
in raw_colors
},
"light": {
str(col.color_id): hexes_to_rgbs(col.colors.bg_colors) for col
in raw_colors
},
}
self.set("PROFILE_COLORS", self.colors)
async def make_gradient(
self,
photo_source: Message,
bbox: tuple,
color1: int,
color2: int,
force_linear: bool = False,
add_glow: bool = False,
_full: bool = False,
background_only: bool = True,
):
gradient = get_gradient((1280, 1280), color1, color2, "linear" if force_linear else "radial")
if add_glow:
pass # TODO
if not _full:
gradient = crop_by_bbox(gradient, bbox)
if not background_only and not _full:
p_b = await photo_source.download_media(bytes)
p_b_io = io.BytesIO(p_b)
p_b_io.seek(0)
result = set_gradient(p_b_io, gradient)
else:
result = io.BytesIO()
gradient.save(result, format='PNG')
result.seek(0)
result.name = "grad @nullmod.png"
return result
async def _get_photo_source(self, m: Message, r: Message):
photo_source = (
m
if (not r or not (r.photo or r.document and "image/" in getattr(r.document, "mime_type", "")))
else r
)
if not (photo_source.photo or photo_source.document and "image/" in getattr(photo_source.document, "mime_type", "")):
return None
return photo_source
@loader.command(
ru_doc="[фотография/reply] - создать аватарку с градиентом из цвета профиля\n"
"--update-cache - обновить кеш профиля, если вы только что сменили фон профиля\n"
"--linear - использовать линейный градиент\n"
"--light - использовать светлую тему\n"
"--ios - создать аватарку для iOS-клиентов"
)
async def makepp(self, message: Message):
"""[photo/reply] - create a profile picture with a gradient from profile color
--update-cache - update profile cache if you just changed profile background
--linear - use linear gradient
--light - use light theme
--ios - create a profile picture for iOS clients"""
reply: Message = await message.get_reply_message()
args = utils.get_args(message)
if "--ios" in args:
bbox = BBOX_IOS
_type = "ios"
args.remove("--ios")
else:
bbox = BBOX_TGA_TGD
_type = "android"
if "--update-cache" in args:
upd_cache = True
args.remove("--update-cache")
else:
upd_cache = False
if "--linear" in args:
force_linear = True
args.remove("--linear")
else:
force_linear = False
if "--light" in args:
theme = "light"
args.remove("--light")
else:
theme = "dark"
if "--full" in args:
_full = True
args.remove("--full")
else:
_full = False
user = None
background_only = False
add_glow = False
if args:
user = await self.client.get_entity(int(args[0]) if args[0].isdigit() else args[0])
if not (photo_source := await self._get_photo_source(message, reply)):
background_only = True
if not user:
if upd_cache:
user = self.client.hikka_me = await self.client.get_me()
elif reply:
user = reply.sender
else:
user = self.client.hikka_me
if not user.premium:
color1, color2 = (28, 28, 28), (28, 28, 28)
elif user.emoji_status and isinstance(user.emoji_status, EmojiStatusCollectible):
color1, color2 = (
user.emoji_status.edge_color, user.emoji_status.center_color
)
color1 = hex_to_rgb(color1)
color2 = hex_to_rgb(color2)
elif user.profile_color:
color_variant = user.profile_color.color
color1, color2 = self.colors.get(theme).get(
str(color_variant),
((28, 28, 28), (28, 28, 28))
)
if _type == "ios":
add_glow = True
force_linear = True
else:
color1, color2 = (28, 28, 28), (28, 28, 28)
await utils.answer(message, self.strings["gradient_creating"])
result = await self.make_gradient(
photo_source,
bbox,
color1,
color2,
force_linear,
add_glow,
_full,
background_only
)
await utils.answer(message, self.strings["gradient_created"], file=result, force_document=True)
@loader.command(ru_doc="[gift link/slug] - создать аватарку с градиентом из фона nft-подарка")
async def nftbg(self, message: Message):
"""[gift link/slug] - create a profile picture with a gradient from nft gift background"""
reply: Message = await message.get_reply_message()
args = utils.get_args(message)
if "--ios" in args:
bbox = BBOX_IOS
args.remove("--ios")
else:
bbox = BBOX_TGA_TGD
if "--linear" in args:
force_linear = True
args.remove("--linear")
else:
force_linear = False
if "--full" in args:
_full = True
args.remove("--full")
else:
_full = False
if not args:
return await utils.answer(message, self.strings["noargs"])
args = args[0].split("/")[-1]
background_only = False
try:
gift: UniqueStarGift = await self.client(GetUniqueStarGiftRequest(args))
except Exception as e:
return await utils.answer(message, self.strings["nft_error"] + "\n" + str(e))
backdrop = next(attr for attr in gift.gift.attributes if isinstance(attr, StarGiftAttributeBackdrop))
color1, color2 = (
backdrop.edge_color, backdrop.center_color
)
color1 = hex_to_rgb(color1)
color2 = hex_to_rgb(color2)
if not (photo_source := await self._get_photo_source(message, reply)):
background_only = True
await utils.answer(message, self.strings["gradient_creating"])
result = await self.make_gradient(
photo_source,
bbox,
color1,
color2,
force_linear,
_full=_full,
background_only=background_only
)
await utils.answer(message, self.strings["nft_done"].format(args), file=result, force_document=True)

View File

@@ -1,3 +1,4 @@
Chess
HaremManager
SchedulePlus
SchedulePlus
Gradientor

View File

@@ -1,59 +0,0 @@
name: Generate Index Page
on:
push:
branches:
- main
permissions:
contents: write
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Generate index.html
run: |
cat <<EOF > _site/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>H:Mods</title>
<link type=text/css href="https://github.com/C0dwiz/H.Modules/raw/assets/style.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<h1>H:Mods modules</h1>
<ul class="module-list">
$(for file in *.py; do echo " <li class="module-item"><a href=\"$file\" class="module-link">$file</a></li>"; done)
</ul>
</div>
</body>
</html>
EOF
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: .

View File

@@ -1,7 +0,0 @@
full.py
autocleaner.py
silent.py
# Ruff Format
.ruff_cache/
.idea

View File

@@ -1,136 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: ASCIIArt
# Description: Converting images to ASCII art
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: ASCIIArt
# scope: ASCIIArt 0.0.1
# requires: pillow
# ---------------------------------------------------------------------------------
import logging
import os
import tempfile
from PIL import Image
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class ASCIIArtMod(loader.Module):
"""Converting images to ASCII art"""
strings = {
"name": "ASCIIArt",
"no_media_reply": "<b>Please reply to the image!</b>",
"loading": "<emoji document_id=5116240346656801621>❓</emoji> <b>Converting an image to ASCII...</b>",
"error": "<emoji document_id=5121063440311386962>👎</emoji> <b>Error when converting an image.</b>",
"done": "<emoji document_id=5123163417326126159>✅</emoji> <b>Here is your ASCII art:</b>",
}
strings_ru = {
"no_media_reply": "<b>Пожалуйста, ответьте на изображение!</b>",
"loading": "<emoji document_id=5116240346656801621>❓</emoji> <b>Конвертирую изображение в ASCII...</b>",
"error": "<emoji document_id=5121063440311386962>👎</emoji> <b>Ошибка при конвертации изображения.</b>",
"done": "<emoji document_id=5123163417326126159>✅</emoji> <b>Вот ваш ASCII-арт:</b>",
}
@loader.command(
ru_doc="<реплай на изображение> сделать ascii art",
en_doc="<replay on image> make ascii art",
)
async def cascii(self, message):
reply = await message.get_reply_message()
if not self._is_image(reply):
await utils.answer(message, self.strings("no_media_reply"))
return
await utils.answer(message, self.strings("loading"))
ascii_art = await self._generate_ascii_art(reply)
if ascii_art:
await self._send_ascii_file(message, ascii_art)
await message.delete()
else:
await utils.answer(message, self.strings("error"))
def _is_image(self, reply):
"""Проверка, является ли ответ изображением"""
return reply and (
reply.photo
or (reply.document and reply.file.mime_type.startswith("image/"))
)
async def _generate_ascii_art(self, reply):
"""Генерирует ASCII-арт из изображения"""
try:
image_path = await reply.download_media(tempfile.gettempdir())
if not image_path:
return None
with Image.open(image_path) as img:
img = img.convert("L")
img = img.resize(self._get_new_dimensions(img), Image.NEAREST)
chars = "@#S%?*+;:,. "
pixels = img.getdata()
ascii_str = "".join(chars[pixel // 25] for pixel in pixels)
return "\n".join(
ascii_str[i : i + img.width]
for i in range(0, len(ascii_str), img.width)
)
except Exception as e:
logger.error(f"Error generating ASCII art: {e}")
return None
finally:
if image_path and os.path.exists(image_path):
os.remove(image_path)
def _get_new_dimensions(self, img):
"""Получаем новые размеры для изображения"""
new_width = 100
aspect_ratio = img.height / img.width
new_height = int(aspect_ratio * new_width * 0.55)
return new_width, new_height
async def _send_ascii_file(self, message, ascii_art):
"""Сохраняет ASCII-арт во временный файл и отправляет его"""
try:
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", suffix=".txt", delete=False
) as tmp_file:
tmp_file_path = tmp_file.name
tmp_file.write(ascii_art)
await message.client.send_file(
message.chat_id,
tmp_file_path,
caption=self.strings("done"),
force_document=True,
reply_to=getattr(message, "reply_to_msg_id", None),
)
finally:
if tmp_file_path and os.path.exists(tmp_file_path):
os.remove(tmp_file_path)

View File

@@ -1,114 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AccountData
# Description: Find out the approximate date of registration of the telegram account
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api AccountData
# scope: Api AccountData 0.0.1
# ---------------------------------------------------------------------------------
import logging
from datetime import datetime
import aiohttp
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class AccountData(loader.Module):
"""Find out the approximate date of registration of the telegram account"""
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"api_token",
"7518491974:1ea2284eec9dc40a9838cfbcb48a2b36",
"API token for datereg.pro",
validator=loader.validators.String(),
)
)
strings = {
"name": "AccountData",
"_cls_doc": "Find out the approximate date of registration of the telegram account",
"date_text": "<emoji document_id=5983150113483134607>⏰️</emoji> Date of registration of this account: {data} (Accuracy: {accuracy}%)",
"date_text_ps": "<emoji document_id=6028435952299413210></emoji> <i>Tip: To increase accuracy, the person whose registration date is being checked can write any message to</i> @mewpl2.\n\nDon't worry, this account is not run by a person, but by a userbot just like yours, which will check the registration date using Telegram's built-in tool.",
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> You did not reply to the user's message",
}
strings_ru = {
"date_text": "<emoji document_id=5983150113483134607>⏰️</emoji> Дата регистрации этого аккаунта: {data} (Точность: {accuracy}%)",
"_cls_doc": "Узнайте примерную дату регистрации Telegram-аккаунта",
"date_text_ps": "<emoji document_id=6028435952299413210></emoji> <i>Совет: Для повышения точности, человек, дата регистрации которого проверяется, может написать любое сообщение</i> @mewpl2.\n\nНе бойтесь, на этом аккаунте сидит не человек, а такой же юзербот, как и у вас, который проверит дату регистрации при помощи встроенного инструмента Telegram.",
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> Вы не ответили на сообщение пользователя",
}
async def get_creation_date(self, user_id: int) -> str:
api_token = self.config.get("api_token", "")
if not api_token:
return {"error": "API token not configured"}
url = "https://api.datereg.pro/api/v1/users/getCreationDateFast"
params = {"token": api_token, "user_id": user_id}
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response:
if response.status == 200:
json_response = await response.json()
if json_response["success"]:
return {
"creation_date": json_response["creation_date"],
"accuracy_percent": json_response["accuracy_percent"],
} # type: ignore
else:
return {"error": json_response["error"]["message"]} # type: ignore
else:
return {"error": f"HTTP {response.status}"} # type: ignore
@loader.command(
ru_doc="Узнать примерную дату регистрации Telergam-аккаунта",
en_doc="Find out the approximate date of registration of the telegram account",
)
async def accdata(self, message):
if reply := await message.get_reply_message():
result = await self.get_creation_date(user_id=reply.sender.id)
if "error" in result or not result.get("creation_date"):
error_msg = result.get("error", "Unknown error occurred")
await utils.answer(message, f"Ошибка: {error_msg}")
return
try:
month, year = map(int, result['creation_date'].split('.'))
date_object = datetime(year, month, 1)
formatted = date_object.strftime('%B %Y')
await utils.answer(
message,
f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}",
)
except (ValueError, KeyError) as e:
await utils.answer(message, f"Ошибка обработки данных: {str(e)}")
else:
await utils.answer(message, self.strings("no_reply"))

View File

@@ -1,84 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AnimeQuotes
# Description: A module for sending random quotes from anime
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: AnimeQuotes
# scope: AnimeQuotes 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import logging
import aiohttp
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class AnimeQuotesMod(loader.Module):
"""A module for sending random quotes from anime"""
strings = {
"name": "AnimeQuotes",
"quote_template": (
'<b>Quote:</b> "{quote}"\n\n'
"<b>Character:</b> {character}\n"
"<b>Anime:</b> {anime}"
),
"error": "<b>Couldn't get a quote. Try again later!</b>",
}
strings_ru = {
"quote_template": (
'<b>Цитата:</b> "{quote}"\n\n'
"<b>Персонаж:</b> {character}\n"
"<b>Аниме:</b> {anime}"
),
"error": "<b>Не удалось получить цитату. Попробуйте позже!</b>",
}
@loader.command(
ru_doc="Получить случайную цитату из аниме",
en_doc="Get a random quote from the anime",
)
async def quote(self, message):
url = "https://api.animechan.io/v1/quotes/random"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
data = await response.json()
quote_content = data["data"]["content"]
character_name = data["data"]["character"]["name"]
anime_name = data["data"]["anime"]["name"]
quote = self.strings["quote_template"].format(
quote=quote_content, character=character_name, anime=anime_name
)
await utils.answer(message, quote)
except aiohttp.ClientError:
await utils.answer(message, self.strings["error"])

View File

@@ -1,76 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Article
# Description: Displays your article Criminal Code of the Russian Federation
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Article
# scope: Article 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import json
import logging
import random
from typing import Dict
import requests
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class ArticleMod(loader.Module):
"""Displays your article Criminal Code of the Russian Federation"""
strings = {
"name": "Article",
"article": "<emoji document_id=5226512880362332956>📖</emoji> <b>Your article of the Criminal Code of the Russian Federation</b>:\n\n<blockquote>Number {}\n\n{}</blockquote>",
}
strings_ru = {
"article": "<emoji document_id=5226512880362332956>📖</emoji> <b>Твоя статья УК РФ</b>:\n\n<blockquote>Номер {}\n\n{}</blockquote>",
}
@loader.command(
ru_doc="Отображается ваша статья Уголовного кодекса Российской Федерации",
en_doc="Displays your article Criminal Code of the Russian Federation",
)
async def arccmd(self, message):
if values := self._load_values():
random_key = random.choice(list(values.keys()))
random_value = values[random_key]
await utils.answer(
message, self.strings("article").format(random_key, random_value)
)
def _load_values(self) -> Dict[str, str]:
url = "https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/zakon.json"
try:
response = requests.get(url)
if response.ok:
data = json.loads(response.text)
return data
except (requests.RequestException, json.JSONDecodeError):
pass
return {}

View File

@@ -1,205 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AutofarmCookies
# Description: Autofarm in the bot @cookies_game_bot
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: AutofarmCookies
# scope: AutofarmCookies 0.0.1
# ---------------------------------------------------------------------------------
import logging
import random
from datetime import timedelta
from telethon import functions
from telethon.tl.custom import Message
from .. import loader, utils
__version__ = (1, 0, 0)
logger = logging.getLogger(__name__)
@loader.tds
class AutofarmCookiesMod(loader.Module):
"""Autofarm in the bot @cookies_game_bot"""
strings = {
"name": "AutofarmCookies",
"farmon": (
"<i>The deferred task has been created, autofarming has been started, everything will start in 10 minutes"
" seconds...</i>"
),
"farmon_already": "<i>It has already been launched :)</i>",
"farmoff": "<i>The autopharm is stopped\nSelected:</i> <b>%coins% Cookies</b>",
"farm": "<i>I typed:</i> <b>%coins% Cookies</b>",
}
strings_ru = {
"farmon": (
"<i>Отложенная задача создана, автофарминг запущен, всё начнётся через 10"
" секунд...</i>"
),
"farmon_already": "<i>Уже запущено :)</i>",
"farmoff": "<i>Автофарм остановлен.\nНвброно:</i> <b>%coins% Cookies</b>",
"farm": "<i>Я набрал:</i> <b>%coins% Cookies</b>",
}
def __init__(self):
self.name = self.strings["name"]
async def client_ready(self, client, db):
self.client = client
self.db = db
self.myid = (await client.get_me()).id
self.cookies = "@cookies_game_bot"
@loader.command(
ru_doc="Запустить автофарминг",
en_doc="Launch auto-farming",
)
async def cookon(self, message):
status = self.db.get(self.name, "status", False)
if status:
return await message.edit(self.strings["farmon_already"])
self.db.set(self.name, "status", True)
await self.client.send_message(
self.cookies, "/cookie", schedule=timedelta(seconds=10)
)
await message.edit(self.strings["farmon"])
@loader.command(
ru_doc="Остановить автофарминг",
en_doc="Stop auto-farming",
)
async def cookoff(self, message):
self.db.set(self.name, "status", False)
coins = self.db.get(self.name, "coins", 0)
if coins:
self.db.set(self.name, "coins", 0)
await message.edit(self.strings["farmoff"].replace("%coins%", str(coins)))
@loader.command(
ru_doc="Вывод кол-ва коинов, добытых этим модулем",
en_doc="Output of the number of coins mined by this module",
)
async def cookies(self, message):
coins = self.db.get(self.name, "coins", 0)
await message.edit(self.strings["farm"].replace("%coins%", str(coins)))
async def watcher(self, event):
if not isinstance(event, Message): # noqa: F821
return
chat = utils.get_chat_id(event)
if chat != self.cookies:
return
status = self.db.get(self.name, "status", False)
if not status:
return
if event.raw_text == "/cookie":
return await self.client.send_message(
self.cookies, "/cookie", schedule=timedelta(hours=2)
)
if event.sender_id != self.cookies:
return
if "🙅‍♂️!" in event.raw_text:
args = [int(x) for x in event.raw_text.split() if x.isnumeric()]
randelta = random.randint(20, 60)
if len(args) == 4:
delta = timedelta(
hours=args[1], minutes=args[2], seconds=args[3] + randelta
)
elif len(args) == 3:
delta = timedelta(minutes=args[1], seconds=args[2] + randelta)
elif len(args) == 2:
delta = timedelta(seconds=args[1] + randelta)
else:
return
sch = (
await self.client(
functions.messages.GetScheduledHistoryRequest(self.cookies, 1488)
)
).messages
await self.client(
functions.messages.DeleteScheduledMessagesRequest(
self.cookies, id=[x.id for x in sch]
)
)
return await self.client.send_message(
self.cookies, "/cookie", schedule=delta
)
if "" in event.raw_text:
args = event.raw_text.split()
for x in args:
if x[0] == "+":
return self.db.set(
self.name,
"coins",
self.db.get(self.name, "coins", 0) + int(x[1:]),
)
async def message_q(
self,
text: str,
user_id: int,
mark_read: bool = False,
delete: bool = False,
):
async with self.client.conversation(user_id) as conv:
msg = await conv.send_message(text)
response = await conv.get_response()
if mark_read:
await conv.mark_read()
if delete:
await msg.delete()
await response.delete()
return response
@loader.command(
ru_doc="Показывает ваш мешок",
en_doc="Shows your bag",
)
async def me(self, message):
bot = "@cookies_game_bot"
bags = await self.message_q(
"/me",
bot,
delete=True,
)
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, bags.text)
@loader.command(
ru_doc="Помощь по модулю AutofarmCookies",
en_doc="Help with the AutofarmCookies module",
)
async def ckies(self, message):
chelp = """
🍀| <b>Помощь по командам:</b>
.cookon - Включает авто-фарм.
.cookoff - Выключает авто-фарм.
.me - Показывает ваш мешок"""
await utils.answer(message, chelp)

View File

@@ -1,252 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: BirthdayTime
# Description: Counting down to your birthday
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: BirthdayTime
# scope: Api BirthdayTime 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import calendar
import logging
import random
from datetime import datetime
from telethon.errors.rpcerrorlist import UserPrivacyRestrictedError
from telethon.tl.functions.account import UpdateProfileRequest
from telethon.tl.functions.users import GetFullUserRequest
from .. import loader, utils
logger = logging.getLogger(__name__)
D_MSG = [
"Ждешь его?",
"Осталось немного)",
"Дни пролетят, даже не заметишь",
"Уже знаешь что хочешь получить в подарок?)",
"Сколько исполняется?",
"Жду не дождусь уже",
]
@loader.tds
class DaysToMyBirthday(loader.Module):
"""Counting down to your birthday"""
strings = {
"name": "BirthdayTime",
"date_error": "<emoji document_id=5422840512681877946>❗️</emoji> <b>Your birthdate is not specified in the config, please correct this :)</b>",
"msg": (
"<emoji document_id=5377476217698001788>🎉</emoji> <b>"
"There are {} days, {} hours, {} minutes, and {} seconds left until your birthday. \n<emoji document_id=5377442914521588226>"
"💙</emoji> {}</b>"
),
"conf": "<i>Open config...</i>",
"name_changed": "<b>Name updated!</b>",
"name_not_changed": "<b>Name was not updated.</b>",
"name_privacy_error": "<b>Unable to change name due to privacy settings.</b>",
"error": "<b>An error occurred. Please check the logs.</b>",
}
strings_ru = {
"date_error": "<emoji document_id=5422840512681877946>❗️</emoji> <b>В конфиге не указан день вашего рождения, пожалуйста, исправь это :)</b>",
"msg": (
"<emoji document_id=5377476217698001788>🎉</emoji> <b>"
"До вашего дня рождения осталось {} дней, {} часов, {} "
"минут, {} секунд. \n<emoji document_id=5377442914521588226>"
"💙</emoji> {}</b>"
),
"conf": "<i>Открываю конфиг...</i>",
"btname_yes": (
"<b><emoji document_id=6327560044845991305>😶</emoji> Хорошо, теперь я "
"буду изменять ваше имя в зависимости от количества дней до дня рождения</b>"
),
"btname_no": "<emoji document_id=6325696222313055607>😶</emoji>Хорошо, я больше не буду изменять ваше имя",
"name_changed": "<b>Имя обновлено!</b>",
"name_not_changed": "<b>Имя не было обновлено.</b>",
"name_privacy_error": "<b>Не удалось изменить имя из-за настроек приватности.</b>",
"error": "<b>Произошла ошибка. Пожалуйста, проверьте логи.</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"birthday_date",
None,
lambda: "Дата вашего рождения. Указывать только день",
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"birthday_month",
None,
"Месяц вашего рождения",
validator=loader.validators.Choice(
[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]
),
),
)
self._task = None
async def client_ready(self):
if self._task:
self._task.cancel()
self._task = asyncio.create_task(self.checker())
async def checker(self):
while True:
if not self.db.get(__name__, "change_name", False):
await asyncio.sleep(60)
continue
try:
now = datetime.now()
day = self.config["birthday_date"]
monthy = self.config["birthday_month"]
month = list(calendar.month_name).index(monthy)
birthday = datetime(now.year, month, day)
if now.month > month or (now.month == month and now.day > day):
birthday = datetime(now.year + 1, month, day)
time_to_birthday = abs(birthday - now)
days = time_to_birthday.days
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
if not user or not user.users:
await asyncio.sleep(60)
continue
name = user.users[0].last_name or ""
ln = f"{self.db.get(__name__, 'last_name', '')}{days} d."
if name == ln:
await asyncio.sleep(60)
continue
else:
await self.client(UpdateProfileRequest(last_name=ln))
self.db.set(__name__, "last_name", name)
except UserPrivacyRestrictedError:
self.db.set(__name__, "change_name", False)
logger.error("Error: Can't change name due to privacy settings.")
except Exception as e:
logger.error(f"Error in checker: {e}")
finally:
await asyncio.sleep(60)
@loader.command(
ru_doc="Включить таймер дней в ник (нестабильно)",
en_doc="Enable timer of days in nickname (unstable)",
)
async def btnameon(self, message):
try:
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
name = user.users[0].last_name or ""
except Exception as e:
logger.error(f"Error getting user info: {e}")
await utils.answer(message, self.strings("error"))
return
self.db.set(__name__, "last_name", name)
self.db.set(__name__, "change_name", True)
await utils.answer(message, self.strings("btname_yes"))
@loader.command(
ru_doc="Выключить таймер дней в ник",
en_doc="Disable timer of days in nickname",
)
async def btnameoff(self, message):
change_name = self.db.get(__name__, "change_name", False)
if not change_name:
await utils.answer(message, self.strings("btname_no"))
return
self.db.set(__name__, "change_name", False)
await utils.answer(message, self.strings("btname_no"))
try:
await self.client(
UpdateProfileRequest(last_name=self.db.get(__name__, "last_name"))
)
await utils.answer(message, self.strings("name_not_changed"))
except UserPrivacyRestrictedError:
await utils.answer(message, self.strings("name_privacy_error"))
except Exception as e:
logger.error(f"Error removing name: {e}")
await utils.answer(message, self.strings("error"))
@loader.command(
ru_doc="Вывести таймер",
en_doc="Display the timer",
)
async def bt(self, message):
if (
self.config["birthday_date"] is None
or self.config["birthday_month"] is None
):
await utils.answer(message, self.strings("date_error"))
msg = await self.client.send_message(message.chat_id, self.strings("conf"))
await self.allmodules.commands["config"](
await utils.answer(msg, f"{self.get_prefix()}config BirthdayTime")
)
return
try:
now = datetime.now()
day = self.config["birthday_date"]
monthy = self.config["birthday_month"]
month = list(calendar.month_name).index(monthy)
birthday = datetime(now.year, month, day)
if now.month > month or (now.month == month and now.day > day):
birthday = datetime(now.year + 1, month, day)
time_to_birthday = abs(birthday - now)
await utils.answer(
message,
self.strings("msg").format(
time_to_birthday.days,
(time_to_birthday.seconds // 3600),
(time_to_birthday.seconds // 60 % 60),
(time_to_birthday.seconds % 60),
random.choice(D_MSG),
),
)
except Exception as e:
logger.error(f"Error in bt command: {e}")
await utils.answer(message, self.strings("error"))

View File

@@ -1 +0,0 @@
mods.archquise.ru

View File

@@ -1,54 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: CheckSpamBan
# Description: Check spam ban for your account.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: CheckSpamBan
# scope: CheckSpamBan 0.0.1
# ---------------------------------------------------------------------------------
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class SpamBanCheckMod(loader.Module):
"""Checks spam ban for your account."""
strings = {
"name": "CheckSpamBan",
}
@loader.command(
ru_doc="Проверяет вашу учетную запись на спам-бан с помощью бота @SpamBot",
en_doc="Checks your account for spam ban via @SpamBot bot",
)
async def spambot(self, message):
async with self.client.conversation(178220800) as conv:
user_message = await conv.send_message("/start")
await user_message.delete()
spam_message = await conv.get_response()
await utils.answer(message, spam_message.text)
await spam_message.delete()

View File

@@ -1,110 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: CryptoCurrency
# Description: Module for displaying current cryptocurrency exchange rates.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api CryptoCurrency
# scope: Api CryptoCurrency 0.0.1
# ---------------------------------------------------------------------------------
import logging
import aiohttp
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class CryptoCurrencyMod(loader.Module):
"""Module for displaying current cryptocurrency exchange rates."""
strings = {
"name": "CryptoCurrency",
"query_missing": "Please specify a cryptocurrency ticker or name.",
"coin_not_found": "Cryptocurrency '{query}' not found.",
}
strings_ru = {
"query_missing": "Пожалуйста, укажите тикер или название криптовалюты.",
"coin_not_found": "Криптовалюта '{query}' не найдена.",
}
async def fetch_json(self, url):
"""Fetch JSON data from a given URL."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.json()
async def get_exchange_rates(self):
"""Get exchange rates for RUB and EUR based on USD."""
data = await self.fetch_json("https://open.er-api.com/v6/latest/USD")
return data["rates"]["RUB"], data["rates"]["EUR"]
async def find_coin(self, query):
"""Find a cryptocurrency by its name or symbol."""
data = await self.fetch_json(
"https://api.coinlore.net/api/tickers/?start=0&limit=100"
)
return next(
(
item
for item in data["data"]
if query.lower() in item["name"].lower()
or query.lower() in item["symbol"].lower()
),
None,
)
@loader.command(
ru_doc="Отображает текущий курс криптовалюты в рублях, долларах США и евро",
en_doc="Displays the current cryptocurrency rate in RUB, USD, and EUR",
)
async def crypto(self, message):
query = utils.get_args_raw(message)
if not query:
return await utils.answer(message, self.strings("query_missing"))
coin = await self.find_coin(query)
if not coin:
return await utils.answer(
message, self.strings("coin_not_found").format(query=query)
)
price_usd = float(coin["price_usd"])
usd_rub_rate, usd_eur_rate = await self.get_exchange_rates()
price_rub = price_usd * usd_rub_rate
price_eur = price_usd * usd_eur_rate
response = self.format_response(coin, price_usd, price_rub, price_eur)
await utils.answer(message, response)
def format_response(self, coin, price_usd, price_rub, price_eur):
"""Format the response message with cryptocurrency information."""
return (
f"💰 {coin['name']} ({coin['symbol']})\n"
f"USD: ${price_usd:.2f}\n"
f"RUB: ₽{price_rub:.2f}\n"
f"EUR: €{price_eur:.2f}\n"
)

View File

@@ -1,360 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: EmojiStickerBlocker
# Description: Block emojis, stickers and sticker packs
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: EmojiStickerBlocker
# scope: EmojiStickerBlocker0.0.1
# ---------------------------------------------------------------------------------
import logging
import re
from typing import Optional, Set
from telethon.errors import FloodWaitError, MessageDeleteForbiddenError
from telethon.tl.types import Message, MessageMediaDocument
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class EmojiStickerBlocker(loader.Module):
"""Block emojis, stickers and sticker packs with enhanced functionality"""
strings = {
"name": "EmojiStickerBlocker",
"no_permission": "<emoji document_id=5854929766146118183>❌</emoji> Need delete messages permission",
"pack_blocked": "<emoji document_id=5854762571659218443>✅</emoji> Pack blocked",
"pack_not_found": "<emoji document_id=5854929766146118183>❌</emoji> Pack not found",
"sticker_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Sticker blocked",
"emoji_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Emoji blocked",
"pack_unblocked": "<emoji document_id=5854762571659218443>✅</emoji> Pack unblocked",
"item_unblocked": "<emoji document_id=5854929766146118183>❌</emoji> Item unblocked",
"not_found": "<emoji document_id=5854929766146118183>❌</emoji> Not in blocklist",
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Reply to a sticker or emoji",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Specify pack link or name",
"list_packs": "📦 Blocked packs: {}",
"list_stickers": "🖼 Blocked stickers: {}",
"list_emojis": "😀 Blocked emojis: {}",
"all_cleared": "✅ All blocks cleared",
}
strings_ru = {
"no_permission": "<emoji document_id=5854929766146118183>❌</emoji> Нужны права на удаление сообщений",
"pack_blocked": "<emoji document_id=5188311512791393083>✅</emoji> Пак заблокирован",
"pack_not_found": "<emoji document_id=5854929766146118183>❌</emoji> Пак не найден",
"sticker_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Стикер заблокирован",
"emoji_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Эмодзи заблокирован",
"pack_unblocked": "<emoji document_id=5854762571659218443>✅</emoji> Пак разблокирован",
"item_unblocked": "<emoji document_id=5854929766146118183>❌</emoji> Элемент разблокирован",
"not_found": "<emoji document_id=5854929766146118183>❌</emoji> Не найден в блоклисте",
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Ответьте на стикер или эмодзи",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите ссылку или название пака",
"list_packs": "📦 Заблокированные паки: {}",
"list_stickers": "🖼 Заблокированные стикеры: {}",
"list_emojis": "😀 Заблокированные эмодзи: {}",
"all_cleared": "Все блоки очищены",
}
def __init__(self):
self.blocked_packs: Set[str] = set()
self.blocked_stickers: Set[str] = set()
self.blocked_emojis: Set[str] = set()
self._client = None
self._db = None
async def client_ready(self, client, db):
self._client = client
self._db = db
self._load_blocklists()
def _load_blocklists(self):
self.blocked_packs = set(self._db.get(__name__, "blocked_packs", []))
self.blocked_stickers = set(self._db.get(__name__, "blocked_stickers", []))
self.blocked_emojis = set(self._db.get(__name__, "blocked_emojis", []))
def _save_blocklists(self):
self._db.set(__name__, "blocked_packs", list(self.blocked_packs))
self._db.set(__name__, "blocked_stickers", list(self.blocked_stickers))
self._db.set(__name__, "blocked_emojis", list(self.blocked_emojis))
def _extract_pack_name(self, message: Message) -> Optional[str]:
"""Extract pack name from sticker or emoji"""
if not message.media:
return None
if message.sticker:
if hasattr(message.sticker, "set_name") and message.sticker.set_name:
return message.sticker.set_name.lower()
if isinstance(message.media, MessageMediaDocument):
if hasattr(message.media, "document") and hasattr(
message.media.document, "attributes"
):
for attr in message.media.document.attributes:
if (
hasattr(attr, "stickerset")
and hasattr(attr.stickerset, "title")
and attr.stickerset.title
):
return attr.stickerset.title.lower()
return None
def _extract_emoji_text(self, message: Message) -> Optional[str]:
"""Extract emoji text from message"""
if not message.message:
return None
emoji_pattern = re.compile(
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF\U00002702-\U000027B0\U000024C2-\U0001F251]"
)
emojis = emoji_pattern.findall(message.message)
if emojis:
return emojis[0]
return None
async def _delete_message(self, message: Message) -> bool:
"""Delete message with error handling"""
try:
await self._client.delete_messages(message.to_id, [message.id])
return True
except MessageDeleteForbiddenError:
logger.warning("No permission to delete message")
return False
except FloodWaitError as e:
logger.warning(f"Flood wait when deleting message: {e.seconds}s")
return False
except Exception as e:
logger.error(f"Error deleting message: {e}")
return False
async def _should_block_message(self, message: Message) -> tuple[bool, str]:
"""Check if message should be blocked and return reason"""
try:
pack_name = self._extract_pack_name(message)
emoji_text = self._extract_emoji_text(message)
if pack_name and pack_name in self.blocked_packs:
return True, f"pack: {pack_name}"
if message.sticker:
sticker_id = str(message.sticker.id)
if sticker_id in self.blocked_stickers:
return True, f"sticker: {sticker_id}"
if emoji_text and emoji_text in self.blocked_emojis:
return True, f"emoji: {emoji_text}"
except Exception as e:
logger.error(f"Error checking message: {e}")
return False, ""
@loader.command(
ru_doc="[link/название пака] — блокирует эмодзипак/стикерпак в личных сообщениях",
en_doc="[link/pack name] — block emoji pack/sticker pack in private messages",
)
async def packblock(self, message: Message):
"""Block emoji pack/sticker pack"""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
pack_name = args.lower().strip()
if pack_name in self.blocked_packs:
return await utils.answer(message, self.strings["not_found"])
self.blocked_packs.add(pack_name)
self._save_blocklists()
await utils.answer(message, self.strings["pack_blocked"])
@loader.command(
ru_doc="[reply] — блокирует определенный стикер",
en_doc="[reply] — block specific sticker",
)
async def stickblock(self, message: Message):
"""Block sticker from reply"""
if not message.is_reply:
return await utils.answer(message, self.strings["no_reply"])
reply_msg = await message.get_reply_message()
if not reply_msg or not reply_msg.sticker:
return await utils.answer(message, self.strings["no_reply"])
sticker_id = str(reply_msg.sticker.id)
if sticker_id in self.blocked_stickers:
return await utils.answer(message, self.strings["not_found"])
self.blocked_stickers.add(sticker_id)
self._save_blocklists()
await utils.answer(message, self.strings["sticker_blocked"])
@loader.command(
ru_doc="[reply/enter] — блокирует определенное эмодзи",
en_doc="[reply/enter] — block specific emoji",
)
async def emojiblock(self, message: Message):
"""Block emoji from reply or input"""
args = utils.get_args_raw(message)
emoji_text = None
if args:
emoji_text = args.strip()
if not emoji_text:
return await utils.answer(message, self.strings["no_args"])
else:
if not message.is_reply:
return await utils.answer(message, self.strings["no_reply"])
reply_msg = await message.get_reply_message()
if not reply_msg:
return await utils.answer(message, self.strings["no_reply"])
emoji_text = self._extract_emoji_text(reply_msg)
if not emoji_text:
return await utils.answer(message, self.strings["no_reply"])
if emoji_text in self.blocked_emojis:
return await utils.answer(message, self.strings["not_found"])
self.blocked_emojis.add(emoji_text)
self._save_blocklists()
await utils.answer(message, self.strings["emoji_blocked"])
@loader.command(
ru_doc="— снимает блокировку с эмодзипака/стикерпака",
en_doc="— unblock emoji pack/sticker pack",
)
async def ublpack(self, message: Message):
"""Unblock emoji pack/sticker pack"""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
pack_name = args.lower().strip()
if pack_name in self.blocked_packs:
self.blocked_packs.remove(pack_name)
self._save_blocklists()
await utils.answer(message, self.strings["pack_unblocked"])
else:
await utils.answer(message, self.strings["not_found"])
@loader.command(
ru_doc="[reply/enter] — снимает блокировку с определенного эмодзи/стикера",
en_doc="[reply/enter] — unblock specific emoji/sticker",
)
async def ublthis(self, message: Message):
"""Unblock emoji/sticker from reply or input"""
args = utils.get_args_raw(message)
if args:
item = args.strip()
if not item:
return await utils.answer(message, self.strings["no_args"])
else:
if not message.is_reply:
return await utils.answer(message, self.strings["no_reply"])
reply_msg = await message.get_reply_message()
if not reply_msg:
return await utils.answer(message, self.strings["no_reply"])
if reply_msg.sticker:
item = str(reply_msg.sticker.id)
else:
item = self._extract_emoji_text(reply_msg)
if not item:
return await utils.answer(message, self.strings["no_reply"])
unblocked = False
if item in self.blocked_stickers:
self.blocked_stickers.remove(item)
unblocked = True
if item in self.blocked_emojis:
self.blocked_emojis.remove(item)
unblocked = True
if unblocked:
self._save_blocklists()
await utils.answer(message, self.strings["item_unblocked"])
else:
await utils.answer(message, self.strings["not_found"])
@loader.command(
ru_doc="— показать список заблокированных паков/стикеров/эмодзи",
en_doc="— show list of blocked packs/stickers/emojis",
)
async def blocklist(self, message: Message):
"""Show blocklist"""
packs_list = ", ".join(self.blocked_packs) if self.blocked_packs else "нет"
stickers_list = (
", ".join(self.blocked_stickers) if self.blocked_stickers else "нет"
)
emojis_list = ", ".join(self.blocked_emojis) if self.blocked_emojis else "нет"
result = []
if packs_list:
result.append(self.strings["list_packs"].format(packs_list))
if stickers_list:
result.append(self.strings["list_stickers"].format(stickers_list))
if emojis_list:
result.append(self.strings["list_emojis"].format(emojis_list))
if result:
await utils.answer(message, "\n".join(result))
else:
await utils.answer(message, self.strings["all_cleared"])
@loader.command(ru_doc="— очистить все блокировки", en_doc="— clear all blocks")
async def clearblocks(self, message: Message):
"""Clear all blocks"""
self.blocked_packs.clear()
self.blocked_stickers.clear()
self.blocked_emojis.clear()
self._save_blocklists()
await utils.answer(message, self.strings["all_cleared"])
async def watcher(self, message: Message):
"""Monitor messages and block unwanted content"""
if not self._client or not self._db:
return
if message.is_group or message.is_channel:
return
should_block, reason = await self._should_block_message(message)
if should_block:
logger.info(f"Blocking message: {reason}")
await self._delete_message(message)

View File

@@ -1,90 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: FakeActions
# Description: Module for simulating various actions in chat
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api FakeActions
# scope: Api FakeActions 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class FakeActionsMod(loader.Module):
"""Module for simulating various actions in chat"""
strings = {"name": "FakeActions"}
def __init__(self):
self.config = loader.ModuleConfig(
"DEFAULT_DURATION", 5, "Default duration for actions in seconds"
)
async def ftcmd(self, message):
"""<seconds> - Simulates typing in chat for the specified number of seconds."""
await self._simulate_action_command(message, "typing")
async def ffcmd(self, message):
"""<seconds> - Simulates sending a file."""
await self._simulate_action_command(message, "document")
async def fgcmd(self, message):
"""<seconds> - Simulates recording a voice message."""
await self._simulate_action_command(message, "record-audio")
async def fvgcmd(self, message):
"""<seconds> - Simulates recording a video message."""
await self._simulate_action_command(message, "record-round")
async def fpgcmd(self, message):
"""<seconds> - Simulates playing a game."""
await self._simulate_action_command(message, "game")
async def _simulate_action_command(self, message, action):
"""General function for handling action simulation commands."""
duration = self._parse_duration(message)
if duration is None:
await utils.answer(
message,
f"Usage: {self.get_prefix()}{message.raw_text.split()[0][1:]} <seconds>",
)
return
await message.delete()
await self._simulate_action(message, action, duration)
def _parse_duration(self, message):
"""Parse the duration from the message."""
args = message.raw_text.split()
if len(args) == 2 and args[1].isdigit():
return int(args[1])
return self.config["DEFAULT_DURATION"]
async def _simulate_action(self, message, action, duration):
"""Simulate the specified action in chat."""
async with message.client.action(message.chat_id, action):
await asyncio.sleep(duration)

View File

@@ -1,179 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: FakeWallet
# Description: Fun joke - fake crypto wallet. You can change cryptocurrency values using .cfg FakeWallet.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: hikka_only
# scope: hikka_min 1.4.2
# -----------------------------------------------------------------------------------
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class FakeWallet(loader.Module):
"""Fun joke - fake crypto wallet. You can change cryptocurrency values using .cfg FakeWallet."""
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"Toncoin",
0,
lambda: self.strings("ton"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Tether",
0,
lambda: self.strings("tether"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Bitcoin",
0,
lambda: self.strings("btc"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Etherium",
0,
lambda: self.strings("ether"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Binance",
0,
lambda: self.strings("binc"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Tron",
0,
lambda: self.strings("tron"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"USDT",
0,
lambda: self.strings("usdt"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Gram",
0,
lambda: self.strings("gram"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Litecoin",
0,
lambda: self.strings("lite"),
validator=loader.validators.Integer(),
),
)
strings = {
"name": "FakeWallet",
"crypto": "Enter a value for your cryptovalute",
"wallet": "<emoji document_id=5438626338560810621>👛</emoji> <b>Wallet</b>\n\n"
"<emoji document_id=5215276644620586569>☺️</emoji> <a href='https://ton.org'>Toncoin</a>: {} TON\n\n"
"<emoji document_id=5215699136258524363>☺️</emoji> <a href='https://tether.to'>Tether</a>: {} USDT\n\n"
"<emoji document_id=5215590800003451651>☺️</emoji> <a href='https://bitcoin.org'>Bitcoin</a>: {} BTC\n\n"
"<emoji document_id=5217867240044512715>☺️</emoji> <a href='https://etherium.org'>Etherium</a>: {} ETH\n\n"
"<emoji document_id=5215595550237279768>☺️</emoji> <a href='https://binance.org'>Binance coin</a>: {} BNB\n\n"
"<emoji document_id=5215437796088499410>☺️</emoji> <a href='https://tron.network'>TRON</a>: {} TRX\n\n"
"<emoji document_id=5215440441788351459>☺️</emoji> <a href='https://www.centre.io/usdc'>USD Coin</a>: {} USDC\n\n"
"<emoji document_id=5215267041073711005>☺️</emoji> <a href='https://gramcoin.org'>Gram</a>: {} GRAM\n\n"
"<emoji document_id=5217877586620729050>☺️</emoji> <a href='https://litecoin.org'>Litecoin</a>: {} LTC",
"ton": "Enter a value for Toncoin",
"teth": "Enter a value for Tethcoin",
"btc": "Enter a value for Bitcoin",
"ether": "Enter a value for Etherium",
"binc": "Enter a value for Binance coin",
"tron": "Enter a value for Tron",
"usdt": "Enter a value for USDT coin",
"gram": "Enter a value for Gramcoin",
"lite": "Enter a value for Litecoin",
"info": "<b><emoji document_id=5305467350064047192>🫥</emoji><i>Attention!</b>\n\n"
"<i><emoji document_id=5915991028430542030>☝️</emoji>This module is strictly prohibited from being used for the purposes of <b>scam, fraud and advertising</b>.\n\n"
"<emoji document_id=5787190061644647815>🗣</emoji>The module is provided solely for entertainment purposes, and any violation of the <b>Rules for using the module</b>, if detected, will be subject <b>to appropriate punishment</i>",
}
strings_ru = {
"wallet": "<emoji document_id=5438626338560810621>👛</emoji> <b>Кошелёк</b>\n\n"
"<emoji document_id=5215276644620586569>☺️</emoji> <a href='https://ton.org'>Toncoin</a>: {} TON\n\n"
"<emoji document_id=5215699136258524363>☺️</emoji> <a href='https://tether.to'>Tether</a>: {} USDT\n\n"
"<emoji document_id=5215590800003451651>☺️</emoji> <a href='https://bitcoin.org'>Bitcoin</a>: {} BTC\n\n"
"<emoji document_id=5217867240044512715>☺️</emoji> <a href='https://etherium.org'>Etherium</a>: {} ETH\n\n"
"<emoji document_id=5215595550237279768>☺️</emoji> <a href='https://binance.org'>Binance coin</a>: {} BNB\n\n"
"<emoji document_id=5215437796088499410>☺️</emoji> <a href='https://tron.network'>TRON</a>: {} TRX\n\n"
"<emoji document_id=5215440441788351459>☺️</emoji> <a href='https://www.centre.io/usdc'>USD Coin</a>: {} USDC\n\n"
"<emoji document_id=5215267041073711005>☺️</emoji> <a href='https://gramcoin.org'>Gram</a>: {} GRAM\n\n"
"<emoji document_id=5217877586620729050>☺️</emoji> <a href='https://litecoin.org'>Litecoin</a>: {} LTC",
"ton": "Введите количество валюты для Toncoin",
"teth": "Введите количество валюты для Tethcoin",
"btc": "Введите количество валюты для Bitcoin",
"ether": "Введите количество валюты для Etherium",
"binc": "Введите количество валюты для Binance coin",
"tron": "Введите количество валюты для Tron",
"usdt": "Введите количество валюты для USDT coin",
"gram": "Введите количество валюты для Gramcoin",
"lite": "Введите количество валюты для Litecoin",
"info": "<b><emoji document_id=5305467350064047192>🫥</emoji><i> Внимание!</b>\n\n"
"<i><emoji document_id=5915991028430542030>☝️</emoji> Использование этого модуля в целях <b>скама, обмана и рекламы</b> строго запрещено.\n\n"
"<emoji document_id=5787190061644647815>🗣</emoji> Модуль предоставлен исключительно в развлекательных целях, и любое нарушение <b>Правил использования модуля</b>, если его обнаружат, будет подлежать соответствующему наказанию.</i>",
}
@loader.command(
ru_doc="Чтобы заполучить поддельный кошелек",
en_doc="To get a fake wallet",
)
@loader.command()
async def fwalletcmd(self, message):
ton = self.config["Toncoin"]
teth = self.config["Tether"]
btc = self.config["Bitcoin"]
ether = self.config["Etherium"]
binc = self.config["Binance"]
tron = self.config["Tron"]
usdt = self.config["USDT"]
gram = self.config["Gram"]
lite = self.config["Litecoin"]
await utils.answer(
message,
self.strings("wallet").format(
ton, teth, btc, ether, binc, tron, usdt, gram, lite
),
)
@loader.command(
ru_doc="Информация о FakeModule",
en_doc="Info about FakeModule",
)
@loader.command()
async def fwinfocmd(self, message):
await utils.answer(message, self.strings("info"))

View File

@@ -1,124 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: GigaChat
# Description: Module for using GigaChat
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api GigaChat
# scope: Api GigaChat 0.0.1
# ---------------------------------------------------------------------------------
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class GigaChatMod(loader.Module):
"""Module for using GigaChat"""
strings = {
"name": "GigaChat",
"api_key_missing": "Please set the API key in the module configuration.",
"query_missing": "Please enter a query after the command.",
"response_error": "Failed to get a response from GigaChat.",
"error_occurred": "An error occurred: {}",
"formatted_response": (
"<emoji document_id=6030848053177486888>❓</emoji> Query: {}\n"
"<emoji document_id=6030400221232501136>🤖</emoji> GigaChat: {}"
),
"giga_model": "List of GigaChat models:\n{}",
}
strings_ru = {
"api_key_missing": "Пожалуйста, установите API ключ в конфигурации модуля.",
"query_missing": "Пожалуйста, введите запрос после команды.",
"response_error": "Не удалось получить ответ от GigaChat.",
"error_occurred": "Произошла ошибка: {}",
"formatted_response": (
"<emoji document_id=6030848053177486888>❓</emoji> Запрос: {}\n"
"<emoji document_id=6030400221232501136>🤖</emoji> GigaChat: {}"
),
"giga_model": "Список моделей GigaChat:\n{}",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://files.archquise.ru/HModsLibrary.py"
)
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"GIGACHAT_API_KEY",
None,
"Введите ваш API ключ для GigaChat, Чтобы получить ключ API, перейдите сюда: https://developers.sber.ru/studio/workspaces",
validator=loader.validators.Hidden(),
),
loader.ConfigValue(
"GIGACHAT_MODEL",
"GigaChat",
"Введите модель, ее можно получить при команде .gigamodel",
),
)
@loader.command(
ru_doc="Получите исчерпывающий ответ на свой вопрос",
en_doc="Get GigaResponse to your question",
)
async def giga(self, message):
api_key = self.config["GIGACHAT_API_KEY"]
if not api_key:
return await utils.answer(message, self.strings("api_key_missing"))
query = utils.get_args_raw(message)
if not query:
return await utils.answer(message, self.strings("query_missing"))
try:
response = await self.hmodslib.get_giga_response(api_key, query)
if response:
await utils.answer(
message, self.strings("formatted_response").format(query, response)
)
else:
await utils.answer(message, self.strings("response_error"))
except Exception as e:
await utils.answer(message, self.strings("error_occurred").format(str(e)))
@loader.command(
ru_doc="Получить список моделей",
en_doc="Get a list of models",
)
async def gigamodel(self, message):
api_key = self.config["GIGACHAT_API_KEY"]
if not api_key:
return await utils.answer(message, self.strings("api_key_missing"))
try:
response = await self.hmodslib.get_giga_models(api_key)
if response:
await utils.answer(message, self.strings("giga_model").format(response))
else:
await utils.answer(message, self.strings("response_error"))
except Exception as e:
await utils.answer(message, self.strings("error_occurred").format(str(e)))

View File

@@ -1,44 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: H
# Description: H.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: H
# scope: H 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class H(loader.Module):
"""H"""
strings = {"name": "H", "h": "H"}
strings_ru = {"h": "H"}
@loader.command(
ru_doc="H",
)
async def h(self, message):
"""H"""
await utils.answer(message, self.strings("h"))

View File

@@ -1,306 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: HAFK
# Description: Your personal assistant while you are in AFK mode
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: HAFK
# scope: HAFK 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import datetime
import logging
import time
from telethon import types
from telethon.utils import get_peer_id
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class HAFK(loader.Module):
strings = {
"name": "HAFK",
"afk_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on!</b>",
"afk_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on!</b>\n\n<b>Reason:</b> <i>{}</i>",
"afk_here_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on in this chat!</b>",
"afk_here_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on in this chat!</b>\n\n<b>Reason:</b> <i>{}</i>",
"afk_off": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off!</b>",
"afk_off_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off!</b>\n\n<b>You were AFK for:</b> {}",
"afk_off_here_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off in this chat!</b>\n\n<b>You were AFK for:</b> {}",
"already_afk": "<emoji document_id=5465665476971471368>❌</emoji> <b>You are already in AFK mode!</b>",
"already_afk_here": "<emoji document_id=5465665476971471368>❌</emoji> <b>You are already in AFK mode in this chat!</b>",
"not_afk": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK mode is already off.</b>",
"not_afk_here": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK mode is already off in this chat.</b>",
"afk_message": "<emoji document_id=5330130448142049118>🫤</emoji> <b>I'm currently not accepting messages!</b>\n<b>Reason:</b> <i>{}</i>\n\n<i>Inactive mode has been on for:</i> {}",
"afk_message_reason": "<emoji document_id=5330130448142049118>🫤</emoji> <b>I'm currently not accepting messages!</b>\n<b>Reason:</b> <i>{}</i>\n\n<i>Inactive mode has been on for:</i> {}",
"afk_set_failed": "<b>Failed to set AFK status. Check logs.</b>",
"added_excluded_chat": "<b>Chat {} added to excluded chats.</b>",
"removed_excluded_chat": "<b>Chat {} removed from excluded chats.</b>",
"excluded_chats_list": "<b>Excluded chats:</b>\n{}",
"no_excluded_chats": "<b>No chats are excluded.</b>",
"invalid_chat_id": "<b>Invalid chat ID.</b>",
"already_excluded": "<b>Chat {} is already excluded.</b>",
"not_excluded": "<b>Chat {} is not excluded.</b>",
}
strings_ru = {
"afk_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен!</b>",
"afk_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен!</b>\n\n<b>Причина:</b> <i>{}</i>",
"afk_here_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен в этом чате!</b>",
"afk_here_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен в этом чате!</b>\n\n<b>Причина:</b> <i>{}</i>",
"afk_off": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен!</b>",
"afk_off_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен!</b>\n\n<b>Вы были AFK:</b> {}",
"afk_off_here_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен в этом чате!</b>\n\n<b>Вы были AFK:</b> {}",
"already_afk": "<emoji document_id=5465665476971471368>❌</emoji> <b>Вы уже находитесь в AFK-режиме!</b>",
"already_afk_here": "<emoji document_id=5465665476971471368>❌</emoji> <b>Вы уже находитесь в AFK-режиме в этом чате!</b>",
"not_afk": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK-режим уже отключён.</b>",
"not_afk_here": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK-режим уже отключен в этом чате.</b>",
"afk_message": "<emoji document_id=5330130448142049118>🫤</emoji> <b>На данный момент я не принимаю сообщения!</b>\n<b>Причина:</b> <i>{}</i>\n\n<i>С момента включения режима неактивности:</i> {}",
"afk_message_reason": "<emoji document_id=5330130448142049118>🫤</emoji> <b>На данный момент я не принимаю сообщения!</b>\n<b>Причина:</b> <i>{}</i>\n\n<i>С момента включения режима неактивности:</i> {}",
"afk_set_failed": "<b>Не удалось установить AFK-статус. Проверьте логи.</b>",
"added_excluded_chat": "<b>Чат {} добавлен в список исключений.</b>",
"removed_excluded_chat": "<b>Чат {} удален из списка исключений.</b>",
"excluded_chats_list": "<b>Список исключенных чатов:</b>\n{}",
"no_excluded_chats": "<b>Нет исключенных чатов.</b>",
"invalid_chat_id": "<b>Неверный ID чата.</b>",
"already_excluded": "<b>Чат {} уже исключен.</b>",
"not_excluded": "<b>Чат {} не исключен.</b>",
}
DEFAULT_AFK_TIMEOUT = 60
DEFAULT_DELETE_TIMEOUT = 5
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"excluded_chats",
[],
lambda: "List of chat IDs where AFK mode will not be activated",
validator=loader.validators.Series(
validator=loader.validators.Integer()
),
)
)
async def client_ready(self, client, db):
self.client = client
self.db = db
self._me = await client.get_me()
self._ratelimit_cache = {}
self.global_afk = self.db.get(__name__, "afk", False)
self.global_afk_reason = self.db.get(__name__, "afk_reason", None)
self.global_gone_time = self.db.get(__name__, "gone_afk", None)
logger.debug(
f"Initial global AFK state: afk={self.global_afk}, reason={self.global_afk_reason}, gone_time={self.global_gone_time}"
)
@loader.command(
ru_doc="[reason / none] Установить режим AFK",
en_doc="[reason / none] Set AFK mode globally",
)
async def afk(self, message):
await self._afk_toggle(message, global_afk=True)
@loader.command(
ru_doc="[reason / none] Установить режим AFK только в этом чате.",
en_doc="[reason / none] Set AFK mode in current chat only.",
)
async def afkhere(self, message):
await self._afk_toggle(message, global_afk=False)
async def _afk_toggle(self, message, global_afk: bool):
chat_id = utils.get_chat_id(message)
already_afk_string = "already_afk" if global_afk else "already_afk_here"
afk_on_string = "afk_on" if global_afk else "afk_here_on"
afk_on_reason_string = "afk_on_reason" if global_afk else "afk_here_on_reason"
if self._is_afk_enabled(chat_id, global_afk):
await utils.answer(message, self.strings(already_afk_string, message))
return
reason = utils.get_args_raw(message) or None
success = self._set_afk(
True, reason=reason, chat_id=chat_id if not global_afk else None
)
if not success:
await utils.answer(message, self.strings("afk_set_failed", message))
return
await utils.answer(
message,
self.strings(afk_on_reason_string, message).format(reason)
if reason
else self.strings(afk_on_string, message),
)
@loader.command(
ru_doc="Выйти из режима AFK",
en_doc="Exit AFK mode",
)
async def unafk(self, message):
await self._unafk_toggle(message, global_afk=True)
@loader.command(
ru_doc="Выйти из режима AFK в этом чате",
en_doc="Exit AFK mode in this chat",
)
async def unafkhere(self, message):
await self._unafk_toggle(message, global_afk=False)
async def _unafk_toggle(self, message, global_afk: bool):
chat_id = utils.get_chat_id(message)
not_afk_string = "not_afk" if global_afk else "not_afk_here"
afk_off_time_string = "afk_off_time" if global_afk else "afk_off_here_time"
if not self._is_afk_enabled(chat_id, global_afk):
await utils.answer(message, self.strings(not_afk_string, message))
return
total_gone_time = self._calculate_total_afk_time(
datetime.datetime.now().replace(microsecond=0),
chat_id=chat_id if not global_afk else None,
)
self._set_afk(False, chat_id=chat_id if not global_afk else None)
await self.allmodules.log("unafk" if global_afk else "unafkhere")
await utils.answer(
message, self.strings(afk_off_time_string, message).format(total_gone_time)
)
async def watcher(self, message):
if not isinstance(message, types.Message):
return
chat_id = get_peer_id(message.peer_id)
user_id = getattr(message.to_id, "user_id", None)
is_mentioned = message.mentioned or user_id == self._me.id
is_private = isinstance(message.to_id, types.PeerUser)
if not (is_mentioned or is_private) or chat_id in self.config["excluded_chats"]:
return
reason = None
gone_time = None
if self._is_afk_enabled(chat_id, False):
reason = self.db.get(__name__, f"afk_here_{chat_id}_reason", None)
gone_time = self.db.get(__name__, f"gone_afk_here_{chat_id}", None)
elif self.global_afk:
reason = self.global_afk_reason
gone_time = self.global_gone_time
else:
return
if gone_time is None:
logger.warning(f"No 'gone' time found for chat {chat_id}. Cannot send AFK.")
return
afk_message = await self._send_afk_message(message, reason, gone_time)
if afk_message:
await self._delete_message(afk_message, self.DEFAULT_DELETE_TIMEOUT)
def _set_afk(self, value: bool, reason: str = None, chat_id: int = None) -> bool:
try:
key_prefix = f"afk_here_{chat_id}" if chat_id else "afk"
self.db.set(__name__, key_prefix, value)
self.db.set(__name__, f"{key_prefix}_reason", reason)
gone_key = f"gone_{key_prefix}"
gone_time = time.time() if value else None
self.db.set(__name__, gone_key, gone_time)
logger.debug(f"AFK status updated. {gone_key} set to {gone_time}")
if chat_id is None:
self.global_afk = value
self.global_afk_reason = reason
self.global_gone_time = gone_time
logger.debug("Updated global AFK vars")
return True
except Exception as e:
logger.exception(f"Error setting AFK status: {e}")
return False
def _calculate_total_afk_time(
self, now: datetime.datetime, chat_id: int = None
) -> datetime.timedelta:
key_prefix = f"afk_here_{chat_id}" if chat_id else "afk"
gone_time = self.db.get(__name__, f"gone_{key_prefix}")
if gone_time is None:
return datetime.timedelta(seconds=0)
try:
gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0)
return now - gone
except Exception as e:
logger.error(f"Error calculating AFK time: {e}")
return datetime.timedelta(seconds=0)
async def _send_afk_message(self, message, reason, gone_time):
user = await utils.get_user(message)
if user.is_self or user.bot or user.verified:
logger.debug("User is self, bot, or verified. Not sending AFK message.")
return None
now = datetime.datetime.now().replace(microsecond=0)
try:
gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0)
except (TypeError, ValueError) as e:
logger.error(f"Error converting timestamp: {e}")
return None
time_string = str(now - gone)
ret = (
self.strings("afk_message_reason", message).format(reason, time_string)
if reason
else self.strings("afk_message", message).format(time_string)
)
try:
reply = await utils.answer(message, ret, reply_to=message)
return reply
except Exception as e:
logger.exception(f"Error sending AFK message: {e}")
return None
def _is_afk_enabled(self, chat_id: int = None, global_afk: bool = False) -> bool:
return (
self.global_afk
if global_afk
else self.db.get(__name__, f"afk_here_{chat_id}", False)
)
async def _delete_message(self, message, delay):
await asyncio.sleep(delay)
try:
await self.client.delete_messages(message.chat_id, message.id)
except Exception as e:
logger.exception(f"Error deleting message: {e}")

View File

@@ -1,107 +0,0 @@
# 🔐 Licensed under the GNU AGPLv3.
# ---------------------------------------------------------------------------------
# Name: HInstall
# Description: Provides H:Mods modules installation trough buttons
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# requires: PyCryptodome
# ---------------------------------------------------------------------------------
# #################################################################################
# ########## This module is based on @hikariatama 's hikkamods_socket!! ###########
# #################################################################################
__version__ = (1, 0, 0)
import base64
import logging
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import pkcs1_15
from telethon.tl.types import Message
from telethon import functions, types
from typing import Optional
from .. import loader, utils
logger = logging.getLogger(__name__)
pubkey_data = """
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvekpGqKiD2HZwY/J7jZv
PwGRobAS2TaC9HU5LUNRDg90jA/r8xgoFhlCBJocq8+XvJIWpgmIEYWJCz0KpCXu
Meu42bAXvLqniDOqnOt8FjXFapGZvEMLen1CLCRr1OQhVNpRlPjjWo7PM+YpUnbw
giqEZ9nA5DQ5Gi0vsSHXAnBa+ZIsxaY3EwosHMvUUhnnijcbBpkyYRJ8atvsT9AX
cNS+NjDE4Kj8jSnArQ1D1Ct1pcZEXD6DUk2k3HAD4OlZS5nY5IFchWEcpLT/Fjbt
BzGBZCJZ+rp8qR1tCVvVTV3itACc8O0Pirmptkrxb3A4pC0S8oxYBFQcnZAlIiw3
uX36O90AkRwbsdnsp2JVg5AAPUYvdsMoCGG+cSGZC73arqcrvn0VFo7EhsYq/1Ds
CevorFI4TiLVbSlFSVnX5baqmTj+XNhgaWWmiY/+mhErzsWtpCOHYFitf1xqp3zD
9O2Vs7lQIxMsHFISAEhn8BqQxvlwslfcjmbuJxkYriqAHXQGS3IZDXhEZXwouOUV
HGN2YD5aLK0L8OuTNY5cf1TN8C5xgVZoEodAKqAva/i1v/F6IQk3iEo0ncgypeyg
NM1TUudkQ+f1wXqLj2YaVKqRdKswl9vgYpUCHjGZfN+WYT4DbOMrJm1OFeen6geo
xqON1/xeRBgkE3tna3RuhmUCAwEAAQ==
-----END PUBLIC KEY-----
"""
pubkey = RSA.importKey(pubkey_data.strip())
@loader.tds
class HInstallMod(loader.Module):
"""Provides H:Mods modules installation trough buttons"""
strings = {
"name": "HInstall",
"_cls_doc": "Provides H:Mods modules installation trough buttons",
"module_downloaded": "Module downloaded!"
}
strings_ru = {
"_cls_doc": "Позволяет устанавливать модули от H:Mods через кнопки",
"module_downloaded": "Модуль загружен!"
}
async def on_dlmod(self, client, db):
ent = await self.client(functions.users.GetFullUserRequest('@hinstall_bot'))
if ent.full_user.blocked:
await self.client(functions.contacts.UnblockRequest('@hinstall_bot'))
await self.client.send_message('@hinstall_bot', '/start')
await self.client.delete_dialog('@hinstall_bot')
async def _load_module(self, url: str, message: Optional[Message] = None):
loader_m = self.lookup("loader")
await loader_m.download_and_install(url, None)
if getattr(loader_m, "_fully_loaded", getattr(loader_m, "fully_loaded", False)):
getattr(
loader_m,
"_update_modules_in_db",
getattr(loader_m, "update_modules_in_db", lambda: None),
)()
async def watcher(self, message: Message):
if not isinstance(message, Message):
return
if message.sender_id == 8104671142 and message.raw_text.startswith("#install"):
await message.delete()
fileref = (
message.raw_text.split("#install:")[1].strip().splitlines()[0].strip()
)
sig = base64.b64decode(message.raw_text.splitlines()[1].strip().encode())
try:
h = SHA256.new(fileref.encode("utf-8"))
pkcs1_15.new(pubkey).verify(h, sig)
except (ValueError, TypeError):
logger.error(f"Got message with non-verified signature ({fileref=})")
return
await self._load_module(f"https://raw.githubusercontent.com/archquise/H.Modules/refs/heads/main/{fileref}", message)
await self.client.send_message('@hinstall_bot', self.strings['module_downloaded'])

View File

@@ -1,108 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact archquise@gmail.com.
# ---------------------------------------------------------------------------------
# Name: InfoBannersManager
# Description: Автоматически меняет баннеры на случайные из выбранного списка через заданный промежуток времени
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
import logging
import random
from .. import loader
logger = logging.getLogger(__name__)
@loader.tds
class InfoBannersManagerMod(loader.Module):
"""Автоматически меняет баннеры на случайные из выбранного списка через заданный промежуток времени"""
strings = {"name": "InfoBannersManager"}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"enabled",
False,
"Включить автоматическую смену баннеров",
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"delay",
60,
"Задержка между изменениями баннеров в секундах",
validator=loader.validators.Integer(minimum=1),
),
loader.ConfigValue(
"bannerslist",
None,
"Список ссылок на баннеры",
validator=loader.validators.Series(validator=loader.validators.Link()),
),
)
async def banner_changer(self):
"""Change banner periodically"""
try:
if not self.config["bannerslist"]:
logger.warning("Banners list is empty!")
return
banner = random.choice(self.config["bannerslist"])
instance = self.lookup("HerokuInfo")
if not instance:
instance = self.lookup("HikkaInfo")
if instance:
instance.config["banner_url"] = banner
logger.info(f"Banner changed to: {banner}")
else:
logger.warning("Info module not found!")
except Exception as e:
logger.exception(f"Error changing banner: {e}")
@loader.loop(interval=60, autostart=False)
async def banner_loop(self):
"""Main banner changing loop"""
if not self.config["enabled"]:
return
await self.banner_changer()
# Update interval from config
self.banner_loop.set_interval(self.config["delay"])
async def client_ready(self):
"""Initialize the banner changer loop"""
if self.config["enabled"]:
self.banner_loop.start()
def on_config_update(self, config_key, new_value):
"""Handle config updates"""
if config_key == "enabled":
if new_value:
self.banner_loop.start()
else:
self.banner_loop.stop()
elif config_key == "delay":
# Update interval immediately
self.banner_loop.set_interval(new_value)

View File

@@ -1,88 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineButton
# Description: Create inline button
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineButton
# scope: InlineButton 0.0.1
# ---------------------------------------------------------------------------------
import logging
from .. import loader, utils
from ..inline.types import InlineQuery
logger = logging.getLogger(__name__)
@loader.tds
class InlineButtonMod(loader.Module):
"""Create inline buttons with enhanced functionality"""
strings = {
"name": "InlineButton",
"titles": "🔘 Create message with Inline Button",
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Error",
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Invalid input format. Please provide exactly three comma-separated values: message, name, url.",
"error_message": "<emoji document_id=5854929766146118183>❌</emoji> Make sure your input is formatted as: message, name, url.",
"button_created": "<emoji document_id=5854762571659218443>✅</emoji> Button created successfully!",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Please provide arguments: message, name, url.",
}
strings_ru = {
"titles": "🔘 Создать сообщение с Inline Кнопкой",
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка",
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми: сообщение, имя, url.",
"error_message": "<emoji document_id=5854929766146118183>❌</emoji> Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.",
"button_created": "<emoji document_id=5854762571659218443>✅</emoji> Кнопка успешно создана!",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите аргументы: сообщение, имя, url.",
}
@loader.command(
ru_doc="Создать inline кнопку\nНапример: @username_bot crinl Текст сообщения, Текст кнопки, Ссылка в кнопке",
en_doc="Create an inline button\nexample: @username_bot crinl Message text, Button text, Link in the button",
)
async def crinl_inline_handler(self, query: InlineQuery):
args = utils.get_args_raw(query.query)
if not args:
return {
"title": self.strings("error_title"),
"description": self.strings("error_description"),
"message": self.strings("no_args"),
}
args_list = [arg.strip() for arg in args.split(",")]
if len(args_list) != 3:
return {
"title": self.strings("error_title"),
"description": self.strings("error_description"),
"message": self.strings("error_message"),
}
message, name, url = args_list
return True, {
"message": message,
"reply_markup": [{"text": name, "url": url}],
"description": self.strings("button_created"),
}

View File

@@ -1,92 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineCoin
# Description: Mini game heads or tails.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineCoin
# scope: InlineCoin 0.0.1
# ---------------------------------------------------------------------------------
import logging
import random
from typing import Dict
from .. import loader
from ..inline.types import InlineQuery
logger = logging.getLogger(__name__)
@loader.tds
class CoinFlipMod(loader.Module):
"""Mini coin flip game"""
strings = {
"name": "InlineCoin",
"titles": "🪙 Heads or Tails?",
"description": "🎲 Let's find out!",
"heads": "🦅 An eagle fell out!",
"tails": "🪙 Tails fell out!",
"edge": "🙀 Miraculously, the coin remained on its edge!",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Please provide a command to flip.",
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> An error occurred: {error}",
}
strings_ru = {
"titles": "🪙 Орёл или решка?",
"description": "🎲 Давай узнаем!",
"heads": "🦅 Выпал орёл!",
"tails": "🪙 Выпала решка!",
"edge": "🙀 Чудо, монетка осталась на ребре!",
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите команду для подбрасывания монетки.",
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка: {error}",
}
def get_coin_flip_result(self) -> Dict[str, str]:
"""Get coin flip result with better formatting"""
return {
"title": self.strings["titles"],
"description": self.strings["description"],
"message": f"<b>{random.choice([self.strings['heads'], self.strings['tails']])}</b>",
"thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png",
}
@loader.command(
ru_doc="Подбросить монетку",
en_doc="Flip a coin",
)
async def coin_inline_handler(self, query: InlineQuery):
"""Handle coin flip inline query"""
if not query.args:
return {
"title": self.strings["titles"],
"description": self.strings["no_args"],
"message": self.strings["no_args"],
}
result = self.get_coin_flip_result()
return {
"title": self.strings["titles"],
"description": self.strings["description"],
"message": result["message"],
"thumb": result["thumb"],
}

View File

@@ -1,308 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineHelper
# Description: Basic management of the UB in case only the inline works
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineHelper
# scope: InlineHelper 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import logging
import shlex
import sys
from .. import loader, main, utils
from ..inline.types import InlineQuery
logger = logging.getLogger(__name__)
@loader.tds
class InlineHelperMod(loader.Module):
"""Basic management of the UB in case only the inline works"""
strings = {
"name": "InlineHelper",
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Restarting...",
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Updating...",
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Prefix successfully reset to default",
"restart_inline_handler_title": "🔄 Restart Userbot",
"restart_inline_handler_description": "Restart your userbot via inline",
"restart_inline_handler_message": "🔄 Restart",
"update_inline_handler_title": "🔄 Update Userbot",
"update_inline_handler_description": "Update your userbot via inline",
"update_inline_handler_message": "🔄 Update",
"terminal_inline_handler_title": "💻 Command Executed",
"terminal_inline_handler_description": "Command executed successfully",
"terminal_inline_handler_message": "Command <code>{text}</code> executed successfully in terminal",
"modules_inline_handler_title": "📦 Modules",
"modules_inline_handler_description": "List all installed modules",
"modules_inline_handler_result": "📦 All installed modules:\n\n",
"resetprefix_inline_handler_title": "⚠️ Reset Prefix",
"resetprefix_inline_handler_description": "Reset your prefix back to default (be careful!)",
"resetprefix_inline_handler_message": "Are you sure you want to reset your prefix to default dot?",
"resetprefix_inline_handler_reply_text_yes": "Yes, reset it",
"resetprefix_inline_handler_reply_text_no": "No, cancel",
"error_no_module": "<emoji document_id=5854929766146118183>❌</emoji> Module not found: {module}",
"error_command_failed": "<emoji document_id=5854929766146118183>❌</emoji> Command execution failed: {error}",
"error_git_failed": "<emoji document_id=5854929766146118183>❌</emoji> Git operation failed: {error}",
}
strings_ru = {
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагружаю...",
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Обновляю...",
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Префикс успешно сброшен по умолчанию",
"restart_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузить юзербота",
"restart_inline_handler_description": "Перезагрузить юзербота через инлайн",
"restart_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузка",
"update_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить юзербота",
"update_inline_handler_description": "Обновить юзербота через инлайн",
"update_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить",
"terminal_inline_handler_title": "<emoji document_id=5854762571659218443>💻</emoji> Команда выполнена!",
"terminal_inline_handler_description": "Команда успешно выполнена.",
"terminal_inline_handler_message": "Команда <code>{text}</code> была успешно выполнена в терминале",
"modules_inline_handler_title": "<emoji document_id=5854762571659218443>📦</emoji> Модули",
"modules_inline_handler_description": "Вывести список установленных модулей",
"modules_inline_handler_result": "<emoji document_id=5854762571659218443>📦</emoji> Все установленные модули:\n\n",
"resetprefix_inline_handler_title": "<emoji document_id=5854929766146118183>⚠️</emoji> Сбросить префикс",
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию (осторожно!)",
"resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?",
"resetprefix_inline_handler_reply_text_yes": "Да, сбросить",
"resetprefix_inline_handler_reply_text_no": "Нет, отменить",
"error_no_module": "<emoji document_id=5854929766146118183>❌</emoji> Модуль не найден: {module}",
"error_command_failed": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка выполнения команды: {error}",
"error_git_failed": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка git операции: {error}",
}
def __init__(self):
self.client = None
self.db = None
self._base_dir = utils.get_base_dir()
async def client_ready(self, client, db):
self.client = client
self.db = db
async def restart(self, call):
"""Restart callback"""
logger.info("InlineHelper: Restarting userbot...")
try:
await call.edit(self.strings["call_restart"])
await asyncio.create_subprocess_exec(
[
sys.executable,
"-c",
f"cd {self._base_dir} && git reset --hard HEAD && git pull",
],
cwd=self._base_dir,
)
await call.edit(self.strings["call_update"])
await asyncio.sleep(2)
await asyncio.create_subprocess_exec(
[sys.executable, "-c", f"cd {self._base_dir} && git pull"],
cwd=self._base_dir,
)
await call.edit(self.strings["res_prefix"])
except Exception as e:
logger.error(f"Restart failed: {e}")
await call.edit(self.strings["error_git_failed"].format(error=str(e)))
async def update(self, call):
"""Update callback"""
logger.info("InlineHelper: Updating userbot...")
try:
await call.edit(self.strings["call_update"])
await asyncio.create_subprocess_exec(
[
sys.executable,
"-c",
f"cd {self._base_dir} && git reset --hard HEAD && git pull",
],
cwd=self._base_dir,
)
await call.edit(self.strings["res_prefix"])
except Exception as e:
logger.error(f"Update failed: {e}")
await call.edit(self.strings["error_git_failed"].format(error=str(e)))
async def reset_prefix(self, call):
"""Reset prefix callback"""
try:
self.db.set(main.__name__, "command_prefix", ".")
await call.edit(self.strings["res_prefix"])
except Exception as e:
logger.error(f"Reset prefix failed: {e}")
await call.edit(self.strings["error_command_failed"].format(error=str(e)))
@loader.inline_handler(
ru_doc="Перезагрузить юзербота",
en_doc="Reboot the userbot",
)
async def restart_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("restart_inline_handler_title"),
"description": self.strings("restart_inline_handler_description"),
"message": self.strings("restart_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("restart_inline_handler_reply_text"),
"callback": self.restart,
}
],
}
@loader.inline_handler(
ru_doc="Обновить юзербота",
en_doc="Update the userbot",
)
async def update_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("update_inline_handler_title"),
"description": self.strings("update_inline_handler_description"),
"message": self.strings("update_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("update_inline_handler_reply_text"),
"callback": self.update,
}
],
}
@loader.inline_handler(
ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)",
en_doc="Execute the command in the terminal (it is better to prepare the command immediately and just paste it)",
)
async def terminal_inline_handler(self, query: InlineQuery):
"""Execute terminal command safely"""
if not query.args:
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["terminal_inline_handler_message"].format(
text="No command provided"
),
}
command_text = query.args.strip()
if not command_text:
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["terminal_inline_handler_message"].format(
text="No command provided"
),
}
if any(char in command_text for char in ["&", "|", ";", "`", "$"]):
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["error_command_failed"].format(
error="Invalid characters in command"
),
}
try:
args = shlex.split(command_text)
process = await asyncio.create_subprocess_exec(
args,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self._base_dir,
text=True,
)
stdout, stderr = await process.communicate()
stdout.decode().strip() if stdout else ""
error = stderr.decode().strip() if stderr else ""
if error:
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["error_command_failed"].format(error=error),
}
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["terminal_inline_handler_message"].format(
text=command_text
),
}
except Exception as e:
return {
"title": self.strings["terminal_inline_handler_title"],
"description": self.strings["terminal_inline_handler_description"],
"message": self.strings["error_command_failed"].format(error=str(e)),
}
@loader.inline_handler(
ru_doc="Вывести список установленных модулей через инлайн",
en_doc="Display a list of installed modules via the inline",
)
async def modules_inline_handler(self, query: InlineQuery):
"""List all installed modules"""
try:
result = self.strings["modules_inline_handler_result"]
for mod in self.allmodules.modules:
try:
name = mod.strings["name"]
except KeyError:
name = mod.__class__.__name__
result += f"{name}\n"
except Exception as e:
logger.error(f"Error listing modules: {e}")
result = f"Error listing modules: {str(e)}"
return {
"title": self.strings["modules_inline_handler_title"],
"description": self.strings["modules_inline_handler_description"],
"message": result,
}
@loader.inline_handler(
ru_doc="Сбросить префикс (осторожнее, сбрасывает ваш префикс на . )",
en_doc="Reset the prefix (be careful, resets your prefix to . )",
)
async def resetprefix_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("resetprefix_inline_handler_title"),
"description": self.strings("resetprefix_inline_handler_description"),
"message": self.strings("resetprefix_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("resetprefix_inline_handler_reply_text_yes"),
"callback": self.reset_prefix,
},
{
"text": self.strings("resetprefix_inline_handler_reply_text_no"),
"action": "close",
},
],
}

View File

@@ -1,138 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: KBSwapper
# Description: KBSwapper is a module for changing the keyboard layout
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: KBSwapper
# scope: KBSwapper 0.0.1
# ---------------------------------------------------------------------------------
import logging
import string
from .. import loader, utils
logger = logging.getLogger(__name__)
EN_TO_RU = str.maketrans(
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
"йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё",
)
RU_TO_EN = str.maketrans(
"йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё",
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
)
@loader.tds
class KBSwapperMod(loader.Module):
"""KBSwapper is a module for changing the keyboard layout"""
strings = {
"name": "KBSwapper",
"no_reply": "<emoji document_id=5774077015388852135>❌</emoji> <b>Please reply to a message.</b>",
"no_text": "<emoji document_id=5774077015388852135>❌</emoji> <b>The replied message does not contain text.</b>",
"original_message": "<emoji document_id=5260450573768990626>➡️</emoji> <b>Original message:</b>\n<code>{original}</code>",
"fixed_message": "<emoji document_id=5774022692642492953>✅</emoji> <b>Fixed message:</b>\n<code>{fixed}</code>",
"error": "<emoji document_id=5774077015388852135>❌</emoji> <b>An error occurred while processing the message.</b>",
}
strings_ru = {
"no_reply": "<emoji document_id=5774077015388852135>❌</emoji> <b>Пожалуйста, ответьте на сообщение.</b>",
"no_text": "<emoji document_id=5774077015388852135>❌</emoji> <b>Отвеченное сообщение не содержит текста.</b>",
"original_message": "<emoji document_id=5260450573768990626>➡️</emoji> <b>Оригинальное сообщение:</b>\n<code>{original}</code>",
"fixed_message": "<emoji document_id=5774022692642492953>✅</emoji> <b>Исправленное сообщение:</b>\n<code>{fixed}</code>",
"error": "<emoji document_id=5774077015388852135>❌</emoji> <b>Произошла ошибка при обработке сообщения.</b>",
}
@loader.command(
ru_doc="При ответе на своё сообщение меняет раскладку путем редактирования, на чужое — в отдельном сообщении.",
en_doc="Change keyboard layout for the replied message.",
)
async def swap(self, message):
reply = await message.get_reply_message()
if not reply:
await utils.answer(message, self.strings("no_reply"))
return
original_text = reply.text
if not original_text or original_text.isspace():
await utils.answer(message, self.strings("no_text"))
return
try:
trimmed_text = original_text.strip()
has_russian = any(
char
in "йцукенгшщзхъфывапролджэячсмитьбюёЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ"
for char in trimmed_text
)
has_english = any(char in string.ascii_letters for char in trimmed_text)
logger.debug(
f"Text analysis - Russian: {has_russian}, English: {has_english}, Text: {trimmed_text[:50]}..."
)
if has_russian and not has_english:
fixed_text = original_text.translate(RU_TO_EN)
logger.debug("Detected Russian text, translating to English")
elif has_english and not has_russian:
fixed_text = original_text.translate(EN_TO_RU)
logger.debug("Detected English text, translating to Russian")
else:
first_char = (
trimmed_text[0].lower()
if trimmed_text
else original_text[0].lower()
)
logger.debug(
f"Mixed/other characters detected, first char: {first_char}"
)
if first_char in string.ascii_lowercase:
fixed_text = original_text.translate(EN_TO_RU)
logger.debug("Using first char detection: English to Russian")
elif first_char in "йцукенгшщзхъфывапролджэячсмитьбюё":
fixed_text = original_text.translate(RU_TO_EN)
logger.debug("Using first char detection: Russian to English")
else:
fixed_text = original_text
logger.debug("No recognizable letters, returning as is")
if fixed_text != original_text:
logger.debug(
f"Text changed: {original_text[:30]}... → {fixed_text[:30]}..."
)
else:
logger.debug("Text unchanged")
if message.sender_id == reply.sender_id:
await reply.edit(fixed_text)
else:
await utils.answer(
message,
f"{self.strings('original_message').format(original=original_text)}\n"
f"{self.strings('fixed_message').format(fixed=fixed_text)}",
)
except Exception as e:
logger.error(f"Error during swap: {e}")
await utils.answer(message, self.strings("error"))

View File

@@ -1,17 +0,0 @@
Proprietary License Agreement
Copyright (c) 2024-29 Archquise
Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
5. By using the Software, you agree to be bound by the terms and conditions of this license.
For any inquiries or requests for permissions, please contact archquise@gmail.com.

View File

@@ -1,112 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Meme
# Description: Random memes
# Author: @hikka_mods
# Commands:
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Meme
# scope: Meme 0.0.1
# ---------------------------------------------------------------------------------
import logging
import random # noqa: F401
import aiohttp # noqa: F401
from bs4 import BeautifulSoup # noqa: F401
from .. import loader
logger = logging.getLogger(__name__)
@loader.tds
class MemesMod(loader.Module):
"""Random memes"""
strings = {
"name": "Memes",
"done": "☄️ Catch the meme",
"still": "🔄 Update",
"dell": "❌ Close",
}
strings_ru = {
"done": "☄️ Лови мем",
"still": "🔄 Обновить",
"dell": "❌ Закрыть",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://files.archquise.ru/HModsLibrary.py"
)
@loader.command(
ru_doc="",
en_doc="",
)
async def memescmd(self, message):
img = await self.hmodslib.get_random_image()
await self.inline.form(
text=self.strings("done"),
photo=img,
message=message,
reply_markup=[
[
{
"text": self.strings("still"),
"callback": self.ladno,
}
],
[
{
"text": self.strings("dell"),
"callback": self.dell,
}
],
],
silent=True,
)
async def ladno(self, call):
img = await self.hmodslib.get_random_image()
await call.edit(
text=self.strings("done"),
photo=img,
reply_markup=[
[
{
"text": self.strings("still"),
"callback": self.ladno,
}
],
[
{
"text": self.strings("dell"),
"callback": self.dell,
}
],
],
)
async def dell(self, call):
"""Callback button"""
await call.delete()

View File

@@ -1,305 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: MessageMonitor
# Description: Monitor messages for trigger words in all chats.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: MessageMonitor
# scope: MessageMonitor 0.0.1
# ---------------------------------------------------------------------------------
import logging
import re
from typing import List, Optional
from telethon.types import Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class MessageMonitor(loader.Module):
"""
Monitor messages for trigger words in all chats.
"""
strings = {
"name": "MessageMonitor",
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Trigger words have been set: <code>{}</code>",
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Trigger words have not been set",
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Target chat for notifications has been set",
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Target chat for notifications has not been set",
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring has started",
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Monitoring has stopped",
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring <b>{}</b>",
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.triggers word1 word2</code>",
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> enabled",
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> disabled",
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Ignored chats have been set: <code>{}</code>",
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Ignored chats have not been set",
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.ignore 123456789 -987654321</code> (chat IDs)",
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Reply to a message in the desired chat or specify its ID",
"monitoring_msg": (
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Trigger word detected!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
"<b>Chat:</b> <code>{}</code>\n"
"<b>User:</b> {}\n"
"<b>Link:</b> <a href='{}'>{}</a>\n\n"
"<b>Message:</b>\n{}"
),
}
strings_ru = {
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Триггерные слова установлены: <code>{}</code>",
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Триггерные слова не установлены",
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Целевой чат для уведомлений установлен",
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Целевой чат для уведомлений не установлен",
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг запущен",
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Мониторинг остановлен",
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг <b>{}</b>",
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.triggers слово1 слово2</code>",
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> включен",
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> выключен",
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Игнорируемые чаты установлены: <code>{}</code>",
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Игнорируемые чаты не установлены",
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.ignore 123456789 -987654321</code> (ID чатов)",
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Ответьте на сообщение в нужном чате или укажите его ID",
"monitoring_msg": (
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Обнаружено триггерное слово!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
"<b>Чат:</b> <code>{}</code>\n"
"<b>Пользователь:</b> {}\n"
"<b>Ссылка:</b> <a href='{}'>{}</a>\n\n"
"<b>Сообщение:</b>\n{}"
),
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"triggers",
[],
"List of trigger words to monitor",
validator=loader.validators.Series(),
),
loader.ConfigValue(
"target_chat",
None,
"Target chat ID for notifications",
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"ignore_chats",
[],
"List of chat IDs to ignore",
validator=loader.validators.Series(),
),
)
self._triggers: List[str] = []
self._target_chat: Optional[int] = None
self._ignore_chats: List[int] = []
self._compiled_patterns: List[re.Pattern] = []
async def client_ready(self, client, db):
"""Initialize module when client is ready"""
await self._update_config()
self.client = client
async def _update_config(self):
"""Update internal configuration and compile regex patterns"""
self._triggers = [trigger.lower() for trigger in self.config["triggers"]]
self._target_chat = self.config["target_chat"]
self._ignore_chats = [
int(chat_id)
for chat_id in self.config["ignore_chats"]
if str(chat_id).lstrip("-").isdigit()
]
self._compiled_patterns = [
re.compile(r"\b" + re.escape(trigger) + r"\b", re.IGNORECASE)
for trigger in self._triggers
]
@loader.command(
ru_doc="Показать статус мониторинга",
en_doc="Show monitoring status",
)
async def status(self, message: Message):
"""Show current monitoring status"""
status_text = (
self.strings["monitoring_status_on"]
if self._target_chat and self._triggers
else self.strings["monitoring_status_off"]
)
await utils.answer(
message, self.strings["monitoring_status"].format(status_text)
)
@loader.command(
ru_doc="Установить триггерные слова. Пример: .triggers слово1 слово2",
en_doc="Set trigger words. Example: .triggers word1 word2",
)
async def triggers(self, message: Message):
"""Set trigger words"""
args = utils.get_args(message)
if not args:
await utils.answer(message, self.strings["triggers_example"])
return
self._triggers = [arg.lower() for arg in args]
self.config["triggers"] = self._triggers
await self._update_config()
await utils.answer(
message, self.strings["triggers_set"].format(", ".join(self._triggers))
)
@loader.command(
ru_doc="Установить целевой чат для уведомлений. Ответьте на сообщение или укажите ID",
en_doc="Set target chat for notifications. Reply to a message or provide its ID",
)
async def settarget(self, message: Message):
"""Set target chat"""
args = utils.get_args_raw(message)
chat_id = None
if getattr(message, "is_reply", False):
reply_message = await message.get_reply_message()
if reply_message and hasattr(reply_message, "chat_id"):
chat_id = reply_message.chat_id
elif args and (args.isdigit() or (args.startswith("-") and args[1:].isdigit())):
chat_id = int(args)
if chat_id:
self.config["target_chat"] = chat_id
self._target_chat = chat_id
await utils.answer(message, self.strings["target_set"])
else:
await utils.answer(message, self.strings["no_reply"])
@loader.command(
ru_doc="Установить игнорируемые чаты. Укажите ID чатов через пробел.",
en_doc="Set ignored chats. Provide chat IDs separated by space.",
)
async def ignore(self, message: Message):
"""Set ignored chats"""
args = utils.get_args(message)
if not args:
await utils.answer(message, self.strings["ignore_example"])
return
valid_ids = []
for arg in args:
if arg.isdigit() or (arg.startswith("-") and arg[1:].isdigit()):
valid_ids.append(int(arg))
self.config["ignore_chats"] = valid_ids
await self._update_config()
if valid_ids:
await utils.answer(
message,
self.strings["ignore_set"].format(", ".join(map(str, valid_ids))),
)
else:
await utils.answer(message, self.strings["ignore_none"])
@loader.watcher(out=False, only_messages=True)
async def message_watcher(self, message: Message):
"""Watch for messages containing trigger words"""
if not self._target_chat or not self._triggers:
return
chat_id = getattr(message, "chat_id", None)
if chat_id and chat_id in self._ignore_chats:
logger.debug(f"Message in ignored chat: {chat_id}. Skipping monitoring.")
return
text = getattr(message, "text", "")
if not text:
return
found_triggers = [
trigger
for pattern, trigger in zip(self._compiled_patterns, self._triggers)
if pattern.search(text)
]
if not found_triggers:
return
try:
chat = await message.get_chat()
chat_title = getattr(
chat,
"title",
"Личные сообщения"
if getattr(message, "is_private", False)
else f"Чат с ID {chat_id}",
)
sender = await message.get_sender()
if sender:
sender_name = sender.first_name
if getattr(sender, "last_name", None):
sender_name += f" {sender.last_name}"
if not sender_name:
sender_name = getattr(
sender, "username", "Неизвестный пользователь"
)
else:
sender_name = "Неизвестный пользователь"
link = await self._get_message_link(message, sender)
await self.client.send_message(
self._target_chat,
self.strings["monitoring_msg"].format(
chat_title,
chat_id,
sender_name,
link,
text,
),
parse_mode="HTML",
)
logger.debug(
f"Sent notification about trigger word(s) {found_triggers} to chat {self._target_chat}"
)
except Exception as e:
logger.error(f"Error processing message: {e}")
async def _get_message_link(self, message: Message, sender) -> str:
"""Generate message link based on message type"""
message_id = message.id
if getattr(message, "to_id", None):
to_id_obj = getattr(message, "to_id")
if getattr(to_id_obj, "channel_id", None):
return f"https://t.me/c/{to_id_obj.channel_id}/{message_id}"
if (
getattr(message, "is_private", False)
and sender
and getattr(sender, "username", None)
):
return f"https://t.me/{sender.username}/{message_id}"
return f"https://t.me/c/{message_id}"

File diff suppressed because it is too large Load Diff

View File

@@ -1,126 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Music
# Description: Searches for music using Telegram music bots
# Author: @hikka_mods
# meta developer: @hikka_mods
# scope: Music
# scope: Music 0.0.2
# ---------------------------------------------------------------------------------
# Thanks to @murpizz for the search code yandex
import logging
from telethon.errors.rpcerrorlist import (
BotMethodInvalidError,
FloodWaitError,
MessageNotModifiedError,
)
from telethon.tl.types import Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class MusicMod(loader.Module):
strings = {
"name": "Music",
"no_query": "<emoji document_id=5337117114392127164>🤷‍♂</emoji> <b>Provide a search query!</b>",
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Searching...</b>",
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Possible match:</b>",
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Track not found: <code>{}</code></b>",
"usage": "<b>Usage:</b> <code>.music [track name]</code>",
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Error:</b> <code>{}</code>",
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>No results: <code>{}</code></b>",
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Wait {}s (Telegram limits)</b>",
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Bot error: <code>{}</code></b>",
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>No audio</b>",
"generic_result": "<emoji document_id=5336965905773504919></emoji> <b>Non-media result. Check the bot's chat</b>",
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Searching Yandex.Music...</b>",
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Track not found on Yandex.Music</b>",
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Error (Yandex): {}</b>",
}
strings_ru = {
"name": "Music",
"no_query": "<emoji document_id=5337117114392127164>🤷‍♂</emoji> <b>Укажите запрос!</b>",
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Поиск...</b>",
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Возможно, это оно:</b>",
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Трек не найден: <code>{}</code></b>",
"usage": "<b>Использование:</b> <code>.music [название трека]</code>",
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Ошибка:</b> <code>{}</code>",
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code></b>",
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram)</b>",
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Ошибка бота: <code>{}</code></b>",
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио</b>",
"generic_result": "<emoji document_id=5336965905773504919></emoji> <b>Немедийный результат. Проверьте чат с ботом</b>",
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Поиск в Яндекс.Музыке...</b>",
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке</b>",
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Ошибка (Яндекс): {}</b>",
}
def __init__(self):
self.murglar_bot = "@murglar_bot"
@loader.command(
ru_doc="Найти трек в Yandex.Music: `.music {название}`",
en_doc="Find a track in Yandex.Music: `.music yandex {name}`",
)
async def music(self, message):
args = utils.get_args(message)
if not args:
if reply := await message.get_reply_message():
await self._yafind(message, reply.raw_text.strip())
else:
await utils.answer(message, self.strings("usage", message))
return
await self._yafind(message, query=args)
async def _yafind(self, message: Message, query: str):
if not query:
return await utils.answer(message, self.strings("no_query", message))
await utils.answer(message, self.strings("yafind_searching", message))
try:
results = await message.client.inline_query(
self.murglar_bot, f"s:ynd {query}"
)
if not results:
return await utils.answer(
message, self.strings("yafind_not_found", message)
)
await results[0].click(
entity=message.chat_id,
hide_via=True,
reply_to=message.reply_to_msg_id if message.reply_to_msg_id else None,
)
await message.delete()
except Exception as e:
logger.exception("Yandex search error:")
await utils.answer(message, self.strings("yafind_error", message).format(e))

View File

@@ -1,184 +0,0 @@
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png">
<img src="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png" alt="H:Mods Logo" width="400">
</picture>
</div>
<h1 align="center">
<a href="https://github.com/archquise/H.Modules">
<img src="https://readme-typing-svg.herokuapp.com/?lines=H:Mods;Hikka+Modules+Collection&center=true&vCenter=true&width=500&height=80&color=00D9FF&center=true&vCenter=true">
</a>
</h1>
<p align="center">
<a href="https://github.com/hikariatama/Hikka">
<img src="https://img.shields.io/badge/Hikka-Userbot-blue?style=for-the-badge&logo=python" alt="Hikka">
</a>
<a href="https://github.com/coddrago/Heroku">
<img src="https://img.shields.io/badge/Heroku-Userbot-purple?style=for-the-badge&logo=heroku" alt="Heroku">
</a>
</p>
<p align="center">
<a href="https://t.me/hikka_mods">
<img src="https://img.shields.io/badge/Telegram%20Channel-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram Channel">
</a>
<a href="https://github.com/archquise/H.Modules">
<img src="https://img.shields.io/badge/GitHub-Repository-181717?style=for-the-badge&logo=github" alt="GitHub">
</a>
<a href="https://github.com/archquise/H.Modules/stargazers">
<img src="https://img.shields.io/github/stars/archquise/H.Modules?style=for-the-badge&logo=github&color=yellow" alt="Stars">
</a>
</p>
---
## 🚀 Quick Start
### 📦 Repository Installation (Recommended)
The easiest way to install and manage all modules:
```bash
.addrepo https://github.com/archquise/H.Modules/raw/main
```
After adding the repository, install any module:
```bash
.dlm <module_name>
```
### 🎯 Direct Installation
Install a specific module directly:
```bash
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/<module_name>.py
```
---
## 🛠️ Installation Guide
### Step 1: Add Repository
```bash
.addrepo https://github.com/archquise/H.Modules/raw/main
```
### Step 2: Install Modules
```bash
# Install specific module
.dlm TelegraphComic
# Install from direct URL
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/TelegraphComic.py
```
### Step 4: Configure Modules
Most modules have configurable settings:
```bash
# View module configuration
.config TelegraphComic
# Update configuration
.config TelegraphComic upload_service catbox
```
## 🐛 Troubleshooting
### Common Issues
<details>
<summary>❌ Module Installation Failed</summary>
**Solution:**
1. Check repository URL is correct
2. Ensure you have internet connection
3. Try restarting Hikka/Heroku
4. Use direct installation method
```bash
.dlm https://raw.githubusercontent.com/archquise/H.Modules/main/<module_name>.py
```
</details>
<details>
<summary>⚠️ Module Not Working</summary>
**Solution:**
2. Check configuration: `.cfg <module_name>`
3. Check dependencies are installed
4. Update the module: `.dlm <module_name>`
</details>
<details>
<summary>🔧 Configuration Issues</summary>
**Solution:**
1. Reset to defaults: `.cfg <module_name> reset`
2. Check syntax: `.cfg <module_name>`
3. View help: `.help <module_name>`
</details>
---
## 📜 License
<div align="center">
**🔒 Proprietary License**
This project is licensed under a proprietary license. By using this software, you agree to the following terms:
</div>
### 📋 License Terms
> **✅ What You CAN Do:**
> - Use the software for personal and non-commercial purposes
> - Install and use modules in Hikka/Heroku
> - Modify configuration settings
> - Report issues and suggest improvements
> **❌ What You CANNOT Do:**
> - Modify, alter, or change the software without explicit permission
> - Redistribute the software in original or modified form
> - Use for commercial purposes without permission
> - Remove copyright notices or attribution
### 📧 Contact
For inquiries or permission requests:
- **Email:** `archquise@gmail.com`
- **Telegram:** [@hikka_mods](https://t.me/hikka_mods)
---
<div align="center">
<picture>
<img src="https://raw.githubusercontent.com/trinib/trinib/82213791fa9ff58d3ca768ddd6de2489ec23ffca/images/footer.svg" alt="Footer" width="100%">
</picture>
<p>
<sub>Built with ❤️ for the Hikka/Heroku community</sub>
</p>
<p>
<a href="#top">⬆️ Back to Top</a>
</p>
</div>

View File

@@ -1,81 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VowelReplacer
# Description: Replaces vowel letters with ё
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: VowelReplacer
# scope: VowelReplacer 0.0.1
# ---------------------------------------------------------------------------------
import logging
from telethon.tl.types import Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class VowelReplacer(loader.Module):
"""Replaces vowel letters with ё"""
strings = {
"name": "Vowel Replacer",
"on": "✅ Vowel substitution for ё has been successfully enabled.",
"off": "🚫 Vowel substitution for ё is disabled.",
}
strings_ru = {
"on": "✅ Замена гласных на ё успешно включена.",
"off": "🚫 Замена гласных на ё отключена.",
}
async def client_ready(self, client, db):
self.db = db
self._client = client
self.enabled = self.db.get("vowel_replacer", "enabled", False)
@loader.command(
ru_doc="Включить или отключить замену гласных на ё.",
en_doc="Enable or disable vowel substitution for ё.",
)
async def vowelreplace(self, message):
self.enabled = not self.enabled
self.db.set("vowel_replacer", "enabled", self.enabled)
if self.enabled:
response = self.strings("on")
else:
response = self.strings("off")
await utils.answer(message, response)
async def watcher(self, message: Message):
"""Автоматическая замена гласных на ё при получении собственного сообщения."""
if self.enabled and message.out:
vowels = "аеёиоуыэюяАЕЁИОУЫЭЮЯ"
message_text = message.text
replaced_text = "".join(
"ё" if char in vowels else char for char in message_text
)
await message.edit(replaced_text)

View File

@@ -1,153 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: SMArchiver
# Description: unloads all messages from Favorites
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: SMArchiver
# scope: SMArchiver 0.0.1
# requires: zipfile
# ---------------------------------------------------------------------------------
import logging
import os
import zipfile
from datetime import datetime
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class SMArchiver(loader.Module):
"""unloads all messages from Favorites"""
strings = {
"name": "SMArchiver",
"archive_created": "🎉 Archive with messages has been successfully created: {filename}",
"no_messages": "⚠️ There are no messages in Saved Messages.",
"error": "❌ An error occurred: {error}",
"processing": "🛠️ Processing messages... Please wait.\n\nP.S: Be careful, if you have a lot of messages, you may get flooding, and if you have a lot of heavy files, the download will be slower than usual.",
}
strings_ru = {
"archive_created": "🎉 Архив с сообщениями успешно создан: {filename}",
"no_messages": "⚠️ В Сохраненных сообщениях нет сообщений.",
"error": "❌ Произошла ошибка: {error}",
"processing": "🛠️ Обработка сообщений... Пожалуйста, подождите.\n\nP.S: Будьте осторожны, если у вас много сообщений, вы можете получить флуд, а если у вас много тяжелых файлов, загрузка будет медленнее обычного.",
}
@loader.command(
ru_doc="выгружает все сообщения из Избранного / Saved Messages и собирает их в одном архиве.",
en_doc="downloads all messages from Favorites / Saved Messages and collects them in one archive.",
)
async def smdump(self, message):
await utils.answer(message, self.strings["processing"])
saved_messages = await message.client.get_messages("me", limit=None)
if not saved_messages:
await utils.answer(message, self.strings["no_messages"])
return
archive_path = await self.create_archive(saved_messages)
try:
await message.client.send_file(
message.chat_id,
archive_path,
caption=self.strings["archive_created"].format(
filename=os.path.basename(archive_path)
),
)
except Exception as e:
await utils.answer(message, self.strings["error"].format(error=str(e)))
finally:
self.cleanup(archive_path)
async def create_archive(self, saved_messages):
current_month = datetime.now().strftime("%B %Y")
archive_path = "saved_messages.zip"
with zipfile.ZipFile(archive_path, "w") as archive:
self.initialize_archive_structure(archive, current_month)
for msg in saved_messages:
await self.add_message_to_archive(msg, archive, current_month)
return archive_path
def initialize_archive_structure(self, archive, current_month):
month_folder = f"{current_month}/"
archive.writestr(month_folder, "")
message_folders = {
"Text Messages": f"{month_folder}Text Messages/",
"Voice Messages": f"{month_folder}Voice Messages/",
"Video Messages": f"{month_folder}Video Messages/",
"Videos": f"{month_folder}Videos/",
"Audios": f"{month_folder}Audios/",
"GIFs": f"{month_folder}GIFs/",
"Files": f"{month_folder}Files/",
}
for folder in message_folders.values():
archive.writestr(folder, "")
async def add_message_to_archive(self, msg, archive, current_month):
"""Обрабатывает отдельное сообщение и добавляет его в архив."""
if msg.message:
await self.add_text_message_to_archive(msg, archive, current_month)
if msg.media:
await self.add_media_to_archive(msg, archive, current_month)
async def add_text_message_to_archive(self, msg, archive, current_month):
timestamp = datetime.fromtimestamp(msg.date.timestamp()).strftime(
"%Y%m%d_%H%M%S"
)
safe_name = f"message_{timestamp}.txt"
archive.writestr(
os.path.join(f"{current_month}/Text Messages/", safe_name), msg.message
)
async def add_media_to_archive(self, msg, archive, current_month):
media_file = await msg.client.download_media(msg.media)
if media_file:
mime_type = (
msg.media.document.mime_type if hasattr(msg.media, "document") else None
)
folder = self.get_media_folder(mime_type, current_month)
archive.write(
media_file, os.path.join(folder, os.path.basename(media_file))
)
def get_media_folder(self, mime_type, current_month):
if mime_type:
if mime_type.startswith("audio/"):
return f"{current_month}/Audios/"
elif mime_type.startswith("video/"):
return f"{current_month}/Videos/"
elif mime_type.startswith("image/gif"):
return f"{current_month}/GIFs/"
return f"{current_month}/Files/"
def cleanup(self, archive_path):
if os.path.exists(archive_path):
os.remove(archive_path)

View File

@@ -1,386 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TaskManager
# Description: Manages tasks with Telegram commands and inline keyboards.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: TaskManager
# scope: TaskManager 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import datetime
import json
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
from .. import loader, utils
logger = logging.getLogger(__name__)
@dataclass
class Task:
"""Represents a task."""
description: str
due_date: Optional[datetime.datetime] = None
completed: bool = False
created_at: datetime.datetime = field(default_factory=datetime.datetime.now)
id: str = field(default_factory=lambda: f"{datetime.datetime.now().timestamp()}")
def to_dict(self) -> dict:
"""Convert task to dictionary for JSON serialization."""
return {
"id": self.id,
"description": self.description,
"due_date": self.due_date.isoformat() if self.due_date else None,
"completed": self.completed,
"created_at": self.created_at.isoformat(),
}
@classmethod
def from_dict(cls, data: dict) -> "Task":
"""Create task from dictionary."""
return cls(
id=data.get("id", f"{datetime.datetime.now().timestamp()}"),
description=data["description"],
due_date=datetime.datetime.fromisoformat(data["due_date"])
if data.get("due_date")
else None,
completed=data["completed"],
created_at=datetime.datetime.fromisoformat(data["created_at"]),
)
class TaskManager:
"""Manages tasks, storing them in a JSON file."""
def __init__(self, data_file: str):
self.data_file = Path(data_file)
self.tasks: Dict[int, List[Task]] = {}
self._lock = asyncio.Lock()
self.load_data()
def load_data(self):
"""Loads task data from the JSON file."""
if not self.data_file.exists():
self.tasks = {}
logger.info("Task data file not found. Starting empty.")
return
try:
with open(self.data_file, "r", encoding="utf-8") as f:
data = json.load(f)
self.tasks = {
int(user_id): [Task.from_dict(task) for task in task_list]
for user_id, task_list in data.items()
}
except (json.JSONDecodeError, KeyError, ValueError) as e:
logger.warning(f"Failed to load task data: {e}. Starting empty.")
self.tasks = {}
except Exception as e:
logger.error(f"Unexpected error loading task data: {e}")
self.tasks = {}
async def save_data(self):
"""Saves task data to the JSON file."""
async with self._lock:
try:
self.data_file.parent.mkdir(parents=True, exist_ok=True)
data = {
str(user_id): [task.to_dict() for task in task_list]
for user_id, task_list in self.tasks.items()
}
with open(self.data_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
except IOError as e:
logger.error(f"Failed to save task data: {e}")
except Exception as e:
logger.error(f"Unexpected error saving task data: {e}")
async def add_task(self, user_id: int, task: Task):
self.tasks.setdefault(user_id, []).append(task)
await self.save_data()
async def remove_task(self, user_id: int, index: int) -> bool:
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
del self.tasks[user_id][index]
await self.save_data()
return True
logger.warning(f"Invalid index for removal: {index}, user: {user_id}")
return False
async def complete_task(self, user_id: int, index: int) -> bool:
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
self.tasks[user_id][index].completed = True
await self.save_data()
return True
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
return False
def get_tasks(self, user_id: int, include_completed: bool = True) -> List[Task]:
tasks = self.tasks.get(user_id, [])
if not include_completed:
tasks = [task for task in tasks if not task.completed]
return tasks
async def clear_tasks(self, user_id: int) -> bool:
if user_id in self.tasks:
self.tasks[user_id] = []
await self.save_data()
return True
logger.info(f"No tasks to clear for user: {user_id}")
return False
def get_task(self, user_id: int, index: int) -> Optional[Task]:
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
return self.tasks[user_id][index]
return None
def get_overdue_tasks(self, user_id: int) -> List[Task]:
"""Get overdue tasks for a user."""
now = datetime.datetime.now()
return [
task
for task in self.get_tasks(user_id)
if task.due_date and task.due_date < now and not task.completed
]
async def update_task(self, user_id: int, index: int, **kwargs) -> bool:
"""Update task properties."""
task = self.get_task(user_id, index)
if not task:
return False
for key, value in kwargs.items():
if hasattr(task, key):
setattr(task, key, value)
await self.save_data()
return True
@loader.tds
class TaskManagerModule(loader.Module):
"""Manages tasks with Telegram commands and inline keyboards."""
strings = {
"name": "TaskManager",
"task_added": "<emoji document_id=5776375003280838798>✅</emoji> Task added.",
"task_removed": "<emoji document_id=5776375003280838798>✅</emoji> Task removed.",
"task_completed": "<emoji document_id=5776375003280838798>✅</emoji> Task completed.",
"task_not_found": "<emoji document_id=5778527486270770928>❌</emoji> Task not found.",
"no_tasks": "<emoji document_id=5956561916573782596>📄</emoji> No active tasks.",
"task_list": "<emoji document_id=5956561916573782596>📄</emoji> Your tasks:\n{}",
"invalid_index": "<emoji document_id=5778527486270770928>❌</emoji> Invalid index. Provide valid integer.",
"description_required": "<emoji document_id=5879813604068298387>❗️</emoji> Provide task description.",
"clear_confirmation": "⚠️ Delete all tasks?",
"tasks_cleared": "✅ All tasks deleted.",
"due_date_format": "<emoji document_id=5778527486270770928>❌</emoji> Invalid date. Use YYYY-MM-DD HH:MM.",
"task_info": "<emoji document_id=6028435952299413210></emoji> Task: {description}\n<emoji document_id=5967412305338568701>📅</emoji> Due: {due_date}\n<emoji document_id=5825794181183836432>✔️</emoji> Completed: {completed}\n<emoji document_id=5936170807716745162>🎛</emoji> Created: {created_at}",
"confirm_clear": "Confirm",
"cancel_clear": "Cancel",
"clear_cancelled": "❌ Deletion cancelled.",
"index_required": "⚠️ Provide task index.",
"clear_confirmation_text": "Are you sure you want to clear all tasks?",
"confirm": "Confirm",
"cancel": "Cancel",
}
strings_ru = {
"task_added": "<emoji document_id=5776375003280838798>✅</emoji> Задача добавлена.",
"task_removed": "<emoji document_id=5776375003280838798>✅</emoji> Задача удалена.",
"task_completed": "<emoji document_id=5776375003280838798>✅</emoji> Задача выполнена.",
"task_not_found": "<emoji document_id=5778527486270770928>❌</emoji> Задача не найдена.",
"no_tasks": "<emoji document_id=5956561916573782596>📄</emoji> Нет активных задач.",
"task_list": "<emoji document_id=5956561916573782596>📄</emoji> Ваши задачи:\n{}",
"invalid_index": "<emoji document_id=5778527486270770928>❌</emoji> Неверный индекс. Укажите целое число.",
"description_required": "<emoji document_id=5879813604068298387>❗️</emoji> Укажите описание задачи.",
"clear_confirmation": "⚠️ Удалить все задачи?",
"tasks_cleared": "Все задачи удалены.",
"due_date_format": "<emoji document_id=5778527486270770928>❌</emoji> Неверный формат даты. Используйте ГГГГ-ММ-ДД ЧЧ:ММ.",
"task_info": "<emoji document_id=6028435952299413210></emoji> Задача: {description}\n<emoji document_id=5967412305338568701>📅</emoji> Срок: {due_date}\n<emoji document_id=5825794181183836432>✔️</emoji> Выполнена: {completed}\n<emoji document_id=5936170807716745162>🎛</emoji> Создана: {created_at}",
"confirm_clear": "Подтвердить",
"cancel_clear": "Отменить",
"clear_cancelled": "❌ Удаление отменено.",
"index_required": "⚠️ Укажите индекс задачи.",
"clear_confirmation_text": "Вы уверены, что хотите удалить все задачи?",
"confirm": "Подтвердить",
"cancel": "Отменить",
}
def __init__(self):
self.task_manager: Optional[TaskManager] = None
async def client_ready(self, client, db):
data_dir = Path.cwd() / "data"
data_dir.mkdir(exist_ok=True)
self.task_manager = TaskManager(str(data_dir / "tasks.json"))
@loader.command(
ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>",
en_doc="Add task:\n.taskadd <description> | <date (opt)>",
)
async def taskadd(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("description_required"))
return
try:
description, due_date_str = (
args.split("|", 1) if "|" in args else (args, None)
)
description = description.strip()
due_date_str = due_date_str.strip() if due_date_str else None
due_date = (
datetime.datetime.fromisoformat(due_date_str) if due_date_str else None
)
except ValueError:
await utils.answer(message, self.strings("due_date_format"))
return
except Exception as e:
logger.error(f"Error adding task: {e}")
await utils.answer(
message, f"<emoji document_id=5778527486270770928>❌</emoji> Error: {e}"
)
return
task = Task(description=description, due_date=due_date)
await self.task_manager.add_task(message.sender_id, task)
await utils.answer(message, self.strings("task_added"))
@loader.command(ru_doc="[index] - удалить задачу", en_doc="[index] - remove task")
async def taskremove(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
if await self.task_manager.remove_task(message.sender_id, index):
await utils.answer(message, self.strings("task_removed"))
else:
await utils.answer(message, self.strings("task_not_found"))
@loader.command(
ru_doc="[index] - Завершите задачу", en_doc="[index] - Complete task"
)
async def taskcomplete(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
if await self.task_manager.complete_task(message.sender_id, index):
await utils.answer(message, self.strings("task_completed"))
else:
await utils.answer(message, self.strings("task_not_found"))
@loader.command(ru_doc="Список задач", en_doc="List tasks")
async def tasklist(self, message):
tasks = self.task_manager.get_tasks(message.sender_id)
if not tasks:
await utils.answer(message, self.strings("no_tasks"))
return
task_list_str = "\n".join(
[
f" {i + 1}. {'<emoji document_id=5776375003280838798>✅</emoji>' if task.completed else '<emoji document_id=5778527486270770928>❌</emoji>'} {task.description} (Due: {task.due_date.strftime('%Y-%m-%d %H:%M') if task.due_date else 'None'})"
for i, task in enumerate(tasks)
]
)
await utils.answer(message, self.strings("task_list").format(task_list_str))
@loader.command(
ru_doc="[index] - Посмотреть информацию о задаче",
en_doc="[index] - Show task info",
)
async def taskinfo(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
task = self.task_manager.get_task(message.sender_id, index)
if not task:
await utils.answer(message, self.strings("task_not_found"))
return
due_date_str = (
task.due_date.strftime("%Y-%m-%d %H:%M") if task.due_date else "None"
)
created_at_str = task.created_at.strftime("%Y-%m-%d %H:%M")
await utils.answer(
message,
self.strings("task_info").format(
description=task.description,
due_date=due_date_str,
completed="Yes" if task.completed else "No",
created_at=created_at_str,
),
)
@loader.command(ru_doc="Удалить все задачи", en_doc="Clear all tasks")
async def taskclear(self, message):
await self.inline.form(
text=self.strings("clear_confirmation_text"),
message=message,
reply_markup=[
[
{"text": self.strings("confirm"), "callback": self.clear_confirm},
{"text": self.strings("cancel"), "callback": self.clear_cancel},
]
],
silent=True,
)
async def clear_confirm(self, call):
"""Callback for confirming task clearing."""
if await self.task_manager.clear_tasks(call.from_user.id):
await call.edit(self.strings("tasks_cleared"))
else:
await call.edit(self.strings("no_tasks"))
async def clear_cancel(self, call):
"""Callback for canceling task clearing."""
await call.edit(self.strings("clear_cancelled"))

View File

@@ -1,200 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TelegramStatusCodes
# Description: Dictionary of telegram status codes
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api TelegramStatusCodes
# scope: Api TelegramStatusCodes 0.0.1
# ---------------------------------------------------------------------------------
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
responses = {
300: (
"⛔ SEE_OTHER",
"The request must be repeated, but directed to a different data center.",
),
400: (
"⛔ BAD_REQUEST",
"The query contains errors. In the event that a request was created using a form and contains user generated data, the user should be notified that the data must be corrected before the query is repeated.",
),
401: (
"⛔ UNAUTHORIZED",
"There was an unauthorized attempt to use functionality available only to authorized users.",
),
403: (
"⛔ FORBIDDEN",
"Privacy violation. For example, an attempt to write a message to someone who has blacklisted the current user.",
),
404: (
"⛔ NOT_FOUND",
"An attempt to invoke a non-existent object, such as a method",
),
406: (
"⛔ NOT_ACCEPTABLE",
"""
Similar to <b>400 BAD_REQUESTS</b>, but the app must display the error to the user a bit differently.
Do not display any visible error to the user when receiving the <b>rpc_error</b> constructor: instead, wait for an <a href="https://core.telegram.org/constructor/updateServiceNotification ">updateServiceNotification</a> update, and handle it as usual.
Basically, an <a href="https://core.telegram.org/constructor/updateServiceNotification"updateServiceNotification</a> <b>pop-up</b> update will be emitted independently (ie NOT as an <a href="https://core.telegram.org/type/Updates">Updates</a> constructor inside <b>rpc_result</b> but as a normal update) immediately after emission of a 406 <b>rpc_error</b>: the update will contain the actual localized error message to show to the user with a UI popup.
An exception to this is the <b>AUTH_KEY_DUPLICATED</b> error, which is only emitted if any of the non-media DC detects that an authorized session is sending requests in parallel from two separate TCP connections, from the same or different IP addresses.
Note that parallel connections are still allowed and actually recommended for media DCs.
Also note that by session we mean a logged-in session identified by an <a href="https://core.telegram.org/constructor/authorization">authorization</a> constructor, fetchable using <a href="https://core.telegram.org/method/account.getAuthorizations">account.getAuthorizations</a>, not an MTProto session.
If the client receives an <b>AUTH_KEY_DUPLICATED</b> error, the session was already invalidated by the server and the user must generate a new auth key and login again.""",
),
420: (
"⛔ FLOOD",
"The maximum allowed number of attempts to invoke the given method with the given input parameters has been exceeded. For example, in an attempt to request a large number of text messages (SMS) for the same phone number.",
),
500: (
"⛔ INTERNAL",
"""An internal server error occurred while a request was being processed; for example, there was a disruption while accessing a database or file storage.
If a client receives a 500 error, or you believe this error should not have occurred, please collect as much information as possible about the query and error and send it to the developers""",
),
}
responses_ru = {
300: (
"⛔ SEE_OTHER",
"Запрос должен быть повторен, но направлен в другой дата-центр.",
),
400: (
"⛔ BAD_REQUEST",
"Запрос содержит ошибки. В случае, если запрос был создан с помощью формы и содержит данные, введенные пользователем, пользователю следует сообщить, что данные должны быть исправлены перед повторным выполнением запроса.",
),
401: (
"⛔ UNAUTHORIZED",
"Была совершена неавторизованная попытка использовать функциональность, доступную только авторизованным пользователям.",
),
403: (
"⛔ FORBIDDEN",
"Нарушение конфиденциальности. Например, попытка написать сообщение пользователю, который добавил текущего пользователя в черный список.",
),
404: (
"⛔ NOT_FOUND",
"Попытка обращения к несуществующему объекту, например, к методу.",
),
406: (
"⛔ NOT_ACCEPTABLE",
"""
Аналогично <b>400 BAD_REQUESTS</b>, но приложение должно отображать ошибку пользователю немного иначе.
Не показывайте пользователю видимую ошибку при получении конструктора <b>rpc_error</b>: вместо этого дождитесь обновления <a href="https://core.telegram.org/constructor/updateServiceNotification">updateServiceNotification</a> и обработайте его как обычно.
По сути, обновление-всплывающее окно <b>updateServiceNotification</b> будет отправлено независимо (т.е. НЕ как конструктор <b>Updates</b> внутри <b>rpc_result</b>, а как обычное обновление) сразу после выдачи 406 <b>rpc_error</b>: обновление будет содержать актуальное локализованное сообщение об ошибке для показа пользователю в интерфейсе.
Исключением является ошибка <b>AUTH_KEY_DUPLICATED</b>, которая возникает только в том случае, если любой из не-медиа DC обнаруживает, что авторизованная сессия отправляет запросы параллельно из двух отдельных TCP-соединений с одного или разных IP-адресов.
Обратите внимание, что параллельные соединения по-прежнему разрешены и фактически рекомендуются для медиа-DC.
Также обратите внимание, что под сессией понимается авторизованная сессия, идентифицируемая конструктором <a href="https://core.telegram.org/constructor/authorization">authorization</a>, которую можно получить с помощью <a href="https://core.telegram.org/method/account.getAuthorizations">account.getAuthorizations</a>, а не сессия MTProto.
Если клиент получает ошибку <b>AUTH_KEY_DUPLICATED</b>, сессия уже была аннулирована сервером, и пользователю необходимо сгенерировать новый ключ авторизации и войти снова.""",
),
420: (
"⛔ FLOOD",
"Превышено максимально допустимое количество попыток вызова данного метода с указанными входными параметрами. Например, при попытке запросить большое количество текстовых сообщений (SMS) для одного и того же номера телефона.",
),
500: (
"⛔ INTERNAL",
"""Произошла внутренняя ошибка сервера во время обработки запроса; например, произошел сбой при доступе к базе данных или файловому хранилищу.
Если клиент получает ошибку 500 или вы считаете, что эта ошибка не должна была возникнуть, пожалуйста, соберите как можно больше информации о запросе и ошибке и отправьте ее разработчикам.""",
),
}
@loader.tds
class TelegramStatusCodes(loader.Module):
"""Dictionary of telegram status codes"""
strings = {
"name": "TelegramStatusCodes",
"args_incorrect": "<b>Incorrect args</b>",
"not_found": "<b>Code not found</b>",
"syntax_error": "<b>Args are mandatory</b>",
"scode": "<b>{} {}</b>\n⚜️ Code Description: <i>{}</i>",
}
strings_ru = {
"args_incorrect": "<b>Неверные аргументы</b>",
"not_found": "<b>Код не найден</b>",
"syntax_error": "<b>Аргументы обязательны</b>",
"_cmd_doc_httpsc": "<код> - Получить информацию о статус-коде",
"_cmd_doc_httpscs": "Показать все доступные коды",
"_cls_doc": "Словарь статус-кодов Telegram",
"scode": "<b>{} {}</b>\n⚜️ Описание статус-кода: <i>{}</i>",
}
async def client_ready(self, client, db):
self.ub_lang = self._db.get("hikka.translations", "lang")
if not self.ub_lang:
self.ub_lang = self._db.get("heroku.translations", "lang")
@loader.unrestricted
@loader.command(
ru_doc="<код состояния> - Получение информации о статус-коде",
en_doc="<statuscode> - Get status code info",
)
async def tgccmd(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, self.strings("syntax_error", message))
return
try:
if int(args[0]) not in responses:
await utils.answer(message, self.strings("not_found", message))
except ValueError:
await utils.answer(message, self.strings("args_incorrect", message))
if self.ub_lang != "ru":
await utils.answer(
message,
self.strings("scode", message).format(
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
),
)
else:
await utils.answer(
message,
self.strings("scode", message).format(
responses[int(args[0])][0], args[0], responses_ru[int(args[0])][1]
),
)
@loader.unrestricted
@loader.command(
ru_doc="Получите все статус-коды Telegram",
en_doc="Get all Telegram status codes",
)
async def tgcscmd(self, message):
await utils.answer(
message,
"\n".join(
[f"<b>{str(sc)}: {text}</b>" for sc, (text, _) in responses.items()]
),
)

View File

@@ -1,536 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2026-2029 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TelegraphComics
# Description: Create comics on Telegraph from ZIP/RAR archives
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# requires: aiohttp, zipfile, telegraph
# ---------------------------------------------------------------------------------
import asyncio
import logging
import os
import tempfile
from typing import List, Optional
import zipfile
import aiohttp
from telethon.types import MessageMediaDocument, Message
from telegraph import Telegraph
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class TelegraphComicMod(loader.Module):
"""Create comics on Telegraph from ZIP/CBZ/RAR archives"""
strings = {
"name": "TelegraphComic",
"invalid_args": "<emoji document_id=5388785832956016892>❌</emoji> Invalid arguments. Usage: .telegraphcomics <title> | <cover_url> (optional)",
"no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Reply to a message with ZIP/CBZ/RAR file",
"unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Unsupported file format. Only ZIP/CBZ/RAR files are supported",
"processing": "<emoji document_id=5256094480498436162>⏳</emoji> Processing archive...",
"uploading": "<emoji document_id=5854762571659218443>⏳</emoji> Uploading images...",
"creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Creating Telegraph article...",
"archive_extracted": "<emoji document_id=5854762571659218443>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>",
"upload_files": "<emoji document_id=5854762571659218443>📦</emoji> Upload image files:",
"creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Creating Telegraph article:",
"success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph article created!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Archive successfully extracted: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Upload image files:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Creating Telegraph article:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>',
"error": "<emoji document_id=5854929766146118183>❌</emoji> <b>Error:</b> {}",
"_cls_doc": "Create comics on Telegraph from ZIP/CBZ/RAR archives",
}
strings_ru = {
"_cls_doc": "Создание комиксов на Telegraph из ZIP/CBZ/RAR архивов",
"invalid_args": "<emoji document_id=5388785832956016892>❌</emoji> Неверные аргументы. Использование: .telegraphcomics <название> | <ссылкаа_обложку>(необязательно)",
"no_reply": "<emoji document_id=5388785832956016892>❌</emoji> Ответьте на сообщение с ZIP/CBZ/RAR файлом",
"unsupported_format": "<emoji document_id=5388785832956016892>❌</emoji> Неподдерживаемый формат. Только ZIP/CBZ/RAR файлы",
"processing": "<emoji document_id=5256094480498436162>⏳</emoji> Обработка архива...",
"uploading": "<emoji document_id=5256094480498436162>⏳</emoji> Загрузка изображений...",
"creating_article": "<emoji document_id=5854762571659218443>⏳</emoji> Создание Telegraph статьи...",
"archive_extracted": "<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>",
"upload_files": "<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:",
"creating_telegraph": "<emoji document_id=5854762571659218443>📝</emoji> Создание Telegraph статьи:",
"success": '<emoji document_id=5208422125924275090>✅</emoji> <b>Telegraph статья создана!</b>\n\n<emoji document_id=5256094480498436162>📦</emoji> Архив успешно распакован: <emoji document_id=5208422125924275090>✅</emoji>\n\n<emoji document_id=5256094480498436162>📦</emoji> Загрузка файлов изображений:\n{upload_status}\n\n<emoji document_id=5256230583717079814>📝</emoji> Создание Telegraph статьи:\n{article_status}\n\n<emoji document_id=5271604874419647061>🔗</emoji> <a href="{url}">{url}</a>',
"error": "<emoji document_id=5388785832956016892>❌</emoji> <b>Ошибка:</b> {}",
"available_services": "Доступные сервисы: catbox, bashupload, kappa, x0, tmpfiles, pomf",
"current_service": "Текущий сервис: {}",
"invalid_service": "❌ Неизвестный сервис: {}\n\n{}",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"upload_service",
"catbox",
"Upload service to use",
validator=loader.validators.Choice(
["catbox", "bashupload", "kappa", "x0", "tmpfiles", "pomf"]
),
),
loader.ConfigValue(
"short_name",
"HikkaMods",
"short name for the article",
validator=loader.validators.String(),
),
loader.ConfigValue(
"author_name",
"HikkaMods",
"nickname of the author of the article",
validator=loader.validators.String(),
),
loader.ConfigValue(
"author_url",
"https://t.me/hikka_mods",
"link to author",
validator=loader.validators.String(),
),
)
async def client_ready(self, client, db):
self.client = client
self.db = db
self.telegraph = Telegraph()
self.telegraph.create_account(
short_name=self.config["short_name"],
author_name=self.config["author_name"],
author_url=self.config["author_url"],
)
async def _upload_file_to_service(
self,
session: aiohttp.ClientSession,
url: str,
file_path: str,
field_name: str,
**extra_fields,
) -> Optional[str]:
"""Generic file upload method"""
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field(field_name, f, filename=os.path.basename(file_path))
for key, value in extra_fields.items():
data.add_field(key, value)
async with session.post(url, data=data) as response:
if response.status == 200:
result = await response.text()
return result.strip() if result else None
else:
logger.info(
f"Upload failed with status {response.status}: {await response.text()}"
)
except Exception as e:
logger.info(f"Error uploading to {url}: {e}")
return None
async def upload_to_catbox(self, file_path: str) -> Optional[str]:
"""Upload file to catbox.moe"""
async with aiohttp.ClientSession() as session:
result = await self._upload_file_to_service(
session,
"https://catbox.moe/user/api.php",
file_path,
"fileToUpload",
reqtype="fileupload",
)
return (
result
if result and result.startswith("https://files.catbox.moe/")
else None
)
async def upload_to_bashupload(self, file_path: str) -> Optional[str]:
"""Upload file to bashupload.com"""
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("file", f, filename=os.path.basename(file_path))
async with session.post(
"https://bashupload.com", data=data
) as response:
if response.status == 200:
result = await response.text()
lines = result.strip().split("\n")
for line in lines:
if line.startswith("https://"):
return line
if "wget" in result:
urls = [
line
for line in result.split("\n")
if "wget" in line
]
if urls:
parts = urls[0].split()
for part in parts:
if part.startswith("https://"):
return part
except Exception as e:
logger.info(f"Error uploading to bashupload: {e}")
return None
async def upload_to_kappa(self, file_path: str) -> Optional[str]:
"""Upload file to kappa.lol"""
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("file", f, filename=os.path.basename(file_path))
async with session.post(
"https://kappa.lol/api/upload", data=data
) as response:
if response.status == 200:
result = await response.json()
if result and "id" in result:
return f"https://kappa.lol/{result['id']}"
except Exception as e:
logger.info(f"Error uploading to kappa: {e}")
return None
async def upload_to_x0(self, file_path: str) -> Optional[str]:
"""Upload file to x0.at"""
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("file", f, filename=os.path.basename(file_path))
async with session.post("https://x0.at", data=data) as response:
if response.status == 200:
result = await response.text()
return (
result.strip()
if result and "https://" in result
else None
)
except Exception as e:
logger.info(f"Error uploading to x0: {e}")
return None
async def upload_to_tmpfiles(self, file_path: str) -> Optional[str]:
"""Upload file to tmpfiles.org"""
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("file", f, filename=os.path.basename(file_path))
async with session.post(
"https://tmpfiles.org/api/v1/upload", data=data
) as response:
if response.status == 200:
result = await response.json()
if result and "data" in result and "url" in result["data"]:
return result["data"]["url"]
except Exception as e:
logger.info(f"Error uploading to tmpfiles: {e}")
return None
async def upload_to_pomf(self, file_path: str) -> Optional[str]:
"""Upload file to pomf.lain.la"""
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("files[]", f, filename=os.path.basename(file_path))
async with session.post(
"https://pomf.lain.la/upload.php", data=data
) as response:
if response.status == 200:
result = await response.json()
if result and "files" in result and result["files"]:
return result["files"][0].get("url")
except Exception as e:
logger.info(f"Error uploading to pomf: {e}")
return None
async def upload_file(self, file_path: str) -> Optional[str]:
"""Upload file to selected service"""
service_name = self.config["upload_service"]
service_map = {
"catbox": self.upload_to_catbox,
"bashupload": self.upload_to_bashupload,
"kappa": self.upload_to_kappa,
"x0": self.upload_to_x0,
"tmpfiles": self.upload_to_tmpfiles,
"pomf": self.upload_to_pomf,
}
service_func = service_map.get(service_name)
if not service_func:
return await self.upload_to_catbox(file_path)
try:
result = await service_func(file_path)
return result
except Exception as e:
logger.error(f"Upload to {service_name} failed: {e}")
return None
async def extract_zip_archive(self, zip_path: str, extract_dir: str) -> List[str]:
"""Extract ZIP archive and return sorted list of image files"""
image_extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif"}
image_files = []
try:
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(extract_dir)
for root, _, files in os.walk(extract_dir):
for file in files:
if os.path.splitext(file)[1].lower() in image_extensions:
image_files.append(os.path.join(root, file))
image_files.sort(key=lambda x: os.path.basename(x).lower())
except Exception as e:
logger.info(f"Error extracting ZIP archive: {e}")
return image_files
async def create_telegraph_article(
self, title: str, image_urls: List[str], cover_url: Optional[str] = None
) -> Optional[str]:
"""Create Telegraph article with images"""
try:
if cover_url:
content = f'<img src="{cover_url}"/><br>'
content += "<br>".join(f'<img src="{url}"/>' for url in image_urls)
else:
content = "<br>".join(f'<img src="{url}"/>' for url in image_urls)
response = await asyncio.to_thread(
lambda: self.telegraph.create_page(
title=title,
html_content=content,
author_name=self.config["author_name"],
author_url=self.config["author_url"],
)
)
return response["url"]
except Exception as e:
logger.info(f"Error creating Telegraph article: {e}")
return None
async def _process_cover_url(self, cover_url: str) -> Optional[str]:
"""Process cover URL - handle Telegram message links and direct URLs"""
if not cover_url:
return None
cover_url = cover_url.strip()
if "t.me/" in cover_url and "/" in cover_url.split("t.me/")[1]:
try:
parts = cover_url.split("/")
if len(parts) >= 4:
chat_username = parts[-3]
message_id = int(parts[-1])
message = await self.client.get_messages(
chat_username, ids=message_id
)
if message and message.media:
media_path = await message.download_media()
if media_path:
uploaded_url = await self.upload_file(media_path)
os.remove(media_path)
return uploaded_url
except Exception as e:
logger.info(f"Error processing Telegram cover link: {e}")
return cover_url
return cover_url
async def _process_comics_request(self, message, create_func) -> None:
"""Common logic for processing comics requests"""
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not args or not reply:
await utils.answer(message, self.strings["invalid_args"])
return
if not isinstance(reply.media, MessageMediaDocument):
await utils.answer(message, self.strings["no_reply"])
return
if "|" in args:
title, cover_url = args.split("|", 1)
else:
title = args
cover_url = None
title = title.strip()
cover_url = (
await self._process_cover_url(cover_url.strip()) if cover_url else None
)
await utils.answer(message, self.strings["processing"])
file_path = await reply.download_media()
if not file_path:
await utils.answer(
message, self.strings["error"].format("Failed to download file")
)
return
try:
if not (file_path.lower().endswith((".zip", ".cbz"))):
await utils.answer(message, self.strings["unsupported_format"])
return
with tempfile.TemporaryDirectory() as temp_dir:
archive_path = file_path
if file_path.lower().endswith(".cbz"):
import shutil
zip_path = file_path[:-4] + ".zip"
shutil.copy2(file_path, zip_path)
archive_path = zip_path
image_files = await self.extract_zip_archive(archive_path, temp_dir)
if archive_path != file_path and os.path.exists(archive_path):
os.remove(archive_path)
if not image_files:
await utils.answer(
message,
self.strings["error"].format("No images found in archive"),
)
return
await utils.answer(message, self.strings["archive_extracted"])
await utils.answer(message, self.strings["uploading"])
upload_tasks = [self.upload_file(img_file) for img_file in image_files]
upload_results = await asyncio.gather(
*upload_tasks, return_exceptions=True
)
image_urls = []
failed_uploads = 0
upload_errors = []
upload_status_lines = []
for i, (img_file, result) in enumerate(
zip(image_files, upload_results)
):
filename = os.path.basename(img_file)
if isinstance(result, Exception):
error_str = str(result)
logger.info(f"Upload failed: {error_str}")
failed_uploads += 1
upload_errors.append(error_str)
upload_status_lines.append(
f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>"
)
elif result and "https://" in result:
image_urls.append(result)
upload_status_lines.append(
f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>"
)
else:
failed_uploads += 1
upload_errors.append("Invalid response from upload service")
upload_status_lines.append(
f"{filename} - <emoji document_id=5388785832956016892>❌</emoji>"
)
if not image_urls:
error_details = []
error_details.append(f"Failed uploads: {failed_uploads}")
if upload_errors:
unique_errors = list(set(upload_errors))[:3]
error_details.append("Errors: " + "; ".join(unique_errors))
error_msg = " | ".join(error_details)
await utils.answer(
message,
self.strings["error"].format(error_msg),
)
return
upload_status = (
self.strings["upload_files"] + "\n" + "\n".join(upload_status_lines)
)
await utils.answer(message, upload_status)
await utils.answer(message, self.strings["creating_article"])
article_url = await create_func(title, image_urls, cover_url)
if article_url:
article_status_lines = []
for i, (img_file, url) in enumerate(zip(image_files, image_urls)):
filename = os.path.basename(img_file)
article_status_lines.append(
f"{filename} - <emoji document_id=5208422125924275090>✅</emoji>"
)
upload_status = "\n".join(upload_status_lines)
article_status = "\n".join(article_status_lines)
await utils.answer(
message,
self.strings["success"].format(
upload_status=upload_status,
article_status=article_status,
url=article_url,
),
)
else:
await utils.answer(
message,
self.strings["error"].format("Failed to create article"),
)
except Exception as e:
await utils.answer(
message,
self.strings["error"].format(f"Processing error: {e}"),
)
finally:
if os.path.exists(file_path):
os.remove(file_path)
@loader.command(
ru_doc="Создать комикс на Telegraph из ZIP/CBZ/RAR архива\nАргументы: <название> | <ссылкаа_обложку>(необязательно)\nИспользование: .telegraphcomics <title> | <cover_url>(optional)",
en_doc="Create Telegraph comic from ZIP/CBZ/RAR archive\nArguments: <title> | <cover_url>(optional)\nUsage: .telegraphcomics <title> | <cover_url>(optional)",
)
async def telegraphcomicscmd(self, message):
await self._process_comics_request(message, self.create_telegraph_article)

View File

@@ -1,80 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Text2File
# Description: Module for convertation your text to file
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Text2File
# scope: Text2File 0.0.1
# ---------------------------------------------------------------------------------
import io
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class Text2File(loader.Module):
"""Module for convertation your text to file"""
strings = {
"name": "Text2File",
"no_args": "Don't have any args! Use .ttf text/code",
"cfg_name": "You can change the extension and file name",
}
strings_ru = {
"no_args": "Недостаточно аргументов! Используйте: .ttf текст/код",
"cfg_name": "Вы можете выбрать расширение и название для файла",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"name",
"file.txt",
lambda: self.strings("cfg_name"),
),
)
@loader.command(
ru_doc="Создать файл с вашим текстом или кодом",
en_doc="Create a file with your text or code",
)
async def ttfcmd(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("no_args"))
return
text = args
by = io.BytesIO(text.encode("utf-8"))
by.name = self.config["name"]
await utils.send_file(
message.chat_id,
by,
caption=None,
reply_to=message.reply_to_msg_id,
)

View File

@@ -1,282 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TikTokDownloader
# Description: A module for downloading videos and photos from TikTok without watermark
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api TikTokDownloader
# scope: Api TikTokDownloader 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
from dataclasses import dataclass
import logging
import os
import re
from typing import List, Optional, Union
from urllib.parse import urljoin
import aiohttp
from tqdm import tqdm
from .. import loader, utils
logger = logging.getLogger(__name__)
@dataclass
class data:
dir_name: str
media: Union[str, List[str]]
type: str
class TikTok:
def __init__(self, host: Optional[str] = None):
self.headers = {
"User-Agent": (
"Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) "
"AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 "
"Mobile/7B334b Safari/531.21.10"
)
}
self.host = host or "https://www.tikwm.com/"
self.session = aiohttp.ClientSession()
self.data_endpoint = "api"
self.search_videos_keyword_endpoint = "api/feed/search"
self.search_videos_hashtag_endpoint = "api/challenge/search"
self.link = None
self.result = None
self.logger = logging.getLogger("damirtag-TikTok")
handler = logging.StreamHandler()
formatter = logging.Formatter(
"[damirtag-TikTok:%(funcName)s]: %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
async def close_session(self):
await self.session.close()
async def __ensure_data(self, link: str):
if self.link != link:
self.link = link
self.result = await self._fetch_data(link)
self.logger.info("Successfully ensured data from the link")
async def __get_images(self, download_dir: Optional[str] = None):
download_dir = download_dir or self.result["id"]
os.makedirs(download_dir, exist_ok=True)
tasks = [
self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg"))
for i, url in enumerate(self.result["images"])
]
await asyncio.gather(*tasks)
self.logger.info(f"Images - Downloaded and saved photos to {download_dir}")
return data(
dir_name=download_dir,
media=[
os.path.join(download_dir, f"image_{i + 1}.jpg")
for i in range(len(self.result["images"]))
],
type="images",
)
async def __get_video(self, video_filename: Optional[str] = None, hd: bool = False):
video_url = self.result["hdplay"] if hd else self.result["play"]
video_filename = video_filename or f"{self.result['id']}.mp4"
async with self.session.get(video_url) as response:
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
with open(video_filename, "wb") as file:
with tqdm(
total=total_size, unit="B", unit_scale=True, desc=video_filename
) as pbar:
async for chunk in response.content.iter_any():
file.write(chunk)
pbar.update(len(chunk))
self.logger.info(f"Video - Downloaded and saved video as {video_filename}")
return data(
dir_name=os.path.dirname(video_filename), media=video_filename, type="video"
)
async def _fetch_data(self, link: str) -> dict:
url = self.get_url(link)
params = {"url": url, "hd": 1}
return await self._make_request(self.data_endpoint, params=params)
async def _download_file(self, url: str, path: str):
async with self.session.get(url) as response:
response.raise_for_status()
with open(path, "wb") as file:
while chunk := await response.content.read(1024):
file.write(chunk)
async def download_sound(
self,
link: str,
audio_filename: Optional[str] = None,
audio_ext: Optional[str] = ".mp3",
):
await self.__ensure_data(link)
if not audio_filename:
audio_filename = f"{self.result['music_info']['title']}{audio_ext}"
else:
audio_filename += audio_ext
await self._download_file(self.result["music_info"]["play"], audio_filename)
self.logger.info(f"Sound - Downloaded and saved sound as {audio_filename}")
return audio_filename
async def download(
self, link: str, video_filename: Optional[str] = None, hd: bool = True
):
await self.__ensure_data(link)
if "images" in self.result:
return await self.__get_images(video_filename)
if "hdplay" in self.result or "play" in self.result:
return await self.__get_video(video_filename, hd)
self.logger.error("No downloadable content found in the provided link.")
raise Exception("No downloadable content found in the provided link.")
async def _make_request(self, endpoint: str, params: dict) -> dict:
async with self.session.get(
urljoin(self.host, endpoint), params=params, headers=self.headers
) as response:
response.raise_for_status()
data = await response.json()
return data.get("data", {})
@staticmethod
def get_url(text: str) -> Optional[str]:
urls = re.findall(r"http[s]?://[^\s]+", text)
return urls[0] if urls else None
@staticmethod
def _get_video_link(unique_id: str, aweme_id: str) -> str:
return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}"
@staticmethod
def _get_uploader_link(unique_id: str) -> str:
return f"https://www.tiktok.com/@{unique_id}"
@loader.tds
class TikTokDownloader(loader.Module):
"""TikTok Downloader module"""
strings = {
"name": "TikTokDownloader",
"downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Downloading…</b>",
"success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The photo(s) has/have been successfully downloaded!</b>!",
"success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The video has been successfully downloaded!</b>",
"success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The sound has been successfully downloaded!</b>",
"error": "Error occurred while downloading.\n{}",
}
strings_ru = {
"downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Загружаем…</b>",
"success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Фотография(-и) была(-и) успешно загружены!</b>!",
"success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Видео было успешно загружено!</b>",
"success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Звук был успешно загружен!</b>",
"error": "Во время загрузки произошла ошибка.\n{}",
}
@loader.command(
ru_doc="Скачать звук с TikTok",
en_doc="Download sound from TikTok",
)
async def ttsound(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, "Please provide a TikTok URL.")
return
url = args[0]
await utils.answer(message, self.strings("downloading"))
tiktok_downloader = TikTok()
try:
download_result = await tiktok_downloader.download_sound(url)
await message.client.send_file(
message.to_id, download_result, caption=self.strings("success_sound")
)
await message.delete()
except Exception as e:
await utils.answer(
message,
f"{self.strings('error').format(e)}\n Убедитесь, что ссылка ведет именно на видео или фото с нужным звуком, прямая ссылка на звук не сработает!",
)
finally:
await tiktok_downloader.close_session()
@loader.command(
ru_doc="Скачать видео или фото с TikTok",
en_doc="Download videos or photos from TikTok",
)
async def tt(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, "Please provide a TikTok URL.")
return
url = args[0]
await utils.answer(message, self.strings("downloading"))
tiktok_downloader = TikTok()
try:
download_result = await tiktok_downloader.download(url)
if download_result.type == "video":
await message.client.send_file(
message.to_id,
download_result.media,
caption=self.strings("success_video"),
)
await message.delete()
elif download_result.type == "images":
await message.client.send_file(
message.to_id,
download_result.media,
caption=self.strings("success_photo"),
)
await message.delete()
except Exception as e:
await utils.answer(message, self.strings("error").format(e))
finally:
await tiktok_downloader.close_session()

View File

@@ -1,538 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TimedEmojiStatus
# Description: Temporary emoji status with auto-revert
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: TimedEmojiStatus
# scope: TimedEmojiStatus 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import logging
import re
import time
from datetime import datetime, timedelta
from typing import Dict, Optional
from telethon.tl.functions.account import UpdateEmojiStatusRequest
from telethon.tl.types import EmojiStatus, MessageEntityCustomEmoji, Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class TimedEmojiStatusMod(loader.Module):
"""Temporary emoji status with auto-revert using scheduler"""
strings = {
"name": "TimedEmojiStatus",
"no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify emoji or emoji document_id</b>",
"no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify time (ex: 1h, 30m, 2d)</b>",
"invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Invalid time format (ex: 30m, 2h, 1d, 1w)</b>",
"status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status set:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>For:</b> {} ({})",
"status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status updated: {}</b>",
"no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>No active status</b>",
"status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status removed</b>",
"current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Active status:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>Until:</b> {} ({})",
"no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Premium required for emoji status</b>",
"error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Error: {}</b>",
}
strings_ru = {
"no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите эмодзи или document_id</b>",
"no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите время (напр: 1h, 30m, 2d)</b>",
"invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Неверный формат времени (напр: 30m, 2h, 1d, 1w)</b>",
"status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус установлен:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>На:</b> {} ({})",
"status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус обновлён: {}</b>",
"no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>Нет активного статуса</b>",
"status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус удалён</b>",
"current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Активный статус:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>До:</b> {} ({})",
"no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Требуется Premium для эмодзи статуса</b>",
"error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Ошибка: {}</b>",
}
def __init__(self):
self.status_data: Dict[int, Dict] = {}
self.scheduler_tasks: Dict[int, asyncio.Task] = {}
async def client_ready(self, client, db):
self._client = client
self._db = db
if not self._client.hikka_me.premium:
logger.warning("Premium required for emoji status functionality")
await self._restore_active_statuses()
async def _restore_active_statuses(self):
"""Restore and reschedule active statuses after restart"""
saved = self._db.get(__name__, "statuses", {})
current_time = time.time()
for user_id, data in saved.items():
end_time = data.get("end_time", 0)
if end_time > current_time:
remaining_time = end_time - current_time
logger.info(
f"Restoring status for user {user_id}, remaining: {remaining_time}s"
)
task = asyncio.create_task(
self._schedule_revert_sleep(user_id, remaining_time)
)
self.scheduler_tasks[user_id] = task
self.status_data[user_id] = data
else:
logger.info(f"Removing expired status for user {user_id}")
del saved[user_id]
if saved != self._db.get(__name__, "statuses", {}):
self._db.set(__name__, "statuses", saved)
def _parse_time(self, time_str: str) -> Optional[timedelta]:
"""Parse time string like 1h30m, 2d, 1w, 1mth"""
pattern = r"(\d+)([smhdwmth]+)"
matches = re.findall(pattern, time_str.lower())
if not matches:
return None
total_seconds = 0
for value, unit in matches:
value = int(value)
if unit == "s":
total_seconds += value
elif unit == "m":
total_seconds += value * 60
elif unit == "h":
total_seconds += value * 3600
elif unit == "d":
total_seconds += value * 86400
elif unit == "w":
total_seconds += value * 604800
elif unit in ["mth", "month"]:
total_seconds += value * 2592000 # 30 days
return timedelta(seconds=total_seconds)
def _format_time(self, td: timedelta) -> str:
"""Format timedelta to human readable string"""
total_days = td.days
months = total_days // 30
remaining_days = total_days % 30
if months > 0:
if remaining_days > 0:
return f"{months}mth {remaining_days}d"
return f"{months}mth"
elif total_days > 0:
return f"{total_days}d {td.seconds // 3600}h"
elif td.seconds >= 3600:
return f"{td.seconds // 3600}h {(td.seconds % 3600) // 60}m"
else:
return f"{td.seconds // 60}m"
def _extract_document_id(self, emoji_input: str) -> Optional[int]:
"""Extract document_id from emoji string"""
pattern = r"<emoji\s+document_id=(\d+)>.*?</emoji>"
match = re.search(pattern, emoji_input)
if match:
return int(match.group(1))
if emoji_input.isdigit():
return int(emoji_input)
return None
def _extract_document_id_from_entities(self, message: Message) -> Optional[int]:
"""Extract document_id from message entities"""
if not message.entities:
return None
for entity in message.entities:
if isinstance(entity, MessageEntityCustomEmoji):
return entity.document_id
return None
def _safe_emoji_display(
self, emoji_str: str, document_id: Optional[int] = None
) -> str:
"""Safely display emoji without causing errors"""
if not emoji_str:
return ""
if document_id:
return f"[Custom Emoji ID: {document_id}]"
if emoji_str.isdigit():
return f"[Custom Emoji ID: {emoji_str}]"
if "<emoji document_id=" in emoji_str:
import re
match = re.search(r'document_id=(\d+)', emoji_str)
if match:
return f"[Custom Emoji ID: {match.group(1)}]"
return "[Custom Emoji]"
if len(emoji_str) == 1 or (
len(emoji_str) <= 4 and all(ord(c) >= 0x1F000 for c in emoji_str)
):
return emoji_str
return emoji_str[:10] + "..." if len(emoji_str) > 10 else emoji_str
async def _set_emoji_status(
self, emoji_input: str, until: datetime | None = None, message: Message = None
) -> tuple[bool, Optional[int]]:
"""Set emoji status (requires Premium). Returns (success, document_id)"""
try:
logger.info(f"Setting emoji status for: {emoji_input}")
if not self._client.hikka_me.premium:
logger.warning("Premium required for emoji status")
return False, None
if not emoji_input:
logger.info("Removing emoji status")
await self._client(UpdateEmojiStatusRequest(emoji_status=None))
return True, None
document_id = None
if message:
document_id = self._extract_document_id_from_entities(message)
if document_id:
logger.info(
f"Found document_id from message entities: {document_id}"
)
if not document_id:
document_id = self._extract_document_id(emoji_input)
if document_id:
logger.info(f"Extracted document_id from text: {document_id}")
if not document_id:
try:
logger.info("Trying to get document_id from test message")
test_msg = await self._client.send_message("me", emoji_input)
document_id = self._extract_document_id_from_entities(test_msg)
await self._client.delete_messages("me", [test_msg.id])
if document_id:
logger.info(
f"Found document_id from test message: {document_id}"
)
else:
logger.warning("No document_id found in test message")
except Exception as e:
logger.error(f"Error getting document_id from test message: {e}")
if document_id:
try:
emoji_status = EmojiStatus(document_id=document_id, until=until)
await self._client(
UpdateEmojiStatusRequest(emoji_status=emoji_status)
)
logger.info(
f"Status set successfully with document_id: {document_id}"
)
return True, document_id
except Exception as e:
logger.error(f"Error setting status: {e}")
if "PREMIUM" in str(e).upper():
return False, None
return False, None
logger.warning("No document_id found, all methods failed")
return False, None
except Exception as e:
logger.error(f"General error setting emoji status: {e}")
return False, None
async def _revert_status(self, user_id: int):
"""Revert status to final emoji or remove"""
logger.info(f"Starting revert status for user {user_id}")
if user_id in self.scheduler_tasks:
del self.scheduler_tasks[user_id]
if user_id in self.status_data:
data = self.status_data[user_id]
final_emoji = data.get("final_emoji", "")
final_doc_id = data.get("final_doc_id")
logger.info(
f"Reverting status for user {user_id} to: '{final_emoji}' (saved doc_id: {final_doc_id})"
)
try:
if final_emoji and final_doc_id:
logger.info(
f"Setting final emoji using saved document_id: {final_doc_id}"
)
try:
emoji_status = EmojiStatus(document_id=final_doc_id)
await self._client(
UpdateEmojiStatusRequest(emoji_status=emoji_status)
)
logger.info(
f"Successfully set final emoji with document_id: {final_doc_id}"
)
except Exception as e:
logger.error(f"Error setting final emoji with document_id: {e}")
success, _ = await self._set_emoji_status(final_emoji)
if not success:
await self._set_emoji_status("")
elif final_emoji:
logger.info(f"Attempting to set final emoji: '{final_emoji}'")
success, final_doc_id = await self._set_emoji_status(final_emoji)
if success:
logger.info(
f"Successfully reverted to final emoji: '{final_emoji}' (doc_id: {final_doc_id})"
)
else:
logger.warning(
f"Failed to set final emoji '{final_emoji}', removing status instead"
)
await self._set_emoji_status("")
else:
logger.info("No final emoji specified, removing status")
await self._set_emoji_status("")
except Exception as e:
logger.error(f"Error reverting status: {e}")
try:
await self._set_emoji_status("")
except Exception as e2:
logger.error(f"Error removing status: {e2}")
logger.info(f"Removing status data for user {user_id}")
del self.status_data[user_id]
saved = self._db.get(__name__, "statuses", {})
if user_id in saved:
logger.info(f"Removing saved status for user {user_id}")
del saved[user_id]
self._db.set(__name__, "statuses", saved)
logger.info(f"Revert status completed for user {user_id}")
async def _schedule_revert_sleep(self, user_id: int, delay: float):
"""Schedule status revert using asyncio.sleep"""
try:
logger.info(f"Scheduling revert for user {user_id} in {delay} seconds")
await asyncio.sleep(delay)
await self._revert_status(user_id)
except asyncio.CancelledError:
logger.info(f"Revert task cancelled for user {user_id}")
except Exception as e:
logger.error(f"Error in scheduled revert for user {user_id}: {e}")
async def _schedule_revert(self, user_id: int, data: Dict):
"""Schedule status revert"""
end_time = data.get("end_time", 0)
delay = max(0, end_time - time.time())
self.status_data[user_id] = data
await self._schedule_revert_sleep(user_id, delay)
@loader.command(
ru_doc="<время> <эмодзи/document_id> [финальный_эмодзи/document_id] - установить временный статус",
en_doc="<time> <emoji/document_id> [final_emoji/document_id] - set temporary status",
)
async def setmoji(self, message: Message):
"""Set timed emoji status"""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_time"])
parts = args.split(maxsplit=2)
if len(parts) < 2:
return await utils.answer(message, self.strings["no_emoji"])
time_str, initial_emoji = parts[0], parts[1]
final_emoji = parts[2] if len(parts) > 2 else ""
td = self._parse_time(time_str)
if not td:
return await utils.answer(message, self.strings["invalid_time"])
if message.sender_id in self.scheduler_tasks:
self.scheduler_tasks[message.sender_id].cancel()
del self.scheduler_tasks[message.sender_id]
try:
success, initial_doc_id = await self._set_emoji_status(
initial_emoji, message=message
)
if not success:
return await utils.answer(message, self.strings["no_premium"])
except Exception as e:
return await utils.answer(message, self.strings["error"].format(str(e)))
final_doc_id = None
if final_emoji:
try:
final_doc_id = self._extract_document_id(final_emoji)
if not final_doc_id:
if message and len(parts) > 2:
emoji_entities = [
e
for e in message.entities
if isinstance(e, MessageEntityCustomEmoji)
]
if len(emoji_entities) >= 2:
final_doc_id = emoji_entities[1].document_id
if not final_doc_id:
try:
test_msg = await self._client.send_message("me", final_emoji)
final_doc_id = self._extract_document_id_from_entities(test_msg)
await self._client.delete_messages("me", [test_msg.id])
except Exception as e:
logger.warning(
f"Could not get document_id for final emoji: {e}"
)
if final_doc_id:
logger.info(f"Final emoji document_id: {final_doc_id}")
else:
logger.warning(
f"Could not resolve document_id for final emoji: {final_emoji}"
)
except Exception as e:
logger.warning(f"Error getting final emoji document_id: {e}")
end_time = time.time() + td.total_seconds()
user_id = message.sender_id
data = {
"initial_emoji": initial_emoji,
"final_emoji": final_emoji,
"initial_doc_id": initial_doc_id,
"final_doc_id": final_doc_id,
"end_time": end_time,
"set_time": time.time(),
}
self.status_data[user_id] = data
saved = self._db.get(__name__, "statuses", {})
saved[user_id] = data
self._db.set(__name__, "statuses", saved)
task = asyncio.create_task(
self._schedule_revert_sleep(user_id, td.total_seconds())
)
self.scheduler_tasks[user_id] = task
end_dt = datetime.fromtimestamp(end_time)
time_str = self._format_time(td)
logger.info(
f"Display formatting - initial: '{initial_emoji}' (doc_id: {initial_doc_id}), final: '{final_emoji}' (doc_id: {final_doc_id})"
)
current_display = self._safe_emoji_display(initial_emoji, initial_doc_id)
final_display = (
self._safe_emoji_display(final_emoji, final_doc_id)
if final_emoji
else "❌ (удалить)"
)
logger.info(
f"Display results - current: '{current_display}', final: '{final_display}'"
)
await utils.answer(
message,
self.strings["status_set"].format(
current_display, final_display, time_str, f"{end_dt:%H:%M:%S}"
),
)
@loader.command(ru_doc="Показать текущий статус", en_doc="Show current status")
async def showmoji(self, message: Message):
"""Show current emoji status"""
user_id = message.sender_id
if user_id not in self.status_data:
return await utils.answer(message, self.strings["no_status"])
data = self.status_data[user_id]
end_time = data.get("end_time", 0)
initial_emoji = data.get("initial_emoji", "")
final_emoji = data.get("final_emoji", "")
initial_doc_id = data.get("initial_doc_id")
final_doc_id = data.get("final_doc_id")
if end_time <= time.time():
return await utils.answer(message, self.strings["no_status"])
end_dt = datetime.fromtimestamp(end_time)
remaining = timedelta(seconds=end_time - time.time())
remaining_str = self._format_time(remaining)
current_display = self._safe_emoji_display(initial_emoji, initial_doc_id)
final_display = (
self._safe_emoji_display(final_emoji, final_doc_id)
if final_emoji
else "❌ (удалить)"
)
await utils.answer(
message,
self.strings["current_status"].format(
current_display, final_display, f"{end_dt:%H:%M:%S}", remaining_str
),
)
@loader.command(ru_doc="Удалить статус", en_doc="Remove status")
async def removemoji(self, message: Message):
"""Remove emoji status"""
user_id = message.sender_id
if user_id not in self.status_data:
return await utils.answer(message, self.strings["no_status"])
if user_id in self.scheduler_tasks:
self.scheduler_tasks[user_id].cancel()
del self.scheduler_tasks[user_id]
await self._revert_status(user_id)
await utils.answer(message, self.strings["status_removed"])
async def on_unload(self):
"""Cancel all scheduled tasks on unload"""
for task in self.scheduler_tasks.values():
task.cancel()
self.scheduler_tasks.clear()

View File

@@ -1,963 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: UserbotAvast
# Description: A module for checking modules for security.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: UserbotAvast
# scope: UserbotAvast 0.0.1
# ---------------------------------------------------------------------------------
import ast
import base64
import logging
import re
import zlib
import requests
from .. import loader, utils
logger = logging.getLogger(__name__)
try:
import g4f
G4F_AVAILABLE = True
except ImportError:
G4F_AVAILABLE = False
logger.warning("g4f is not installed. AI analysis will be disabled.")
class SecurityAnalyzer:
"""
Продвинутый анализатор безопасности Python-кода с эвристическим анализом.
"""
SECURITY_KEYWORDS = {
"critical": [
{
"keyword": "DeleteAccountRequest",
"description": "Удаление аккаунта",
"relevance": "Высокая",
},
{
"keyword": "ResetAuthorizationRequest",
"description": "Сброс авторизации",
"relevance": "Высокая",
},
{
"keyword": "client.export_session_string",
"description": "Экспорт сессии",
"relevance": "Высокая",
},
{
"keyword": "edit_2fa",
"description": "Изменение 2FA",
"relevance": "Высокая",
},
{
"keyword": "os.system",
"description": "Системные команды",
"relevance": "Высокая",
},
{
"keyword": "subprocess.Popen",
"description": "Внешние процессы",
"relevance": "Высокая",
},
{
"keyword": "eval",
"description": "Выполнение кода (eval)",
"relevance": "Критическая",
},
{
"keyword": "exec",
"description": "Выполнение кода (exec)",
"relevance": "Критическая",
},
{
"keyword": "MessagePacker.append",
"description": "Патч MessagePacker",
"relevance": "Средняя",
},
{
"keyword": "MessagePacker.extend",
"description": "Патч MessagePacker",
"relevance": "Средняя",
},
{
"keyword": "Scrypt",
"description": "Класс Scrypt (подозрительно)",
"relevance": "Высокая",
},
{
"keyword": "socket.socket",
"description": "Создание сокета",
"relevance": "Высокая",
},
{
"keyword": "shell=True",
"description": "Использование shell=True в subprocess",
"relevance": "Критическая",
},
{
"keyword": "codecs.decode",
"description": "Декодирование с использованием codecs",
"relevance": "Средняя",
},
{
"keyword": "pickle.loads",
"description": "Десериализация (pickle)",
"relevance": "Высокая",
},
{
"keyword": "marshal.loads",
"description": "Десериализация (marshal)",
"relevance": "Высокая",
},
{
"keyword": "__import__",
"description": "Динамический импорт",
"relevance": "Средняя",
},
{
"keyword": "ctypes.CDLL",
"description": "Загрузка динамической библиотеки",
"relevance": "Высокая",
},
{
"keyword": "create_connection",
"description": "Установка соединения",
"relevance": "Высокая",
},
{
"keyword": "http.server",
"description": "Запуск веб-сервера",
"relevance": "Средняя",
},
{
"keyword": "asyncio.create_subprocess_shell",
"description": "Асинхронный запуск процесса через shell",
"relevance": "Критическая",
},
],
"warning": [
{
"keyword": "requests",
"description": "HTTP-запросы",
"relevance": "Средняя",
},
{
"keyword": "aiohttp",
"description": "Асинхронные HTTP-запросы",
"relevance": "Средняя",
},
{
"keyword": "os.remove",
"description": "Удаление файлов",
"relevance": "Средняя",
},
{
"keyword": "os.mkdir",
"description": "Создание каталогов",
"relevance": "Низкая",
},
{
"keyword": "json.loads",
"description": "Парсинг JSON",
"relevance": "Низкая",
},
{
"keyword": "open(..., 'w')",
"description": "Открытие файла на запись",
"relevance": "Средняя",
},
{
"keyword": "open(..., 'a')",
"description": "Открытие файла на добавление",
"relevance": "Средняя",
},
{
"keyword": "telnetlib.Telnet",
"description": "Telnet соединение",
"relevance": "Средняя",
},
{
"keyword": "ftplib.FTP",
"description": "FTP соединение",
"relevance": "Средняя",
},
{
"keyword": "shutil.move",
"description": "Перемещение файлов",
"relevance": "Средняя",
},
{
"keyword": "shutil.copy",
"description": "Копирование файлов",
"relevance": "Средняя",
},
{
"keyword": "threading.Thread",
"description": "Создание потока",
"relevance": "Низкая",
},
{
"keyword": "multiprocessing.Process",
"description": "Создание процесса",
"relevance": "Низкая",
},
{
"keyword": "queue.Queue",
"description": "Использование очереди",
"relevance": "Низкая",
},
{
"keyword": "subprocess.check_output",
"description": "Запуск процесса с захватом вывода",
"relevance": "Средняя",
},
{
"keyword": "subprocess.run",
"description": "Запуск процесса",
"relevance": "Средняя",
},
{
"keyword": "codecs.encode",
"description": "Кодирование с использованием codecs",
"relevance": "Средняя",
},
],
"info": [
{
"keyword": "telethon",
"description": "Использование Telethon",
"relevance": "Низкая",
},
{
"keyword": "pyrogram",
"description": "Использование Pyrogram",
"relevance": "Низкая",
},
{
"keyword": "import",
"description": "Импорт модулей",
"relevance": "Низкая",
},
{
"keyword": "print",
"description": "Вывод в консоль",
"relevance": "Низкая",
},
{
"keyword": "logging.info",
"description": "Логирование",
"relevance": "Низкая",
},
],
}
def __init__(self, ai_enabled: bool = False):
"""Инициализация анализатора."""
self.results = {"critical": [], "warning": [], "info": []}
self.reported_issues = set()
self.code_lines = []
self.is_decoded = False
self.ai_enabled = ai_enabled
def reset(self):
"""Сброс результатов анализа."""
self.results = {"critical": [], "warning": [], "info": []}
self.reported_issues = set()
self.code_lines = []
self.is_decoded = False
async def analyze(self, code: str, strings: dict) -> str:
"""
Выполняет анализ предоставленного Python-кода.
Args:
code: Python-код для анализа.
strings: Словарь строк для локализации.
Returns:
Форматированный отчет об анализе.
"""
self.reset()
original_code = code
try:
code = self._try_decode(code)
self.is_decoded = True
except Exception:
logger.warning("Не удалось расшифровать код, анализ как есть.")
self.code_lines = code.splitlines()
try:
tree = ast.parse(code)
self._visit_tree(tree)
self._heuristic_analysis(code, tree)
if self.ai_enabled and G4F_AVAILABLE:
ai_analysis_result = await self._ai_analysis(code)
if ai_analysis_result:
self.results["critical"].append(
{
"keyword": "AI Analysis",
"description": ai_analysis_result,
"relevance": "Критическая",
"line": 0,
"col": 0,
}
)
except SyntaxError as e:
logger.error(f"Ошибка синтаксиса в коде: {e}")
return strings["syntax_error"].format(error=e)
except Exception as e:
logger.exception("Unexpected error during analysis")
return strings["syntax_error"].format(error=str(e))
return self._format_report(strings, original_code)
async def _ai_analysis(self, code: str) -> str or None:
"""
Использует g4f для анализа кода и выявления потенциальных угроз.
"""
try:
prompt = f"Проанализируйте следующий Python-код на предмет потенциальных угроз безопасности, уязвимостей и вредоносных действий. Предоставьте подробное объяснение, если что-то будет обнаружено:\n\n{code}"
response = await utils.run_sync(
g4f.ChatCompletion.create,
model=g4f.models.default,
messages=[{"role": "user", "content": prompt}],
)
return str(response)
except Exception as e:
logger.error(f"Ошибка при анализе с помощью g4f: {e}")
return None
def _try_decode(self, code):
"""Попытка расшифровать base64 + zlib код."""
if re.search(r"__import__\('zlib'\).decompress\(", code) and re.search(
r"__import__\('base64'\).b64decode\(", code
):
try:
match = re.search(r"b'([A-Za-z0-9+/=]+)'", code)
if match:
encoded_string = match.group(1)
decoded_code = self._decode_base64_zlib(encoded_string)
logger.info("Код успешно расшифрован.")
return decoded_code
except Exception as e:
logger.error(f"Ошибка при расшифровке кода: {e}")
raise
return code
def _decode_base64_zlib(self, encoded_string):
"""Расшифровывает base64 + zlib код."""
try:
decoded_bytes = base64.b64decode(encoded_string)
decompressed_bytes = zlib.decompress(decoded_bytes)
return decompressed_bytes.decode("utf-8")
except Exception as e:
logger.error(f"Ошибка при расшифровке base64+zlib: {e}")
raise
def _get_line_from_code(self, lineno):
"""Получает строку кода по номеру строки."""
try:
return self.code_lines[lineno - 1]
except IndexError:
return ""
def _visit_tree(self, tree):
"""Рекурсивно обходит AST-дерево."""
for node in ast.walk(tree):
self._analyze_node(node)
def _analyze_node(self, node):
"""Анализирует отдельный узел AST."""
if isinstance(node, ast.Name):
self._check_keyword(node.id, node)
elif isinstance(node, ast.Call):
self._check_call(node)
elif isinstance(node, (ast.Import, ast.ImportFrom)):
self._check_import(node)
elif isinstance(node, ast.FunctionDef):
self._check_function_def(node)
elif isinstance(node, ast.ClassDef):
self._check_class_def(node)
elif isinstance(node, ast.Assign):
self._check_assign(node)
def _check_keyword(self, keyword, node):
"""Проверяет ключевые слова."""
for severity, keywords in self.SECURITY_KEYWORDS.items():
for item in keywords:
if item["keyword"] == keyword:
issue_key = (
item["keyword"],
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results[severity].append(
{
"keyword": item["keyword"],
"description": item["description"],
"relevance": item["relevance"],
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
def _check_call(self, node):
"""Анализирует вызовы функций."""
if isinstance(node.func, ast.Name):
self._check_keyword(node.func.id, node)
elif isinstance(node.func, ast.Attribute):
full_attr = ""
if isinstance(node.func.value, ast.Name):
full_attr = node.func.value.id + "." + node.func.attr
self._check_keyword(full_attr, node)
else:
self._check_keyword(node.func.attr, node)
elif isinstance(node.func, ast.Subscript):
if isinstance(node.func.value, ast.Attribute):
full_attr = ""
if isinstance(node.func.value.value, ast.Name):
full_attr = node.func.value.value.id + "." + node.func.value.attr
self._check_keyword(full_attr, node)
def _check_function_def(self, node):
self._check_keyword(node.name, node)
def _check_class_def(self, node):
self._check_keyword(node.name, node)
def _check_import(self, node):
"""Анализирует импорты."""
if isinstance(node, ast.Import):
for alias in node.names:
self._check_keyword(alias.name, node)
elif isinstance(node, ast.ImportFrom):
self._check_keyword(node.module, node)
for alias in node.names:
self._check_keyword(alias.name, node)
def _check_assign(self, node):
"""Анализирует присваивания."""
for target in node.targets:
if isinstance(target, ast.Name):
self._check_keyword(target.id, node)
def _heuristic_analysis(self, code: str, tree: ast.AST):
"""
Эвристический анализ для обнаружения подозрительного кода.
"""
self._check_obfuscation(code, tree)
self._check_dynamic_code_generation(code, tree)
self._check_url_patterns(code)
self._check_api_abuse(tree)
self._check_reverse_shell(code)
self._check_file_operations(code)
def _check_obfuscation(self, code: str, tree: ast.AST):
"""Обнаружение обфускации кода."""
if len(re.findall(r"[A-Za-z0-9+/]{30,}", code)) > 2:
issue_key = ("Base64", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Base64",
"description": "Подозрительные строки Base64",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if "zlib.decompress" in code:
issue_key = ("zlib.decompress", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "zlib.decompress",
"description": "Использование zlib декомпрессии",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
for node in ast.walk(tree):
if isinstance(node, (ast.Call)):
if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
if len(node.args) > 0 and isinstance(node.args[0], ast.Str):
obfuscated_string = node.args[0].s
if (
len(re.findall(r"[A-Za-z0-9+/]{30,}", obfuscated_string))
> 0
):
issue_key = (
"eval/exec+Base64",
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "eval/exec+Base64",
"description": "eval/exec с обфускацией Base64",
"relevance": "Критическая",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
elif isinstance(node.func, ast.Name) and node.func.id in (
"eval",
"exec",
):
if len(node.args) > 0 and isinstance(node.args[0], ast.Name):
issue_key = ("eval/exec+Variable", node.lineno, node.col_offset)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "eval/exec+Variable",
"description": "eval/exec с переменной",
"relevance": "Критическая",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
hash_functions = ["md5", "sha1", "sha256", "sha512"]
for hash_func in hash_functions:
if f"hashlib.{hash_func}" in code:
issue_key = (f"hashlib.{hash_func}", 1, 1)
if issue_key not in self.reported_issues:
self.results["info"].append(
{
"keyword": f"hashlib.{hash_func}",
"description": f"Использование {hash_func} хеширования",
"relevance": "Низкая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if any(
x in code
for x in [
"hashlib.md5(password.encode()).hexdigest()",
"hashlib.sha256(password.encode()).hexdigest()",
]
):
issue_key = ("Weak Hashing", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Weak Hashing",
"description": "Использование хеширования без соли",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _check_dynamic_code_generation(self, code, tree: ast.AST):
"""Обнаружение динамической генерации кода."""
if "compile(" in code:
issue_key = ("compile", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "compile",
"description": "Использование compile() для генерации кода",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
for node in ast.walk(tree):
if (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "type"
):
if (
len(node.args) == 3
and isinstance(node.args[0], ast.Str)
and isinstance(node.args[1], ast.Tuple)
and isinstance(node.args[2], ast.Dict)
):
issue_key = ("type() class", node.lineno, node.col_offset)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "type() class",
"description": "Динамическое создание классов через type()",
"relevance": "Средняя",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
def _check_url_patterns(self, code: str):
"""Обнаружение подозрительных URL-паттернов."""
short_url_domains = [
"bit.ly",
"goo.gl",
"t.co",
"tinyurl.com",
"is.gd",
"ow.ly",
"github.com",
"raw.githubusercontent.com",
]
for domain in short_url_domains:
if domain in code:
issue_key = (f"Short URL ({domain})", 1, 1)
if issue_key not in self.reported_issues:
line = self._get_line_from_code(1)
match = re.search(r"(https?://\S+)", line)
url = (
match.group(1)
if match
else f"Не удалось извлечь URL ({domain})"
)
self.results["warning"].append(
{
"keyword": f"Short URL ({domain})",
"description": f"Обнаружен сокращенный URL: {url}",
"relevance": "Низкая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
webhook_patterns = ["discord.com/api/webhooks", "api.telegram.org/bot"]
for pattern in webhook_patterns:
if pattern in code:
issue_key = (f"Webhook ({pattern})", 1, 1)
if issue_key not in self.reported_issues:
line = self._get_line_from_code(1)
match = re.search(pattern, line)
url = (
match.group(0)
if match
else f"Не удалось извлечь Webhook ({pattern})"
)
self.results["critical"].append(
{
"keyword": f"Webhook ({pattern})",
"description": f"Обнаружен Webhook: {url}",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _check_api_abuse(self, tree: ast.AST):
"""Обнаружение потенциального злоупотребления Telegram API."""
send_methods = ["send_message", "send_file", "send_photo"]
for node in ast.walk(tree):
if isinstance(node, ast.For):
for send_method in send_methods:
if send_method in ast.unparse(node):
issue_key = (
f"Mass {send_method}",
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": f"Mass {send_method}",
"description": f"Подозрение на массовую рассылку ({send_method})",
"relevance": "Средняя",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
if "time.sleep(" in ast.unparse(tree):
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", ast.unparse(tree))
for sleep_time in sleep_calls:
try:
sleep_value = float(sleep_time)
if sleep_value < 1:
issue_key = ("Short Sleep Time", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Short Sleep Time",
"description": "Обнаружена короткая задержка (менее 1 секунды)",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
except ValueError:
pass
def _check_reverse_shell(self, code: str):
"""Обнаружение попыток создания обратного шелла."""
try:
reverse_shell_patterns = [
r"socket\.socket\(\s*socket\.AF_INET",
r"os\.dup2\(",
r"subprocess\.Popen\(\s*\[.+?\]\s*,\s*shell=True",
r"/bin/bash -i",
r"/bin/sh -i",
r"nc -e /bin/bash",
r"nc -e /bin/sh",
r"> /dev/tcp/",
r"python -c 'import socket,subprocess,os;s=socket.socket",
r"python3 -c 'import socket,subprocess,os;s=socket.socket",
]
for pattern in reverse_shell_patterns:
if re.search(pattern, code):
issue_key = ("Reverse Shell", 1, 1)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "Reverse Shell",
"description": "Обнаружена попытка создания обратного шелла",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
except Exception as e:
logger.error(f"Error in _check_reverse_shell: {e}")
def _check_file_operations(self, code: str):
"""Обнаружение потенциально опасных операций с файлами."""
dangerous_file_paths = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/sudoers",
]
for file_path in dangerous_file_paths:
if file_path in code:
issue_key = ("File Override", 1, 1)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "File Override",
"description": f"Попытка записи в критический файл: {file_path}",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if "shutil.rmtree" in code:
issue_key = ("Recursive Delete", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Recursive Delete",
"description": "Обнаружено рекурсивное удаление каталога",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
executable_extensions = [".py", ".sh", ".bat", ".exe"]
for ext in executable_extensions:
if f"open(..., '{ext}'" in code or f"open(... + '{ext}'" in code:
issue_key = ("Executable File Creation", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Executable File Creation",
"description": f"Обнаружено создание файла с расширением {ext}",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _format_report(self, strings: dict, original_code: str) -> str:
"""Форматирует отчет об анализе."""
report = strings["report_header"]
if self.is_decoded:
report += "<b>⚠️ Код был расшифрован перед анализом.</b>\n\n"
else:
report += "<b>⚠️ Анализ проводился над исходным кодом, расшифровка не удалась.</b>\n\n"
total_issues = 0
for severity, issues in self.results.items():
if issues:
report += strings[f"{severity}_header"]
total_issues += len(issues)
for issue in issues:
report += strings["issue_format"].format(
keyword=issue["keyword"],
description=issue["description"],
relevance=issue["relevance"],
line=issue["line"],
col=issue["col"],
)
report += "\n"
if total_issues == 0:
report += strings["no_issues"]
else:
report += strings["report_footer"].format(count=total_issues)
return report
@loader.tds
class UserbotAvast(loader.Module):
"""A module for checking modules for security."""
strings = {
"name": "UserbotAvast",
"cfg_ai_enabled": "Включить анализ с помощью AI (g4f)",
"cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.",
"report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n",
"critical_header": "<b>🔴 Критические угрозы:</b>\n",
"warning_header": "<b>🟠 Предупреждения:</b>\n",
"info_header": "<b>🔵 Информация:</b>\n",
"issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n",
"no_issues": "Не обнаружено проблем безопасности.\n",
"report_footer": "\nВсего обнаружено {count} проблем.\n",
"syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n",
"loading": "⏳ Запуск анализатора безопасности...",
"no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.",
"decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.",
}
strings_ru = {
"cfg_ai_enabled": "Включить анализ с помощью AI (g4f)",
"cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.",
"report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n",
"critical_header": "<b>🔴 Критические угрозы:</b>\n",
"warning_header": "<b>🟠 Предупреждения:</b>\n",
"info_header": "<b>🔵 Информация:</b>\n",
"issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n",
"no_issues": "Не обнаружено проблем безопасности.\n",
"report_footer": "\nВсего обнаружено {count} проблем.\n",
"syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n",
"loading": "⏳ Запуск анализатора безопасности...",
"no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.",
"decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"ai_enabled",
False,
lambda: self.strings["cfg_ai_enabled"],
validator=loader.validators.Boolean(),
),
)
async def client_ready(self, client, db):
"""Вызывается при готовности клиента."""
self.client = client
self.db = db
self.security_analyzer = SecurityAnalyzer(self.config["ai_enabled"])
@loader.unrestricted
@loader.ratelimit
async def checkmodcmd(self, message):
"""
[module_link] или [reply file] или [send file] - выполняет проверку модуля на безопасность.
"""
await utils.answer(message, self.strings["loading"])
args = utils.get_args_raw(message)
code = None
if args:
code = await self._get_code_from_url(args)
if not code:
code = await self._get_code_from_message(message)
if not code:
await utils.answer(message, self.strings["no_module"])
return
try:
result = await self.security_analyzer.analyze(code, self.strings)
await utils.answer(message, result)
except Exception as e:
logger.exception("Error during analysis")
await utils.answer(message, f"An error occurred during analysis: {e}")
async def _get_code_from_url(self, url: str) -> str or None:
"""Получает код модуля по URL."""
try:
response = await utils.run_sync(requests.get, url)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
logger.error(f"Ошибка при получении кода из URL: {e}")
return None
async def _get_code_from_message(self, message) -> str or None:
"""Получает код модуля из прикрепленного файла или ответа на сообщение."""
try:
if message.media:
code = (await self.client.download_file(message.media, bytes)).decode(
"utf-8"
)
return code
reply = await message.get_reply_message()
if reply and reply.media:
code = (await self.client.download_file(reply.media, bytes)).decode(
"utf-8"
)
return code
except Exception as e:
logger.error(f"Ошибка при получении кода из сообщения: {e}")
return None

View File

@@ -1,132 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Video2GIF
# Description: Converts video to GIF
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Video2GIF
# scope: Video2GIF 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import logging
import os
import shutil
import tempfile
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class Video2GIFMod(loader.Module):
"""Convert video to high quality GIF"""
strings = {
"name": "Video2GIF",
"success": "✅ GIF created",
"error": "❌ Conversion failed",
"no_video": "❌ Reply to a video",
"no_ffmpeg": "❌ FFmpeg not installed. Install: apt install ffmpeg",
"processing": "🔄 Processing video...",
"compressing": "📦 Optimizing GIF...",
}
strings_ru = {
"success": "✅ GIF создан",
"error": "❌ Ошибка конвертации",
"no_video": "❌ Ответьте на видео",
"no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg",
"processing": "🔄 Обрабатываю видео...",
"compressing": "📦 Оптимизирую GIF...",
}
def __init__(self):
self._ffmpeg_check = None
async def client_ready(self, client, db):
self._client = client
self._db = db
self._check_ffmpeg()
def _check_ffmpeg(self):
self._ffmpeg_check = shutil.which("ffmpeg") is not None
@loader.command(
ru_doc="[ответ] [fps] [ширина] - конвертировать видео в GIF",
en_doc="[reply] [fps] [width] - convert video to GIF",
)
async def gifc(self, message):
"""Convert video to GIF"""
if not self._ffmpeg_check:
return await utils.answer(message, self.strings["no_ffmpeg"])
reply = await message.get_reply_message()
if not reply or not reply.video:
return await utils.answer(message, self.strings["no_video"])
args = utils.get_args_raw(message).split()
fps = 15 if len(args) < 1 else min(int(args[0]), 30)
width = 480 if len(args) < 2 else min(int(args[1]), 1024)
msg = await utils.answer(message, self.strings["processing"])
try:
gif_path = await self._convert_to_gif(reply, fps, width)
await self._client.send_file(
message.chat_id,
gif_path,
caption=self.strings["success"],
reply_to=reply.id,
)
os.remove(gif_path)
await msg.delete()
except Exception:
await utils.answer(message, self.strings["error"])
async def _convert_to_gif(self, reply, fps: int, width: int) -> str:
"""Convert video to optimized GIF"""
with tempfile.TemporaryDirectory() as tmpdir:
video_path = os.path.join(tmpdir, "video.mp4")
gif_path = os.path.join(tmpdir, "output.gif")
await reply.download_media(video_path)
cmd = [
"ffmpeg",
"-i",
video_path,
"-vf",
f"fps={fps},scale={width}:-1:flags=lanczos",
"-lavfi",
"[0:v]split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
"-y",
gif_path,
]
proc = await asyncio.create_subprocess_exec(*cmd)
await proc.communicate()
return gif_path

View File

@@ -1,240 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VirusTotal
# Description: Checks files for viruses using VirusTotal
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api VirusTotal
# scope: Api VirusTotal 0.0.1
# requires: json aiohttp tempfile
# ---------------------------------------------------------------------------------
import asyncio
import logging
import os
import tempfile
from typing import Any, Dict, Optional
import aiohttp
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class VirusTotalMod(loader.Module):
"""Professional file scanning with VirusTotal"""
strings = {
"name": "VirusTotal",
"no_file": "🚫 Reply to a file",
"downloading": "📥 Downloading file...",
"uploading": "📤 Uploading to VirusTotal...",
"scanning": "🔍 Scanning in progress...",
"waiting": "⏳ Waiting for analysis...",
"no_key": "🚫 Set VirusTotal API key in config",
"error": "❌ Error during scan",
"size_limit": "📁 File exceeds 32MB limit",
"timeout": "⏰ Scan timeout",
"clean": "✅ File is clean",
"suspicious": "⚠️ Suspicious file",
"malicious": "⛔ Malicious file",
"view_report": "📊 View full report",
"close": "❌ Close",
"engines": "Scan engines",
"detections": "Detections",
"status": "Status",
"completed": "Completed",
"queued": "Queued",
"scan_date": "Scan date",
}
strings_ru = {
"no_file": "🚫 Ответьте на файл",
"downloading": "📥 Скачиваю файл...",
"uploading": "📤 Загружаю на VirusTotal...",
"scanning": "🔍 Сканирую...",
"waiting": "⏳ Жду анализа...",
"no_key": "🚫 Укажите API ключ в конфиге",
"error": "❌ Ошибка при сканировании",
"size_limit": "📁 Файл больше 32МБ",
"timeout": "⏰ Таймаут сканирования",
"clean": "✅ Файл чистый",
"suspicious": "⚠️ Подозрительный файл",
"malicious": "⛔ Вредоносный файл",
"view_report": "📊 Полный отчёт",
"close": "❌ Закрыть",
"engines": "Антивирусов",
"detections": "Обнаружено",
"status": "Статус",
"completed": "Завершён",
"queued": "В очереди",
"scan_date": "Дата сканирования",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"api_key",
None,
"VirusTotal API key from https://virustotal.com",
validator=loader.validators.Hidden(),
)
)
self.session: Optional[aiohttp.ClientSession] = None
self.MAX_SIZE = 32 * 1024 * 1024 # 32MB
self.TIMEOUT = 120 # seconds
async def client_ready(self, client, db):
self._client = client
self._db = db
async def on_unload(self):
if self.session:
await self.session.close()
def _get_session(self) -> aiohttp.ClientSession:
"""Get or create aiohttp session with API key"""
if not self.session:
headers = {"x-apikey": self.config["api_key"]}
self.session = aiohttp.ClientSession(headers=headers)
return self.session
@loader.command(
ru_doc="[ответ] - просканировать файл через VirusTotal",
en_doc="[reply] - scan file with VirusTotal",
)
async def vt(self, message):
"""Scan file with VirusTotal"""
api_key = self.config["api_key"]
if not api_key:
return await utils.answer(message, self.strings["no_key"])
reply = await message.get_reply_message()
if not reply or not reply.document:
return await utils.answer(message, self.strings["no_file"])
async with self._get_session() as session:
try:
msg = await utils.answer(message, self.strings["downloading"])
with tempfile.TemporaryDirectory() as tmpdir:
file_path = os.path.join(tmpdir, reply.file.name)
await reply.download_media(file_path)
file_size = os.path.getsize(file_path)
if file_size > self.MAX_SIZE:
return await msg.edit(self.strings["size_limit"])
await msg.edit(self.strings["uploading"])
analysis_id = await self._upload_file(session, file_path)
await msg.edit(self.strings["waiting"])
result = await self._wait_for_analysis(session, analysis_id)
await self._show_results(msg, analysis_id, result)
except asyncio.TimeoutError:
await utils.answer(message, self.strings["timeout"])
except Exception as e:
error_text = f"{self.strings['error']}: {str(e)[:100]}"
await utils.answer(message, error_text)
async def _upload_file(self, session: aiohttp.ClientSession, path: str) -> str:
"""Upload file to VirusTotal and return analysis ID"""
with open(path, "rb") as f:
form = aiohttp.FormData()
form.add_field("file", f, filename=os.path.basename(path))
async with session.post(
"https://www.virustotal.com/api/v3/files", data=form
) as response:
response.raise_for_status()
data = await response.json()
return data["data"]["id"]
async def _wait_for_analysis(
self, session: aiohttp.ClientSession, analysis_id: str
) -> Dict[str, Any]:
"""Poll analysis results until completion"""
url = f"https://www.virustotal.com/api/v3/analyses/{analysis_id}"
for _ in range(20):
async with session.get(url) as response:
response.raise_for_status()
data = await response.json()
status = data["data"]["attributes"]["status"]
if status == "completed":
return data
await asyncio.sleep(3)
raise asyncio.TimeoutError()
async def _show_results(self, message, analysis_id: str, result: Dict[str, Any]):
"""Display scan results in inline form"""
stats = result["data"]["attributes"]["stats"]
date = result["data"]["attributes"]["date"]
malicious = stats.get("malicious", 0)
suspicious = stats.get("suspicious", 0)
undetected = stats.get("undetected", 0)
harmless = stats.get("harmless", 0)
total = malicious + suspicious + undetected + harmless
if malicious > 0:
verdict = self.strings["malicious"]
emoji = ""
elif suspicious > 0:
verdict = self.strings["suspicious"]
emoji = "⚠️"
else:
verdict = self.strings["clean"]
emoji = ""
from datetime import datetime
scan_date = datetime.fromtimestamp(date).strftime("%Y-%m-%d %H:%M:%S")
text = (
f"{emoji} <b>VirusTotal Scan Results</b>\n\n"
f"<b>{self.strings['status']}:</b> {verdict}\n"
f"<b>{self.strings['detections']}:</b> {malicious}\n"
f"<b>{self.strings['engines']}:</b> {total}\n"
f"<b>{self.strings['scan_date']}:</b> {scan_date}\n\n"
f"<code>Malicious: {malicious}/{total}</code>\n"
f"<code>Suspicious: {suspicious}/{total}</code>\n"
f"<code>Harmless: {harmless}/{total}</code>\n"
f"<code>Undetected: {undetected}/{total}</code>"
)
vt_url = f"https://www.virustotal.com/gui/file-analysis/{analysis_id}"
await self.inline.form(
text=text,
message=message,
reply_markup=[
[{"text": f"🔗 {self.strings['view_report']}", "url": vt_url}],
[{"text": self.strings["close"], "action": "close"}],
],
ttl=300, # 5 minutes timeout
)

View File

@@ -1,118 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VoiceDL
# Description: Voice Downloader module
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: VoiceDL
# scope: VoiceDL 0.0.1
# requires: tempfile
# ---------------------------------------------------------------------------------
import asyncio
import logging
import os
import shutil
import tempfile
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class VoiceDLMod(loader.Module):
"""Download voice messages as MP3"""
strings = {
"name": "VoiceDL",
"success": "✅ Voice downloaded as MP3",
"error": "❌ Error downloading voice",
"no_voice": "❌ Reply to a voice message",
"no_ffmpeg": "❌ FFmpeg not found. Install: apt install ffmpeg",
}
strings_ru = {
"success": "✅ Голосовое скачано как MP3",
"error": "❌ Ошибка скачивания",
"no_voice": "❌ Ответьте на голосовое",
"no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg",
}
def __init__(self):
self._ffmpeg_check = None
async def client_ready(self, client, db):
self._client = client
self._db = db
self._check_ffmpeg()
def _check_ffmpeg(self):
self._ffmpeg_check = shutil.which("ffmpeg") is not None
@loader.command(
ru_doc="[ответ] - скачать голосовое как MP3",
en_doc="[reply] - download voice as MP3",
)
async def voicedl(self, message):
if not self._ffmpeg_check:
return await utils.answer(message, self.strings["no_ffmpeg"])
reply = await message.get_reply_message()
if not reply or not reply.voice:
return await utils.answer(message, self.strings["no_voice"])
await self._process_voice(message, reply)
async def _process_voice(self, message, reply):
with tempfile.TemporaryDirectory() as tmpdir:
try:
ogg_path = os.path.join(tmpdir, "voice.ogg")
mp3_path = os.path.join(tmpdir, "voice.mp3")
await reply.download_media(file=ogg_path)
proc = await asyncio.create_subprocess_exec(
"ffmpeg",
"-i",
ogg_path,
"-codec:a",
"libmp3lame",
"-q:a",
"2",
mp3_path,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.communicate()
if proc.returncode != 0:
raise Exception("FFmpeg error")
await message.client.send_file(
message.chat.id,
mp3_path,
caption=self.strings["success"],
reply_to=reply.id,
)
except Exception:
await utils.answer(message, self.strings["error"])

View File

@@ -1,333 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Weather
# Description: Advanced weather module with detailed information
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: api Weather
# scope: api Weather 0.0.1
# ---------------------------------------------------------------------------------
import logging
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, List, Union
import requests
from .. import loader, utils
logger = logging.getLogger(__name__)
DEFAULT_FORECAST_DAYS = 3
DEFAULT_HOURLY_INDEX = 4
WEATHER_API_URL = "https://wttr.in/{city}?format=j1&lang=en"
@dataclass
class WeatherCondition:
"""Represents a weather condition with its emoji."""
condition: str
emoji: str
@dataclass
class WindDirection:
"""Represents a wind direction with its description."""
direction: str
description: str
@dataclass
class ForecastDay:
"""Represents a single day's weather forecast."""
date: str
emoji: str
condition: str
temp_min: str
temp_max: str
wind_speed: str
wind_direction: str
WEATHER_EMOJI: List[WeatherCondition] = [
WeatherCondition("clear", "<emoji document_id=5402477260982731644>☀️</emoji>"),
WeatherCondition("sunny", "<emoji document_id=5402477260982731644>☀️</emoji>"),
WeatherCondition(
"partly cloudy", "<emoji document_id=5350424168615649565>⛅️</emoji>"
),
WeatherCondition("cloudy", "☁️<emoji document_id=5208563370218762357>☁️</emoji>"),
WeatherCondition("overcast", "<emoji document_id=5208563370218762357>☁️</emoji>"),
WeatherCondition("mist", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"),
WeatherCondition("fog", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"),
WeatherCondition("light rain", "<emoji document_id=5283097055852503586>🌦</emoji>"),
WeatherCondition("rain", "<emoji document_id=5283243028905994049>🌧</emoji>"),
WeatherCondition("heavy rain", "<emoji document_id=5282939632416206153>⛈</emoji>"),
WeatherCondition(
"thunderstorm", "<emoji document_id=5282939632416206153>⛈</emoji>"
),
WeatherCondition("snow", "<emoji document_id=5282833267551117457>🌨</emoji>"),
WeatherCondition("heavy snow", "<emoji document_id=5449449325434266744>❄️</emoji>"),
WeatherCondition("sleet", "<emoji document_id=5282833267551117457>🌨</emoji>"),
WeatherCondition("wind", "💨"),
]
WIND_DIRECTIONS: List[WindDirection] = [
WindDirection("N", "⬆️ North"),
WindDirection("NE", "↗️ Northeast"),
WindDirection("E", "➡️ East"),
WindDirection("SE", "↘️ Southeast"),
WindDirection("S", "⬇️ South"),
WindDirection("SW", "↙️ Southwest"),
WindDirection("W", "⬅️ West"),
WindDirection("NW", "↖️ Northwest"),
]
WIND_DIRECTIONS_RU: List[WindDirection] = [
WindDirection("N", "⬆️ Северный"),
WindDirection("NE", "↗️ Северо-восточный"),
WindDirection("E", "➡️ Восточный"),
WindDirection("SE", "↘️ Юго-восточный"),
WindDirection("S", "⬇️ Южный"),
WindDirection("SW", "↙️ Юго-западный"),
WindDirection("W", "⬅️ Западный"),
WindDirection("NW", "↖️ Северо-западный"),
]
@loader.tds
class Weather(loader.Module):
"""Advanced weather module with detailed information"""
strings = {
"name": "Weather",
"no_city": "🚫 <b>Please specify a city</b>",
"invalid_city": "🚫 <b>City not found</b>",
"loading": "🔄 <b>Fetching weather data for {}</b>...",
"error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Error retrieving weather data</b>",
"default_city": "<emoji document_id=5980930633298350051>✅</emoji> Default city set to: <code>{city}</code>",
"weather_text": """<b>{emoji} Weather: {location}</b>
<b>📊 Current conditions:</b>
├ 🌡 Temperature: <code>{temp}°C</code>
├– <i>Feels like:</i> <code>{feels_like}°C</code>
├ 💧 Humidity: <code>{humidity}%</code>
├ 💨 Wind: <code>{wind_speed} km/h</code> {wind_direction}
├ 🌪 Pressure: <code>{pressure} mmHg</code>
├ 👁 Visibility: <code>{visibility} km</code>
└ ☁️ Cloudiness: <code>{clouds}</code>
<b>🌅 Time:</b>
├ 🌅 Sunrise: <code>{sunrise}</code>
├ 🌇 Sunset: <code>{sunset}</code>
└ ⏱ Local time: <code>{local_time}</code>
<b>📅 Forecast for {forecast_days} days:</b>
{forecast}
⏰ Updated: <code>{updated}</code>""",
"forecast_day": """<b>{date}</b> {emoji}
├ 🌡 Temperature: {temp_min}°C ... {temp_max}°C
└ 💨 Wind: {wind_speed} km/h {wind_direction}
""",
}
strings_ru = {
"no_city": "🚫 <b>Пожалуйста, укажите город</b>",
"invalid_city": "🚫 <b>Город не найден</b>",
"loading": "🔄 <b>Получаю метеоданные для {}</b>...",
"default_city": "<emoji document_id=5980930633298350051>✅</emoji> Город по умолчанию установлен: <code>{city}</code>",
"error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Ошибка при получении данных о погоде</b>",
"weather_text": """<b>{emoji} Погода: {location}</b>
<b>📊 Текущие условия:</b>
├ 🌡 Температура: <code>{temp}°C</code>
├– <i>Ощущается как:</i> <code>{feels_like}°C</code>
├ 💧 Влажность: <code>{humidity}%</code>
├ 💨 Ветер: <code>{wind_speed} км/ч</code> {wind_direction}
├ 🌪 Давление: <code>{pressure} мм.рт.ст</code>
├ 👁 Видимость: <code>{visibility} км</code>
└ ☁️ Облачность: <code>{clouds}</code>
<b>🌅 Время:</b>
├ 🌅 Восход: <code>{sunrise}</code>
├ 🌇 Закат: <code>{sunset}</code>
└ ⏱ Местное время: <code>{local_time}</code>
<b>📅 Прогноз на {forecast_days} дня:</b>
{forecast}
⏰ Обновлено: <code>{updated}</code>""",
"forecast_day": """<b>{date}</b> {emoji}
├ 🌡 Температура: {temp_min}°C ... {temp_max}°C
└ 💨 Ветер: {wind_speed} км/ч {wind_direction}
""",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"default_city",
None,
lambda: "Default city for weather command",
),
loader.ConfigValue(
"language",
"ru",
lambda: "Language for weather output (en/ru)",
),
)
def get_weather_emoji(self, condition: str) -> str:
"""Get emoji for weather conditions"""
condition = condition.lower()
for item in WEATHER_EMOJI:
if item.condition in condition:
return item.emoji
return "🌡"
def get_wind_direction(self, direction: str) -> str:
"""Get wind direction description"""
lang = self.config["language"]
directions = WIND_DIRECTIONS_RU if lang == "ru" else WIND_DIRECTIONS
for item in directions:
if item.direction == direction.upper():
return item.description
return direction
async def get_weather_data(self, city: str) -> Union[Dict, None]:
"""Get weather data from wttr.in"""
lang = self.config["language"]
url = WEATHER_API_URL.format(city=city)
if lang == "ru":
url = f"https://wttr.in/{city}?format=j1&lang=ru"
try:
response = await utils.run_sync(requests.get, url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Failed to fetch weather data for {city}: {e}")
return None
except Exception as e:
logger.exception(f"Error fetching weather data: {e}")
return None
def format_forecast(self, forecast_data: list) -> str:
"""Format weather forecast for multiple days."""
forecast_text = ""
for day in forecast_data:
hourly = day["hourly"][DEFAULT_HOURLY_INDEX]
forecast_day = ForecastDay(
date=day["date"],
emoji=self.get_weather_emoji(hourly["weatherDesc"][0]["value"]),
condition=hourly["weatherDesc"][0]["value"],
temp_min=day["mintempC"],
temp_max=day["maxtempC"],
wind_speed=hourly["windspeedKmph"],
wind_direction=self.get_wind_direction(hourly["winddir16Point"]),
)
forecast_text += self.strings("forecast_day").format(
date=forecast_day.date,
emoji=forecast_day.emoji,
condition=forecast_day.condition,
temp_min=forecast_day.temp_min,
temp_max=forecast_day.temp_max,
wind_speed=forecast_day.wind_speed,
wind_direction=forecast_day.wind_direction,
)
return forecast_text
async def process_weather_data(self, weather_data: Dict) -> str:
"""Process weather data and format the text."""
current = weather_data["current_condition"][0]
forecast = weather_data["weather"]
location = (
f"{weather_data['nearest_area'][0]['areaName'][0]['value']}, "
f"{weather_data['nearest_area'][0]['country'][0]['value']}"
)
forecast_text = self.format_forecast(forecast[:DEFAULT_FORECAST_DAYS])
return self.strings("weather_text").format(
location=location,
emoji=self.get_weather_emoji(current["weatherDesc"][0]["value"]),
temp=current["temp_C"],
feels_like=current["FeelsLikeC"],
humidity=current["humidity"],
wind_speed=current["windspeedKmph"],
wind_direction=self.get_wind_direction(current["winddir16Point"]),
pressure=current["pressure"],
visibility=current["visibility"],
clouds=current["weatherDesc"][0]["value"],
sunrise=forecast[0]["astronomy"][0]["sunrise"],
sunset=forecast[0]["astronomy"][0]["sunset"],
local_time=current["observation_time"],
forecast=forecast_text,
forecast_days=DEFAULT_FORECAST_DAYS,
updated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
@loader.command(
ru_doc="Узнайте погоду для указанного города",
en_doc="Get the weather for the specified city",
)
async def weather(self, message):
city = utils.get_args_raw(message) or self.config["default_city"]
if not city:
await utils.answer(message, self.strings("no_city"))
return
await utils.answer(message, self.strings("loading").format(city))
weather_data = await self.get_weather_data(city)
if not weather_data:
await utils.answer(message, self.strings("error"))
return
try:
weather_text = await self.process_weather_data(weather_data)
await utils.answer(message, weather_text)
except Exception as e:
logger.exception(f"Error processing weather data: {e}")
await utils.answer(message, self.strings("error"))
@loader.command(
ru_doc="Установите город по умолчанию для определения погоды",
en_doc="Set the default city for weather",
)
async def weatherset(self, message):
city = utils.get_args_raw(message)
if not city:
await utils.answer(message, self.strings("no_city"))
return
weather_data = await self.get_weather_data(city)
if not weather_data:
await utils.answer(message, self.strings("invalid_city"))
return
self.config["default_city"] = city
await utils.answer(message, self.strings("default_city").format(city=city))

View File

@@ -1,135 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: WindowsKeys
# Description: Provides you Windows activation keys
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: WindowsKeys
# scope: WindowsKeys 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import logging
import time
import aiohttp
from .. import loader
logger = logging.getLogger(__name__)
@loader.tds
class WindowsKeysMod(loader.Module):
"""Windows activation keys"""
strings = {
"name": "WindowsKeys",
"winkey": "✅ Key: <code>{}</code>\n\n⚠ For KMS activation only",
"error": "❌ Failed to get key",
"select": "🔓 Select version:",
"close": "🎈 Close",
"loading": "⌛ Loading...",
}
strings_ru = {
"winkey": "✅ Ключ: <code>{}</code>\n\n⚠ Только для KMS активации",
"error": "❌ Ошибка получения",
"select": "🔓 Выберите версию:",
"close": "🎈 Закрыть",
"loading": "⌛ Загрузка...",
}
def __init__(self):
self.cache = None
self.cache_time = 0
self.CACHE_TTL = 3600
async def client_ready(self, client, db):
self.client = client
self.db = db
@loader.command(ru_doc="Меню ключей Windows", en_doc="Windows keys menu")
async def winkey(self, message):
await self.inline.form(
self.strings["select"],
message=message,
reply_markup=[
[
{
"text": "Win 10/11 Pro",
"callback": self._key,
"args": ("win10_11pro",),
}
],
[
{
"text": "Win 10/11 LTSC",
"callback": self._key,
"args": ("win10_11enterpriseLTSC",),
}
],
[
{
"text": "Win 8.1 Pro",
"callback": self._key,
"args": ("win8.1pro",),
}
],
[{"text": "Win 8 Pro", "callback": self._key, "args": ("win8pro",)}],
[{"text": "Win 7 Pro", "callback": self._key, "args": ("win7pro",)}],
[
{
"text": "Vista Business",
"callback": self._key,
"args": ("winvistabusiness",),
}
],
[{"text": self.strings["close"], "action": "close"}],
],
)
async def _key(self, call, version):
await call.edit(self.strings["loading"])
keys = await self._get_keys()
key = keys.get(version) if keys else None
await call.edit(
self.strings["winkey"].format(key) if key else self.strings["error"],
reply_markup=[
[{"text": "← Back", "callback": self.winkey}],
[{"text": self.strings["close"], "action": "close"}],
],
)
async def _get_keys(self):
if time.time() - self.cache_time < self.CACHE_TTL:
return self.cache
try:
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(10)
) as session:
async with session.get("https://files.archquise.ru/winkeys.json") as r:
self.cache = await r.json()
self.cache_time = time.time()
return self.cache
except Exception: # noqa: E722
return None

View File

@@ -1 +0,0 @@

File diff suppressed because it is too large Load Diff

View File

@@ -1,100 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: animals
# Description: Random cats and dogs
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api animals
# scope: Api animals 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import logging
import requests
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class animals(loader.Module):
"""Random cats and dogs"""
strings = {
"name": "animals",
"loading": "<b>Generation is underway</b> <emoji document_id=5215484787325676090>🕐</emoji>",
"done": "<b>Here is your salute</b> <emoji document_id=5436246187944460315>❤️</emoji>",
}
strings_ru = {
"loading": "<b>Генерация идет полным ходом</b> <emoji document_id=5215484787325676090>🕐</emoji>",
"done": "<b>Вот ваш результат</b> <emoji document_id=5436246187944460315>❤️</emoji>",
}
# thanks https://github.com/C0dwiz/H.Modules/pull/1
async def get_photo(self, prefix: str) -> str:
response = requests.get(f"https://api.{prefix}.com/v1/images/search")
return response.json()[0]["url"]
@loader.command(
ru_doc="Файлы случайных фотографий кошек",
en_doc="Random photos of cats files",
)
async def fcatcmd(self, message):
await utils.answer(message, self.strings("loading"))
cat_url = await self.get_photo("thecatapi")
await utils.answer_file(
message, cat_url, self.strings("done"), force_document=True
)
@loader.command(
ru_doc="Случайные фотографии собачьих файлов",
en_doc="Random photos of dog files",
)
async def fdogcmd(self, message):
await utils.answer(message, self.strings("loading"))
dog_url = await self.get_photo("thedogapi")
await utils.answer_file(
message, dog_url, self.strings("done"), force_document=True
)
@loader.command(
ru_doc="Случайные фотографии кошек",
en_doc="Random photos of cats",
)
async def catcmd(self, message):
await utils.answer(message, self.strings("loading"))
cat_url = await self.get_photo("thecatapi")
await utils.answer_file(
message, cat_url, self.strings("done"), force_document=False
)
@loader.command(
ru_doc="Случайные фотографии собаки",
en_doc="Random photos of dog",
)
async def dogcmd(self, message):
await utils.answer(message, self.strings("loading"))
dog_url = await self.get_photo("thedogapi")
await utils.answer_file(
message, dog_url, self.strings("done"), force_document=False
)

View File

@@ -1,223 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: CAliases
# Description: Module for custom aliases
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: CAliases
# scope: CAliases 0.0.1
# ---------------------------------------------------------------------------------
import logging
from typing import Dict, Optional
from telethon import types
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class CustomAliasesMod(loader.Module):
"""Module for custom aliases"""
strings = {
"name": "CAliases",
"c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Command <code>{}</code> not found!</b>",
"a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Custom alias <code>{}</code> not found!</b>",
"no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>You must specify two args: alias name and command</b>",
"added": (
"<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <i>{alias}</i> for command "
"<code>{prefix}{cmd}</code> successfully added!</b>\n<b>Use it like:</b> <code>{prefix}{alias}{args}</code>"
),
"argsopt": " [args (optional)]",
"deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Custom alias <code>{}</code> successfully deleted</b>",
"list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Custom aliases ({len}):</b>\n",
"no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>You have no custom aliases!</b>",
}
strings_ru = {
"c404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Команда <code>{}</code> не найдена!</b>",
"a404": "<emoji document_id=5312526098750252863>❌</emoji> <b>Кастомный алиас <code>{}</code> не найден!</b>",
"no_args": "<emoji document_id=5312526098750252863>❌</emoji> <b>Вы должны указать как минимум два аргумента: имя алиаса и команду</b>",
"added": (
"<emoji document_id=5314250708508220914>✅</emoji> <b>Успешно добавил алиас с названием <i>{alias}</i> "
"для команды <code>{prefix}{cmd}</code></b>\n<b>Используй его так:</b> <code>{prefix}{alias}{args}</code>"
),
"argsopt": " [аргументы (необязательно)]",
"deleted": "<emoji document_id=5314250708508220914>✅</emoji> <b>Кастомный алиас <code>{}</code> успешно удалён</b>",
"list": "<emoji document_id=5974492756494519709>🔗</emoji> <b>Кастомные алиасы (всего {len}):</b>\n",
"no_aliases": "<emoji document_id=5312526098750252863>❌</emoji> <b>У вас нет кастомных алиасов!</b>",
}
def __init__(self):
self._aliases_cache: Optional[Dict[str, Dict[str, str]]] = None
self._prefix_cache: Optional[str] = None
def _get_aliases(self) -> Dict[str, Dict[str, str]]:
if self._aliases_cache is None:
self._aliases_cache = self.get("aliases", {})
return self._aliases_cache
def _save_aliases(self, aliases: Dict[str, Dict[str, str]]) -> None:
self.set("aliases", aliases)
self._aliases_cache = aliases
def _get_prefix(self) -> str:
if self._prefix_cache is None:
self._prefix_cache = self.get_prefix()
return self._prefix_cache
def _format_alias_list(self) -> str:
"""Format aliases list for display"""
aliases = self._get_aliases()
if not aliases:
return self.strings["no_aliases"]
lines = [self.strings["list"].format(len=len(aliases))]
for alias_name, alias_data in aliases.items():
cmd = alias_data["command"]
if alias_data.get("args"):
cmd += f" {alias_data['args']}"
lines.append(
f" <emoji document_id=5280726938279749656>▪️</emoji> <code>{alias_name}</code> "
f"<emoji document_id=5960671702059848143>👈</emoji> <code>{cmd}</code>"
)
return "\n".join(lines)
def _validate_command(self, cmd: str) -> bool:
"""Check if command exists"""
return cmd in self.allmodules.commands
def _parse_alias_args(self, message: types.Message) -> tuple:
"""Parse alias command arguments"""
raw_args = utils.get_args_raw(message)
if not raw_args:
return None, None, None
parts = raw_args.split(" ", 2)
if len(parts) < 2:
return None, None, None
alias_name = parts[0]
command = parts[1]
cmd_args = parts[2] if len(parts) > 2 else ""
return alias_name, command, cmd_args
@loader.command(
ru_doc="Получить список всех алиасов",
en_doc=" Get list of all aliases"
)
async def caliasescmd(self, message: types.Message):
"""Get all aliases"""
await utils.answer(message, self._format_alias_list())
@loader.command(
ru_doc="<имя> Удалить алиас",
en_doc="<name> Remove alias"
)
async def rmcaliascmd(self, message: types.Message):
"""Remove alias"""
args = utils.get_args(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
alias_name = args[0]
aliases = self._get_aliases()
if alias_name not in aliases:
return await utils.answer(message, self.strings["a404"].format(alias_name))
del aliases[alias_name]
self._save_aliases(aliases)
await utils.answer(message, self.strings["deleted"].format(alias_name))
@loader.command(
ru_doc="<имя> <команда> [аргументы] Добавить новый алиас (может содержать ключевое слово {args})",
en_doc="<name> <command> [arguments] Add new alias (may contain {args} keyword)",
)
async def caliascmd(self, message: types.Message):
"""Add new alias (may contain {args} keyword)"""
alias_name, command, cmd_args = self._parse_alias_args(message)
if not alias_name or not command:
return await utils.answer(message, self.strings["no_args"])
if not self._validate_command(command):
return await utils.answer(message, self.strings["c404"].format(command))
aliases = self._get_aliases()
aliases[alias_name] = {"command": command, "args": cmd_args}
self._save_aliases(aliases)
prefix = self._get_prefix()
full_cmd = f"{command} {cmd_args}" if cmd_args else command
args_display = self.strings["argsopt"] if "{args}" in cmd_args else ""
await utils.answer(
message,
self.strings["added"].format(
alias=alias_name,
prefix=prefix,
cmd=full_cmd,
args=args_display,
),
)
@loader.tag(only_messages=True, no_media=True, no_inline=True, out=True)
async def watcher(self, message: types.Message):
"""Handle alias execution"""
if not message.raw_text:
return
aliases = self._get_aliases()
prefix = self._get_prefix()
text = message.raw_text
first_word = text.split()[0].lower()
if not first_word.startswith(prefix):
return
alias_name = first_word[len(prefix) :]
if alias_name not in aliases:
return
alias_data = aliases[alias_name]
command = alias_data["command"]
template_args = alias_data.get("args", "")
user_args = utils.get_args_raw(message)
if user_args and template_args:
final_command = f"{command} {template_args}".format(args=user_args)
else:
final_command = f"{command} {template_args}" if template_args else command
try:
await self.allmodules.commands[command](
await utils.answer(message, f"{prefix}{final_command}")
)
except Exception as e:
logger.error(f"Error executing alias '{alias_name}': {e}")

View File

@@ -1,369 +0,0 @@
# ---------------------------------------------------------------------------------
# ░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
# ░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
# ░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
# Name: DelMessTools
# Description: Module to manage and delete your messages in the current chat
# Author: @codrago_m
# ---------------------------------------------------------------------------------
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# ---------------------------------------------------------------------------------
# Author: @codrago
# Commands: nopurge, purgetime, purgelength, purgekeyword, purge
# scope: hikka_only
# meta developer: @codrago_m
# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
# meta pic: https://envs.sh/HJx.webp
# ---------------------------------------------------------------------------------
__version__ = (1, 1, 0)
from telethon.tl.types import Message, DocumentAttributeFilename
from .. import loader, utils
class DelMessTools(loader.Module):
"""Module to manage and delete your messages in the current chat"""
strings = {
"name": "DelMessTools",
"purge_complete": "All your messages have been deleted.",
"purge_reply_complete": "Messages up to the replied message have been deleted.",
"purge_keyword_complete": "Messages containing the keyword have been deleted.",
"purge_time_complete": "Messages within the specified time range have been deleted.",
"purge_media_complete": "All your media messages have been deleted.",
"purge_length_complete": "Messages with the specified length have been deleted.",
"purge_type_complete": "Messages of the specified type have been deleted.",
"enabled": "It's not operational now anyway.",
"disabled": "Operation status changed to disabled.",
"interrupted": "The deletion was interrupted because you changed your mind.",
"none": "You didn't even intend to delete anything here, but anyway it's disabled now.",
"no_args": "Please specify arguments for the command.",
"no_keyword": "Please specify a keyword to delete messages.",
"no_length": "Please specify a valid length.",
"invalid_time": "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS",
"invalid_length": "Please specify a valid number for length.",
}
strings_ru = {
"purge_complete": "Все ваши сообщения были удалены.",
"purge_reply_complete": "Сообщения до указанного ответа были удалены.",
"purge_keyword_complete": "Сообщения, содержащие ключевое слово, были удалены.",
"purge_time_complete": "Сообщения в указанном временном диапазоне были удалены.",
"purge_media_complete": "Все ваши медиа-сообщения были удалены.",
"purge_length_complete": "Сообщения указанной длины были удалены.",
"purge_type_complete": "Сообщения указанного типа были удалены.",
"enabled": "Оно итак сейчас не работает.",
"disabled": "Режим работы изменен на выключено.",
"interrupted": "Удаление было прервано т.к вы передумали.",
"none": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено.",
"no_args": "Пожалуйста, укажите аргументы для команды.",
"no_keyword": "Пожалуйста, укажите ключевое слово для удаления сообщений.",
"no_length": "Пожалуйста, укажите корректную длину.",
"invalid_time": "Неверный формат времени. Используйте формат: YYYY-MM-DD HH:MM:SS",
"invalid_length": "Пожалуйста, укажите корректное число для длины.",
}
def __init__(self):
self.client = None
self.db = None
self.tg_id = None
async def client_ready(self, client, db):
self.client = client
self.db = db
self.tg_id = (await client.get_me()).id
@loader.command(
ru_doc="[reply] [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в текущем чате или только до сообщения, на которое ответили\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
en_doc="[reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
)
async def purge(self, message: Message):
if not self.client or not self.db or not self.tg_id:
return
reply = await message.get_reply_message()
is_last = False
args, types_filter, is_each = self.get_types_filter(message)
try:
entity = await self.client.get_entity(message.chat.id)
is_forum = getattr(entity, "forum", False)
except Exception:
is_forum = False
status = self.db.get(__name__, "status", {})
status[message.chat.id] = True
self.db.set(__name__, "status", status)
deleted_count = 0
async for i in self.client.iter_messages(message.peer_id):
status = self.db.get(__name__, "status", {})
if status.get(message.chat.id, None) is not True:
return await utils.answer(message, self.strings["interrupted"])
if is_forum and not is_each:
try:
if utils.get_topic(message) != utils.get_topic(i):
continue
except Exception:
pass
if i.from_id == self.tg_id and self.is_valid_type(i, types_filter):
if reply:
if is_last:
break
if i.id == reply.id:
is_last = True
try:
await self.client.delete_messages(message.peer_id, [i.id])
deleted_count += 1
except Exception:
pass
if reply:
await utils.answer(message, self.strings["purge_reply_complete"])
else:
await utils.answer(message, self.strings["purge_complete"])
@loader.command(
ru_doc="<ключевое слово> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения с указанным ключевым словом в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
en_doc="<keyword> [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
)
async def purgekeyword(self, message: Message):
if not self.client or not self.db or not self.tg_id:
return
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
args, types_filter, is_each = self.get_types_filter(message)
if not args:
return await utils.answer(message, self.strings["no_keyword"])
try:
entity = await self.client.get_entity(message.chat.id)
is_forum = getattr(entity, "forum", False)
except Exception:
is_forum = False
status = self.db.get(__name__, "status", {})
status[message.chat.id] = True
self.db.set(__name__, "status", status)
deleted_count = 0
async for i in self.client.iter_messages(message.peer_id):
status = self.db.get(__name__, "status", {})
if status.get(message.chat.id, None) is not True:
return await utils.answer(message, self.strings["interrupted"])
if is_forum and not is_each:
try:
if utils.get_topic(message) != utils.get_topic(i):
continue
except Exception:
pass
if (
i.from_id == self.tg_id
and args.lower() in (i.text or "").lower()
and self.is_valid_type(i, types_filter)
):
try:
await self.client.delete_messages(message.chat.id, [i.id])
deleted_count += 1
except Exception:
pass
await utils.answer(message, self.strings["purge_keyword_complete"])
@loader.command(
ru_doc="<начальное время> <конечное время> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в указанном временном диапазоне в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется\n Формат времени: YYYY-MM-DD HH:MM:SS",
en_doc="<start_time> <end_time> [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored\n Time format: YYYY-MM-DD HH:MM:SS",
)
async def purgetime(self, message: Message):
if not self.client or not self.db or not self.tg_id:
return
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
args, types_filter, is_each = self.get_types_filter(message)
args = args.split()
if not args or len(args) < 2:
return await utils.answer(message, self.strings["no_args"])
from datetime import datetime
try:
start_time = datetime.strptime(args[0], "%Y-%m-%d %H:%M:%S")
end_time = datetime.strptime(args[1], "%Y-%m-%d %H:%M:%S")
except ValueError:
return await utils.answer(message, self.strings["invalid_time"])
try:
entity = await self.client.get_entity(message.chat.id)
is_forum = getattr(entity, "forum", False)
except Exception:
is_forum = False
status = self.db.get(__name__, "status", {})
status[message.chat.id] = True
self.db.set(__name__, "status", status)
deleted_count = 0
async for i in self.client.iter_messages(message.peer_id):
status = self.db.get(__name__, "status", {})
if status.get(message.chat.id, None) is not True:
return await utils.answer(message, self.strings["interrupted"])
if is_forum and not is_each:
try:
if utils.get_topic(message) != utils.get_topic(i):
continue
except Exception:
pass
if (
i.from_id == self.tg_id
and start_time <= i.date <= end_time
and self.is_valid_type(i, types_filter)
):
try:
await self.client.delete_messages(message.peer_id, [i.id])
deleted_count += 1
except Exception:
pass
await utils.answer(message, self.strings["purge_time_complete"])
@loader.command(
ru_doc="<длина> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения указанной длины в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется",
en_doc="<length> [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored",
)
async def purgelength(self, message: Message):
if not self.client or not self.db or not self.tg_id:
return
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings["no_args"])
args, types_filter, is_each = self.get_types_filter(message)
if not args:
return await utils.answer(message, self.strings["no_length"])
try:
length = int(args)
except ValueError:
return await utils.answer(message, self.strings["invalid_length"])
try:
entity = await self.client.get_entity(message.chat.id)
is_forum = getattr(entity, "forum", False)
except Exception:
is_forum = False
status = self.db.get(__name__, "status", {})
status[message.chat.id] = True
self.db.set(__name__, "status", status)
deleted_count = 0
async for i in self.client.iter_messages(message.peer_id):
status = self.db.get(__name__, "status", {})
if status.get(message.chat.id, None) is not True:
return await utils.answer(message, self.strings["interrupted"])
if is_forum and not is_each:
try:
if utils.get_topic(message) != utils.get_topic(i):
continue
except Exception:
pass
if (
i.from_id == self.tg_id
and len(i.text or "") == length
and self.is_valid_type(i, types_filter)
):
try:
await self.client.delete_messages(message.peer_id, [i.id])
deleted_count += 1
except Exception:
pass
await utils.answer(message, self.strings["purge_length_complete"])
@loader.command(
ru_doc="Прервать процесс удаления\nИспользуйте в чате, где вы ранее начали удаление",
en_doc="Interrupt the deletion process\nUse in the chat where you've previously started deletion",
)
async def nopurge(self, message: Message):
if not self.db:
return
chat_id = utils.get_chat_id(message)
status = self.db.get(__name__, "status", {})
_status = status.get(chat_id, None)
status[chat_id] = False
self.db.set(__name__, "status", status)
if _status is True:
await utils.answer(message, self.strings["disabled"])
elif _status is False:
await utils.answer(message, self.strings["enabled"])
else:
await utils.answer(message, self.strings["none"])
def get_types_filter(self, message: Message):
"""Get the types filter from the command arguments."""
args_raw = utils.get_args_raw(message)
if not args_raw:
return "", [], False
args = args_raw.split()
types_filter = []
valid_types = ["-img", "-voice", "-file", "-all"]
is_each = "-all" in args
_args = args_raw
args_ = ""
for i, arg in enumerate(args):
if arg in valid_types:
_args = " ".join(args[:i])
args_ = " ".join(args[i:])
break
if "-img" in args_:
types_filter.append("img")
if "-voice" in args_:
types_filter.append("voice")
if "-file" in args_:
types_filter.append("file")
if "-all" in args_:
is_each = True
return _args, types_filter, is_each
def is_valid_type(self, message: Message, types_filter):
"""Check if the message matches the specified types filter."""
if not types_filter:
return True # No filtering means all types are valid
if "img" in types_filter and hasattr(message, "photo") and message.photo:
return True
if "voice" in types_filter and hasattr(message, "voice") and message.voice:
return True
if "file" in types_filter and hasattr(message, "document") and message.document:
for attr in getattr(message.document, "attributes", []):
if isinstance(attr, DocumentAttributeFilename):
return True
return False

View File

@@ -1,84 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: face
# Description: Random face
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api face
# scope: Api face 0.0.1
# requires: aiohttp
# ---------------------------------------------------------------------------------
import logging
import aiohttp
import re
import random
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class face(loader.Module):
"""random face"""
strings = {
"name": "face",
"loading": (
"<emoji document_id=5348399448017871250>🔍</emoji> I'm looking for you kaomoji"
),
"random_face": (
"<emoji document_id=5208878706717636743>🗿</emoji> Here is your random one kaomoji\n<code>{}</code>"
),
"error": "An error has occurred!",
}
strings_ru = {
"loading": (
"<emoji document_id=5348399448017871250>🔍</emoji> Ищю вам kaomoji"
),
"random_face": (
"<emoji document_id=5208878706717636743>🗿</emoji> Вот ваш рандомный kaomoji\n<code>{}</code>"
),
"error": "Произошла ошибка!",
}
@loader.command(
ru_doc="Рандом kaomoji",
en_doc="Random kaomoji",
)
async def rfacecmd(self, message):
await utils.answer(message, self.strings("loading"))
url = "https://files.archquise.ru/kaomoji.txt"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.text()
kaomoji_list = [s.strip() for s in re.split(r'[\t\r\n]+', data) if s.strip()]
kaomoji = random.choice(kaomoji_list)
await utils.answer(
message, self.strings("random_face").format(kaomoji)
)
else:
await utils.answer(message, self.strings("error"))

Some files were not shown because too many files have changed in this diff Show More