kroxy 1.0.1__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kroxy-1.0.2/PKG-INFO +627 -0
- kroxy-1.0.2/README.md +601 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/__init__.py +27 -10
- kroxy-1.0.2/kroxy/website/__init__.py +9 -0
- kroxy-1.0.2/kroxy/website/fetch.py +209 -0
- kroxy-1.0.2/kroxy/website/scraper.py +325 -0
- kroxy-1.0.2/kroxy/website/utils.py +287 -0
- kroxy-1.0.2/kroxy.egg-info/PKG-INFO +627 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy.egg-info/SOURCES.txt +4 -1
- {kroxy-1.0.1 → kroxy-1.0.2}/pyproject.toml +2 -2
- kroxy-1.0.1/PKG-INFO +0 -507
- kroxy-1.0.1/README.md +0 -481
- kroxy-1.0.1/kroxy/website/__init__.py +0 -5
- kroxy-1.0.1/kroxy.egg-info/PKG-INFO +0 -507
- {kroxy-1.0.1 → kroxy-1.0.2}/LICENSE +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/MANIFEST.in +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/__init__.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/antinuke.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/api.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/checkers.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/commands.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/giveaway.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/music.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy/discord/utils.py +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy.egg-info/dependency_links.txt +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy.egg-info/requires.txt +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/kroxy.egg-info/top_level.txt +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/setup.cfg +0 -0
- {kroxy-1.0.1 → kroxy-1.0.2}/setup.py +0 -0
kroxy-1.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kroxy
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A powerful Python toolkit for building automation, integrations, and bots.
|
|
5
|
+
Author-email: kroxy <kroxy@example.com>
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/kroxy
|
|
8
|
+
Keywords: bot,automation,antinuke,giveaway,music,scraper,toolkit,utility
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Internet
|
|
17
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build; extra == "dev"
|
|
24
|
+
Requires-Dist: twine; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# kroxy
|
|
28
|
+
|
|
29
|
+
> A powerful Python toolkit for building automation, integrations, and bots.
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/kroxy/)
|
|
32
|
+
[](https://pypi.org/project/kroxy/)
|
|
33
|
+
[](https://pypi.org/project/kroxy/)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install kroxy
|
|
41
|
+
pip install --upgrade kroxy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Import Style
|
|
47
|
+
|
|
48
|
+
Everything in `kroxy` is importable directly from the top level.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# Everything from one place
|
|
52
|
+
import kroxy
|
|
53
|
+
|
|
54
|
+
# Discord
|
|
55
|
+
antinuke = kroxy.AntiNuke(whitelist=[OWNER_ID])
|
|
56
|
+
manager = kroxy.GiveawayManager()
|
|
57
|
+
api = kroxy.DiscordAPI(token="Bot TOKEN")
|
|
58
|
+
embed = kroxy.Utils.build_embed(title="Hello", color=0x5865F2)
|
|
59
|
+
|
|
60
|
+
# Website
|
|
61
|
+
async with kroxy.Fetcher() as f:
|
|
62
|
+
html = await f.get("https://example.com")
|
|
63
|
+
|
|
64
|
+
title = kroxy.Scraper.get_title(html)
|
|
65
|
+
slug = kroxy.WebUtils.slugify("Hello World")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Or import only what you need
|
|
70
|
+
from kroxy import AntiNuke, Checkers, Utils
|
|
71
|
+
from kroxy import GiveawayManager, MusicPlayerManager
|
|
72
|
+
from kroxy import DiscordAPI, SlashCommand, PrefixCommand
|
|
73
|
+
from kroxy import Fetcher, Scraper, WebUtils
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# Submodule access also works
|
|
78
|
+
from kroxy import discord, website
|
|
79
|
+
|
|
80
|
+
discord.AntiNuke(...)
|
|
81
|
+
website.Fetcher()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Package Structure
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
kroxy/
|
|
90
|
+
├── discord/
|
|
91
|
+
│ ├── api.py ← Async Discord REST API client (v10)
|
|
92
|
+
│ ├── commands.py ← Slash and prefix command builders + registry
|
|
93
|
+
│ ├── utils.py ← Embeds, mentions, timestamps, permissions, text formatting
|
|
94
|
+
│ ├── antinuke.py ← Real-time nuke detection with auto-punishment
|
|
95
|
+
│ ├── checkers.py ← Permission, role, and hierarchy validation
|
|
96
|
+
│ ├── giveaway.py ← Weighted giveaway engine with scheduling and reroll
|
|
97
|
+
│ └── music.py ← Multi-guild music queue and player state manager
|
|
98
|
+
└── website/
|
|
99
|
+
├── fetch.py ← Async HTTP GET/POST/download client
|
|
100
|
+
├── scraper.py ← HTML parsing: links, images, meta, tables, text
|
|
101
|
+
└── utils.py ← URL validation, parsing, slugify, file type detection
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Module Reference
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `DiscordAPI` — Discord REST API Client
|
|
111
|
+
|
|
112
|
+
Async HTTP wrapper for the Discord REST API v10.
|
|
113
|
+
|
|
114
|
+
**Constructor:** `DiscordAPI(token: str, bot: bool = True)`
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from kroxy import DiscordAPI
|
|
118
|
+
|
|
119
|
+
api = DiscordAPI(token="Bot YOUR_TOKEN")
|
|
120
|
+
|
|
121
|
+
# Guild & Channel
|
|
122
|
+
guild = await api.get_guild(GUILD_ID)
|
|
123
|
+
channel = await api.get_channel(CHANNEL_ID)
|
|
124
|
+
user = await api.get_user(USER_ID)
|
|
125
|
+
|
|
126
|
+
# Messaging
|
|
127
|
+
await api.send_message(CHANNEL_ID, content="Hello!")
|
|
128
|
+
await api.send_message(CHANNEL_ID, embed=embed_dict)
|
|
129
|
+
await api.send_message(CHANNEL_ID, content="Hi", components=[...])
|
|
130
|
+
await api.delete_message(CHANNEL_ID, MESSAGE_ID)
|
|
131
|
+
|
|
132
|
+
# Moderation
|
|
133
|
+
await api.ban_member(GUILD_ID, USER_ID, reason="Spam", delete_message_days=1)
|
|
134
|
+
await api.unban_member(GUILD_ID, USER_ID)
|
|
135
|
+
await api.kick_member(GUILD_ID, USER_ID, reason="AFK")
|
|
136
|
+
|
|
137
|
+
# Roles
|
|
138
|
+
await api.add_role(GUILD_ID, USER_ID, ROLE_ID)
|
|
139
|
+
await api.remove_role(GUILD_ID, USER_ID, ROLE_ID)
|
|
140
|
+
|
|
141
|
+
# Channels
|
|
142
|
+
await api.create_channel(GUILD_ID, name="general", channel_type=0)
|
|
143
|
+
await api.delete_channel(CHANNEL_ID)
|
|
144
|
+
|
|
145
|
+
# Audit & Invites & Webhooks
|
|
146
|
+
await api.get_audit_logs(GUILD_ID, limit=50)
|
|
147
|
+
await api.get_invites(GUILD_ID)
|
|
148
|
+
await api.delete_invite("invite_code")
|
|
149
|
+
await api.get_webhooks(GUILD_ID)
|
|
150
|
+
await api.delete_webhook(WEBHOOK_ID)
|
|
151
|
+
|
|
152
|
+
await api.close()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### `AntiNuke` — Anti-Nuke System
|
|
158
|
+
|
|
159
|
+
Detects mass destructive actions per user and triggers automatic punishment.
|
|
160
|
+
|
|
161
|
+
**Constructor:** `AntiNuke(whitelist: list = [], limits: dict = {})`
|
|
162
|
+
|
|
163
|
+
**Default Limits:**
|
|
164
|
+
|
|
165
|
+
| Action | Threshold | Window |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `ban` | 3 | 10s |
|
|
168
|
+
| `kick` | 3 | 10s |
|
|
169
|
+
| `channel_delete` | 3 | 10s |
|
|
170
|
+
| `channel_create` | 5 | 10s |
|
|
171
|
+
| `role_delete` | 3 | 10s |
|
|
172
|
+
| `role_create` | 5 | 10s |
|
|
173
|
+
| `webhook_create` | 3 | 10s |
|
|
174
|
+
| `webhook_delete` | 3 | 10s |
|
|
175
|
+
| `bot_add` | 2 | 30s |
|
|
176
|
+
| `mass_mention` | 5 | 5s |
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from kroxy import AntiNuke
|
|
180
|
+
|
|
181
|
+
antinuke = AntiNuke(
|
|
182
|
+
whitelist=[OWNER_ID],
|
|
183
|
+
limits={"ban": (2, 5)} # override: 2 bans in 5s triggers
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
async def on_nuke(action, user_id, guild):
|
|
187
|
+
print(f"[NUKE] {action} by {user_id}")
|
|
188
|
+
|
|
189
|
+
antinuke.on_trigger = on_nuke
|
|
190
|
+
antinuke.punishment = "ban" # "ban" | "kick" | "strip_roles"
|
|
191
|
+
|
|
192
|
+
# Wire to your event system:
|
|
193
|
+
await antinuke.on_member_ban(user_id=uid, guild=guild)
|
|
194
|
+
await antinuke.on_member_kick(user_id=uid, guild=guild)
|
|
195
|
+
await antinuke.on_channel_delete(user_id=uid, guild=guild)
|
|
196
|
+
await antinuke.on_channel_create(user_id=uid, guild=guild)
|
|
197
|
+
await antinuke.on_role_delete(user_id=uid, guild=guild)
|
|
198
|
+
await antinuke.on_role_create(user_id=uid, guild=guild)
|
|
199
|
+
await antinuke.on_webhook_create(user_id=uid, guild=guild)
|
|
200
|
+
await antinuke.on_webhook_delete(user_id=uid, guild=guild)
|
|
201
|
+
await antinuke.on_bot_add(user_id=uid, guild=guild)
|
|
202
|
+
await antinuke.on_mass_mention(user_id=uid, guild=guild)
|
|
203
|
+
|
|
204
|
+
# Whitelist management
|
|
205
|
+
antinuke.add_whitelist(MOD_ID)
|
|
206
|
+
antinuke.remove_whitelist(MOD_ID)
|
|
207
|
+
antinuke.reset_user(USER_ID)
|
|
208
|
+
antinuke.get_stats() # current thresholds dict
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `Checkers` — Permission & Role Validation
|
|
214
|
+
|
|
215
|
+
All methods raise `CheckFailed` on failure, return `True` on success.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from kroxy import Checkers, CheckFailed
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
Checkers.require_admin(member.permissions)
|
|
222
|
+
Checkers.require_manage_guild(member.permissions)
|
|
223
|
+
Checkers.require_manage_roles(member.permissions)
|
|
224
|
+
Checkers.require_ban_members(member.permissions)
|
|
225
|
+
Checkers.require_kick_members(member.permissions)
|
|
226
|
+
Checkers.require_manage_channels(member.permissions)
|
|
227
|
+
Checkers.require_manage_messages(member.permissions)
|
|
228
|
+
Checkers.require_manage_webhooks(member.permissions)
|
|
229
|
+
|
|
230
|
+
Checkers.require_role(member.role_ids, MOD_ROLE_ID, "Moderator")
|
|
231
|
+
Checkers.require_any_role(member.role_ids, [MOD_ROLE_ID, ADMIN_ROLE_ID])
|
|
232
|
+
Checkers.has_all_roles(member.role_ids, [ROLE_A, ROLE_B])
|
|
233
|
+
|
|
234
|
+
Checkers.check_hierarchy(
|
|
235
|
+
executor_top_role_pos=member.top_role.position,
|
|
236
|
+
target_top_role_pos=target.top_role.position,
|
|
237
|
+
bot_top_role_pos=bot.top_role.position,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
Checkers.require_guild_owner(user_id, guild.owner_id)
|
|
241
|
+
Checkers.require_guild_only(ctx.guild_id)
|
|
242
|
+
Checkers.require_dm_only(ctx.guild_id)
|
|
243
|
+
Checkers.block_bots(ctx.author.bot)
|
|
244
|
+
Checkers.require_nsfw(channel.nsfw)
|
|
245
|
+
|
|
246
|
+
except CheckFailed as e:
|
|
247
|
+
await ctx.send(f"❌ {e.message}")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### `Utils` — Discord Utilities
|
|
253
|
+
|
|
254
|
+
Embed builder, mentions, timestamps, permissions, and text formatters.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from kroxy import Utils
|
|
258
|
+
|
|
259
|
+
# Embed builder
|
|
260
|
+
embed = Utils.build_embed(
|
|
261
|
+
title="Report",
|
|
262
|
+
description="Weekly summary",
|
|
263
|
+
color=0x5865F2,
|
|
264
|
+
fields=[
|
|
265
|
+
{"name": "Members", "value": "1,200", "inline": True},
|
|
266
|
+
{"name": "Messages", "value": "45,000", "inline": True},
|
|
267
|
+
],
|
|
268
|
+
footer="kroxy",
|
|
269
|
+
thumbnail="https://example.com/icon.png",
|
|
270
|
+
image="https://example.com/banner.png",
|
|
271
|
+
author_name="Bot",
|
|
272
|
+
author_icon="https://example.com/bot.png",
|
|
273
|
+
timestamp=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Mentions
|
|
277
|
+
Utils.mention_user(123456) # <@123456>
|
|
278
|
+
Utils.mention_role(654321) # <@&654321>
|
|
279
|
+
Utils.mention_channel(999999) # <#999999>
|
|
280
|
+
Utils.parse_mention("<@123456>") # 123456
|
|
281
|
+
|
|
282
|
+
# Timestamps
|
|
283
|
+
Utils.discord_timestamp(dt, style="R") # <t:...:R> relative
|
|
284
|
+
Utils.discord_timestamp(dt, style="F") # <t:...:F> full
|
|
285
|
+
Utils.time_until(3725) # "1 hour, 2 minutes, 5 seconds"
|
|
286
|
+
Utils.snowflake_to_timestamp(id) # datetime object
|
|
287
|
+
|
|
288
|
+
# Permissions
|
|
289
|
+
Utils.has_permission(bitfield, "administrator") # True/False
|
|
290
|
+
Utils.has_permission(bitfield, "ban_members")
|
|
291
|
+
Utils.permissions_list(bitfield) # list of names
|
|
292
|
+
|
|
293
|
+
# Text formatting
|
|
294
|
+
Utils.truncate(text, max_length=2048)
|
|
295
|
+
Utils.code_block("print('hi')", language="python")
|
|
296
|
+
Utils.inline_code("var")
|
|
297
|
+
Utils.bold("text") # **text**
|
|
298
|
+
Utils.italic("text") # *text*
|
|
299
|
+
Utils.underline("text") # __text__
|
|
300
|
+
Utils.strikethrough("text") # ~~text~~
|
|
301
|
+
Utils.spoiler("text") # ||text||
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### `GiveawayManager` — Giveaway Engine
|
|
307
|
+
|
|
308
|
+
Weighted giveaway system with bonus roles, auto-scheduling, and reroll.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from kroxy import GiveawayManager
|
|
312
|
+
|
|
313
|
+
manager = GiveawayManager()
|
|
314
|
+
|
|
315
|
+
async def on_end(giveaway, winners):
|
|
316
|
+
print(f"Winners of '{giveaway.prize}': {winners}")
|
|
317
|
+
|
|
318
|
+
manager.on_end = on_end
|
|
319
|
+
|
|
320
|
+
giveaway = await manager.create(
|
|
321
|
+
prize="Discord Nitro",
|
|
322
|
+
host_id=HOST_ID,
|
|
323
|
+
channel_id=CHANNEL_ID,
|
|
324
|
+
guild_id=GUILD_ID,
|
|
325
|
+
duration=86400, # seconds
|
|
326
|
+
winner_count=3,
|
|
327
|
+
required_role_id=MEMBER_ROLE_ID,
|
|
328
|
+
bonus_roles={
|
|
329
|
+
BOOSTER_ROLE_ID: 2, # +2 extra entries
|
|
330
|
+
VIP_ROLE_ID: 4, # +4 extra entries
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
giveaway.add_entry(user_id=555, role_ids=[BOOSTER_ROLE_ID])
|
|
335
|
+
giveaway.remove_entry(user_id=555)
|
|
336
|
+
|
|
337
|
+
giveaway.is_active # True/False
|
|
338
|
+
giveaway.time_remaining # seconds
|
|
339
|
+
giveaway.total_entries # weighted count
|
|
340
|
+
giveaway.participant_count # unique users
|
|
341
|
+
giveaway.giveaway_id # "A1B2C3D4"
|
|
342
|
+
giveaway.winners # list of user IDs after end
|
|
343
|
+
|
|
344
|
+
await manager.end_now(giveaway.giveaway_id)
|
|
345
|
+
await manager.cancel(giveaway.giveaway_id)
|
|
346
|
+
new_winners = giveaway.reroll()
|
|
347
|
+
|
|
348
|
+
manager.get(giveaway_id)
|
|
349
|
+
manager.get_by_message(message_id)
|
|
350
|
+
manager.all_active()
|
|
351
|
+
manager.all_ended()
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### `MusicPlayerManager` — Music Player
|
|
357
|
+
|
|
358
|
+
Multi-guild music queue and player state manager.
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from kroxy import MusicPlayerManager, Track, LoopMode
|
|
362
|
+
|
|
363
|
+
manager = MusicPlayerManager()
|
|
364
|
+
player = manager.get_or_create(
|
|
365
|
+
guild_id=GUILD_ID,
|
|
366
|
+
channel_id=VOICE_CHANNEL_ID,
|
|
367
|
+
text_channel_id=TEXT_CHANNEL_ID,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
track = Track(
|
|
371
|
+
title="Song Name",
|
|
372
|
+
url="https://youtube.com/watch?v=...",
|
|
373
|
+
stream_url="https://audio.stream/file.mp3",
|
|
374
|
+
duration=240,
|
|
375
|
+
requester_id=USER_ID,
|
|
376
|
+
thumbnail="https://img.youtube.com/vi/.../0.jpg",
|
|
377
|
+
source="youtube",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Queue
|
|
381
|
+
player.queue.add(track)
|
|
382
|
+
player.queue.add_next(track)
|
|
383
|
+
player.queue.shuffle()
|
|
384
|
+
player.queue.remove(index=0)
|
|
385
|
+
player.queue.move(from_index=2, to_index=0)
|
|
386
|
+
player.queue.clear()
|
|
387
|
+
len(player.queue)
|
|
388
|
+
player.queue.total_duration
|
|
389
|
+
player.queue.is_empty
|
|
390
|
+
|
|
391
|
+
# Playback
|
|
392
|
+
await player.play_next()
|
|
393
|
+
await player.skip()
|
|
394
|
+
player.toggle_pause() # returns new paused state
|
|
395
|
+
player.set_volume(0.75) # 0.0 – 2.0
|
|
396
|
+
player.set_loop("track") # "none" | "track" | "queue"
|
|
397
|
+
player.stop()
|
|
398
|
+
|
|
399
|
+
# State
|
|
400
|
+
player.current # Track or None
|
|
401
|
+
player.paused # bool
|
|
402
|
+
player.volume # float
|
|
403
|
+
player.position # seconds elapsed
|
|
404
|
+
player.loop_mode # LoopMode enum
|
|
405
|
+
player.get_state() # full state dict
|
|
406
|
+
|
|
407
|
+
# Events
|
|
408
|
+
player.on_track_start = async_fn # called with (track, player)
|
|
409
|
+
player.on_track_end = async_fn # called with (track, player)
|
|
410
|
+
player.on_queue_empty = async_fn # called with (guild_id)
|
|
411
|
+
|
|
412
|
+
manager.remove(guild_id=GUILD_ID)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
### `SlashCommand` / `PrefixCommand` — Command Builders
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
from kroxy import SlashCommand, PrefixCommand, Option, CommandRegistry
|
|
421
|
+
|
|
422
|
+
@SlashCommand.decorator(
|
|
423
|
+
name="warn",
|
|
424
|
+
description="Warn a member",
|
|
425
|
+
options=[
|
|
426
|
+
Option("user", "Target member", option_type="user", required=True),
|
|
427
|
+
Option("reason", "Reason", required=False),
|
|
428
|
+
],
|
|
429
|
+
permissions="8",
|
|
430
|
+
)
|
|
431
|
+
async def warn(interaction, user, reason="No reason"):
|
|
432
|
+
...
|
|
433
|
+
|
|
434
|
+
@PrefixCommand.decorator(
|
|
435
|
+
name="ban",
|
|
436
|
+
aliases=["b", "yeet"],
|
|
437
|
+
cooldown=5,
|
|
438
|
+
permissions=["ban_members"],
|
|
439
|
+
)
|
|
440
|
+
async def ban(ctx, member, *, reason="No reason"):
|
|
441
|
+
...
|
|
442
|
+
|
|
443
|
+
warn.to_dict() # export for Discord API registration
|
|
444
|
+
|
|
445
|
+
registry = CommandRegistry()
|
|
446
|
+
registry.add_slash(warn)
|
|
447
|
+
registry.add_prefix(ban)
|
|
448
|
+
registry.get_slash("warn")
|
|
449
|
+
registry.get_prefix("b") # alias lookup works
|
|
450
|
+
registry.all_slash()
|
|
451
|
+
registry.all_prefix()
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### `Fetcher` — Async HTTP Client
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
from kroxy import Fetcher
|
|
460
|
+
|
|
461
|
+
# As context manager (auto-closes session)
|
|
462
|
+
async with Fetcher() as f:
|
|
463
|
+
html = await f.get("https://example.com")
|
|
464
|
+
data = await f.get_json("https://api.example.com/data")
|
|
465
|
+
response = await f.post("https://example.com/form", data={"key": "value"})
|
|
466
|
+
result = await f.post_json("https://api.example.com", json={"q": "hello"})
|
|
467
|
+
headers = await f.head("https://example.com")
|
|
468
|
+
code = await f.status("https://example.com") # 200
|
|
469
|
+
ok = await f.is_reachable("https://example.com") # True/False
|
|
470
|
+
path = await f.download("https://example.com/file.zip", save_path="./file.zip")
|
|
471
|
+
raw = await f.get_bytes("https://example.com/image.png")
|
|
472
|
+
final = await f.resolve_redirect("https://bit.ly/abc")
|
|
473
|
+
|
|
474
|
+
# With custom headers and timeout
|
|
475
|
+
async with Fetcher(headers={"Authorization": "Bearer TOKEN"}, timeout=30) as f:
|
|
476
|
+
data = await f.get_json("https://api.example.com/protected")
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
### `Scraper` — HTML Parser
|
|
482
|
+
|
|
483
|
+
All methods are static — pass raw HTML strings, get structured data back.
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
from kroxy import Scraper, Fetcher
|
|
487
|
+
|
|
488
|
+
async with Fetcher() as f:
|
|
489
|
+
html = await f.get("https://example.com")
|
|
490
|
+
|
|
491
|
+
Scraper.get_title(html) # "Example Domain"
|
|
492
|
+
Scraper.get_description(html) # meta description
|
|
493
|
+
Scraper.get_meta(html) # {"og:title": ..., "description": ...}
|
|
494
|
+
Scraper.get_og(html) # {"og:title": ..., "og:image": ...}
|
|
495
|
+
|
|
496
|
+
Scraper.get_links(html, base_url="https://example.com")
|
|
497
|
+
# [{"href": "https://example.com/page", "text": "Click here"}, ...]
|
|
498
|
+
|
|
499
|
+
Scraper.get_links(html, base_url="https://example.com", internal_only=True)
|
|
500
|
+
Scraper.get_links(html, base_url="https://example.com", external_only=True)
|
|
501
|
+
|
|
502
|
+
Scraper.get_images(html, base_url="https://example.com")
|
|
503
|
+
# [{"src": "...", "alt": "...", "width": "...", "height": "..."}, ...]
|
|
504
|
+
|
|
505
|
+
Scraper.get_tables(html)
|
|
506
|
+
# [[[row1col1, row1col2], [row2col1, row2col2]], ...]
|
|
507
|
+
|
|
508
|
+
Scraper.get_text(html) # plain text, no tags
|
|
509
|
+
Scraper.find_by_tag(html, "h1") # ["Heading 1", "Heading 2"]
|
|
510
|
+
Scraper.find_by_id(html, "main") # inner HTML of id="main"
|
|
511
|
+
Scraper.find_by_class(html, "card") # list of inner HTML strings
|
|
512
|
+
Scraper.extract_emails(html) # ["hello@example.com", ...]
|
|
513
|
+
Scraper.extract_phone_numbers(html) # ["+1 800 555 0000", ...]
|
|
514
|
+
Scraper.word_count(html) # 342
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
### `WebUtils` — URL Utilities
|
|
520
|
+
|
|
521
|
+
All methods are static.
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
from kroxy import WebUtils
|
|
525
|
+
|
|
526
|
+
# Validation
|
|
527
|
+
WebUtils.is_valid_url("https://example.com") # True
|
|
528
|
+
WebUtils.is_https("https://example.com") # True
|
|
529
|
+
WebUtils.is_http("http://example.com") # True
|
|
530
|
+
|
|
531
|
+
# Parsing
|
|
532
|
+
WebUtils.get_domain("https://www.example.com/path") # "www.example.com"
|
|
533
|
+
WebUtils.get_base_domain("https://www.example.com/path") # "example.com"
|
|
534
|
+
WebUtils.get_scheme("https://example.com") # "https"
|
|
535
|
+
WebUtils.get_path("https://example.com/foo/bar") # "/foo/bar"
|
|
536
|
+
WebUtils.get_query_params("https://example.com?q=hi&page=2")
|
|
537
|
+
# {"q": ["hi"], "page": ["2"]}
|
|
538
|
+
WebUtils.get_tld("https://www.example.co.uk") # "uk"
|
|
539
|
+
|
|
540
|
+
# Transformation
|
|
541
|
+
WebUtils.add_query_params("https://example.com", {"q": "hello", "page": "2"})
|
|
542
|
+
WebUtils.remove_query_params("https://example.com?q=hi") # "https://example.com"
|
|
543
|
+
WebUtils.join("https://example.com/foo", "../bar") # "https://example.com/bar"
|
|
544
|
+
WebUtils.to_https("http://example.com") # "https://example.com"
|
|
545
|
+
WebUtils.normalize("www.example.com/path/") # "https://example.com/path"
|
|
546
|
+
WebUtils.encode("hello world!") # "hello%20world%21"
|
|
547
|
+
|
|
548
|
+
# Slug
|
|
549
|
+
WebUtils.slugify("Hello World! 123") # "hello-world-123"
|
|
550
|
+
|
|
551
|
+
# File type detection
|
|
552
|
+
WebUtils.get_file_extension("https://example.com/image.png?v=1") # "png"
|
|
553
|
+
WebUtils.is_image_url("https://example.com/photo.jpg") # True
|
|
554
|
+
WebUtils.is_video_url("https://example.com/video.mp4") # True
|
|
555
|
+
WebUtils.is_audio_url("https://example.com/track.mp3") # True
|
|
556
|
+
|
|
557
|
+
# Misc
|
|
558
|
+
WebUtils.extract_urls("Visit https://example.com or https://test.com now")
|
|
559
|
+
# ["https://example.com", "https://test.com"]
|
|
560
|
+
WebUtils.is_same_domain("https://example.com/a", "https://example.com/b") # True
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## Combined Example
|
|
566
|
+
|
|
567
|
+
```python
|
|
568
|
+
import kroxy
|
|
569
|
+
import asyncio
|
|
570
|
+
|
|
571
|
+
async def main():
|
|
572
|
+
# Fetch a page
|
|
573
|
+
async with kroxy.Fetcher() as f:
|
|
574
|
+
html = await f.get("https://example.com")
|
|
575
|
+
api_data = await f.get_json("https://api.example.com/stats")
|
|
576
|
+
|
|
577
|
+
# Scrape it
|
|
578
|
+
title = kroxy.Scraper.get_title(html)
|
|
579
|
+
links = kroxy.Scraper.get_links(html, base_url="https://example.com")
|
|
580
|
+
emails = kroxy.Scraper.extract_emails(html)
|
|
581
|
+
|
|
582
|
+
# Validate a URL
|
|
583
|
+
if kroxy.WebUtils.is_valid_url("https://example.com"):
|
|
584
|
+
slug = kroxy.WebUtils.slugify(title)
|
|
585
|
+
|
|
586
|
+
# Send results to Discord
|
|
587
|
+
api = kroxy.DiscordAPI(token="Bot TOKEN")
|
|
588
|
+
embed = kroxy.Utils.build_embed(
|
|
589
|
+
title=title,
|
|
590
|
+
description=f"Found {len(links)} links, {len(emails)} emails",
|
|
591
|
+
color=0x5865F2,
|
|
592
|
+
timestamp=True,
|
|
593
|
+
)
|
|
594
|
+
await api.send_message(CHANNEL_ID, embed=embed)
|
|
595
|
+
await api.close()
|
|
596
|
+
|
|
597
|
+
asyncio.run(main())
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Requirements
|
|
603
|
+
|
|
604
|
+
- Python **3.9+**
|
|
605
|
+
- `aiohttp >= 3.8.0`
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Versioning
|
|
610
|
+
|
|
611
|
+
See [VERSIONS.md](https://pypi.org/project/kroxy/) for the full changelog.
|
|
612
|
+
|
|
613
|
+
| Version | What changed |
|
|
614
|
+
|---|---|
|
|
615
|
+
| 1.0.2 | `kroxy.website` — Fetcher, Scraper, WebUtils |
|
|
616
|
+
| 1.0.1 | All classes importable from `kroxy` top level |
|
|
617
|
+
| 1.0.0 | Proprietary license, professional metadata |
|
|
618
|
+
| 0.1.0 | Initial release — discord module |
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## License
|
|
623
|
+
|
|
624
|
+
**Proprietary** — Copyright © 2026 kroxy. All rights reserved.
|
|
625
|
+
|
|
626
|
+
Unauthorized copying, redistribution, or modification is strictly prohibited.
|
|
627
|
+
Contact **@kroxy** for permissions.
|