kroxy 0.1.0__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-0.1.0/LICENSE +21 -0
- kroxy-0.1.0/MANIFEST.in +3 -0
- kroxy-0.1.0/PKG-INFO +147 -0
- kroxy-0.1.0/README.md +121 -0
- kroxy-0.1.0/kroxy/__init__.py +13 -0
- kroxy-0.1.0/kroxy/discord/__init__.py +22 -0
- kroxy-0.1.0/kroxy/discord/antinuke.py +146 -0
- kroxy-0.1.0/kroxy/discord/api.py +128 -0
- kroxy-0.1.0/kroxy/discord/checkers.py +159 -0
- kroxy-0.1.0/kroxy/discord/commands.py +187 -0
- kroxy-0.1.0/kroxy/discord/giveaway.py +226 -0
- kroxy-0.1.0/kroxy/discord/music.py +236 -0
- kroxy-0.1.0/kroxy/discord/utils.py +190 -0
- kroxy-0.1.0/kroxy/website/__init__.py +5 -0
- kroxy-0.1.0/kroxy.egg-info/PKG-INFO +147 -0
- kroxy-0.1.0/kroxy.egg-info/SOURCES.txt +20 -0
- kroxy-0.1.0/kroxy.egg-info/dependency_links.txt +1 -0
- kroxy-0.1.0/kroxy.egg-info/requires.txt +5 -0
- kroxy-0.1.0/kroxy.egg-info/top_level.txt +1 -0
- kroxy-0.1.0/pyproject.toml +37 -0
- kroxy-0.1.0/setup.cfg +4 -0
- kroxy-0.1.0/setup.py +4 -0
kroxy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kroxy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kroxy-0.1.0/MANIFEST.in
ADDED
kroxy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kroxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A professional Discord utility library by @kroxy
|
|
5
|
+
Author-email: kroxy <kroxy@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/kroxy
|
|
8
|
+
Project-URL: Repository, https://github.com/kroxy/kroxy
|
|
9
|
+
Keywords: discord,bot,antinuke,giveaway,music,utility
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Communications :: Chat
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
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 professional Discord utility library by **@kroxy**.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install kroxy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Modules
|
|
38
|
+
|
|
39
|
+
### `kroxy.discord`
|
|
40
|
+
|
|
41
|
+
| Module | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `api` | Async Discord REST API client |
|
|
44
|
+
| `commands` | Slash & prefix command builders |
|
|
45
|
+
| `utils` | Embed builder, mentions, timestamps, permissions |
|
|
46
|
+
| `antinuke` | Anti-nuke protection with rate-limit tracking |
|
|
47
|
+
| `checkers` | Permission & role checks with hierarchy validation |
|
|
48
|
+
| `giveaway` | Full-featured weighted giveaway system |
|
|
49
|
+
| `music` | Queue-based music player state manager |
|
|
50
|
+
|
|
51
|
+
### `kroxy.website` *(coming soon)*
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick Examples
|
|
56
|
+
|
|
57
|
+
### Anti-Nuke
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from kroxy.discord import AntiNuke
|
|
61
|
+
|
|
62
|
+
antinuke = AntiNuke(whitelist=[OWNER_ID])
|
|
63
|
+
|
|
64
|
+
async def punish(action, user_id, guild):
|
|
65
|
+
print(f"NUKE DETECTED: {action} by {user_id}")
|
|
66
|
+
|
|
67
|
+
antinuke.on_trigger = punish
|
|
68
|
+
antinuke.punishment = "ban"
|
|
69
|
+
|
|
70
|
+
# In your event handler:
|
|
71
|
+
await antinuke.on_member_ban(user_id=some_user_id, guild=guild)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Giveaway
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from kroxy.discord.giveaway import GiveawayManager
|
|
78
|
+
|
|
79
|
+
manager = GiveawayManager()
|
|
80
|
+
|
|
81
|
+
async def on_giveaway_end(giveaway, winners):
|
|
82
|
+
print(f"Winners of {giveaway.prize}: {winners}")
|
|
83
|
+
|
|
84
|
+
manager.on_end = on_giveaway_end
|
|
85
|
+
|
|
86
|
+
giveaway = await manager.create(
|
|
87
|
+
prize="Discord Nitro",
|
|
88
|
+
host_id=123456789,
|
|
89
|
+
channel_id=987654321,
|
|
90
|
+
guild_id=111111111,
|
|
91
|
+
duration=3600, # 1 hour
|
|
92
|
+
winner_count=2,
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Embed Builder
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from kroxy.discord import Utils
|
|
100
|
+
|
|
101
|
+
embed = Utils.build_embed(
|
|
102
|
+
title="Hello from kroxy!",
|
|
103
|
+
description="This is a professional embed.",
|
|
104
|
+
color=0x5865F2,
|
|
105
|
+
fields=[{"name": "Field", "value": "Value", "inline": True}],
|
|
106
|
+
footer="kroxy library",
|
|
107
|
+
timestamp=True,
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Slash Command
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from kroxy.discord.commands import SlashCommand, Option
|
|
115
|
+
|
|
116
|
+
@SlashCommand.decorator(
|
|
117
|
+
name="ping",
|
|
118
|
+
description="Check bot latency",
|
|
119
|
+
)
|
|
120
|
+
async def ping(interaction):
|
|
121
|
+
await interaction.response.send_message("Pong!")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Music Player
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from kroxy.discord.music import MusicPlayerManager, Track
|
|
128
|
+
|
|
129
|
+
manager = MusicPlayerManager()
|
|
130
|
+
player = manager.get_or_create(guild_id=111111111, channel_id=222222222)
|
|
131
|
+
|
|
132
|
+
track = Track(
|
|
133
|
+
title="My Song",
|
|
134
|
+
url="https://youtube.com/...",
|
|
135
|
+
stream_url="https://...",
|
|
136
|
+
duration=240,
|
|
137
|
+
requester_id=123456789,
|
|
138
|
+
)
|
|
139
|
+
player.queue.add(track)
|
|
140
|
+
await player.play_next()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT © kroxy
|
kroxy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# kroxy
|
|
2
|
+
|
|
3
|
+
A professional Discord utility library by **@kroxy**.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install kroxy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Modules
|
|
12
|
+
|
|
13
|
+
### `kroxy.discord`
|
|
14
|
+
|
|
15
|
+
| Module | Description |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `api` | Async Discord REST API client |
|
|
18
|
+
| `commands` | Slash & prefix command builders |
|
|
19
|
+
| `utils` | Embed builder, mentions, timestamps, permissions |
|
|
20
|
+
| `antinuke` | Anti-nuke protection with rate-limit tracking |
|
|
21
|
+
| `checkers` | Permission & role checks with hierarchy validation |
|
|
22
|
+
| `giveaway` | Full-featured weighted giveaway system |
|
|
23
|
+
| `music` | Queue-based music player state manager |
|
|
24
|
+
|
|
25
|
+
### `kroxy.website` *(coming soon)*
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Examples
|
|
30
|
+
|
|
31
|
+
### Anti-Nuke
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from kroxy.discord import AntiNuke
|
|
35
|
+
|
|
36
|
+
antinuke = AntiNuke(whitelist=[OWNER_ID])
|
|
37
|
+
|
|
38
|
+
async def punish(action, user_id, guild):
|
|
39
|
+
print(f"NUKE DETECTED: {action} by {user_id}")
|
|
40
|
+
|
|
41
|
+
antinuke.on_trigger = punish
|
|
42
|
+
antinuke.punishment = "ban"
|
|
43
|
+
|
|
44
|
+
# In your event handler:
|
|
45
|
+
await antinuke.on_member_ban(user_id=some_user_id, guild=guild)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Giveaway
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from kroxy.discord.giveaway import GiveawayManager
|
|
52
|
+
|
|
53
|
+
manager = GiveawayManager()
|
|
54
|
+
|
|
55
|
+
async def on_giveaway_end(giveaway, winners):
|
|
56
|
+
print(f"Winners of {giveaway.prize}: {winners}")
|
|
57
|
+
|
|
58
|
+
manager.on_end = on_giveaway_end
|
|
59
|
+
|
|
60
|
+
giveaway = await manager.create(
|
|
61
|
+
prize="Discord Nitro",
|
|
62
|
+
host_id=123456789,
|
|
63
|
+
channel_id=987654321,
|
|
64
|
+
guild_id=111111111,
|
|
65
|
+
duration=3600, # 1 hour
|
|
66
|
+
winner_count=2,
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Embed Builder
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from kroxy.discord import Utils
|
|
74
|
+
|
|
75
|
+
embed = Utils.build_embed(
|
|
76
|
+
title="Hello from kroxy!",
|
|
77
|
+
description="This is a professional embed.",
|
|
78
|
+
color=0x5865F2,
|
|
79
|
+
fields=[{"name": "Field", "value": "Value", "inline": True}],
|
|
80
|
+
footer="kroxy library",
|
|
81
|
+
timestamp=True,
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Slash Command
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from kroxy.discord.commands import SlashCommand, Option
|
|
89
|
+
|
|
90
|
+
@SlashCommand.decorator(
|
|
91
|
+
name="ping",
|
|
92
|
+
description="Check bot latency",
|
|
93
|
+
)
|
|
94
|
+
async def ping(interaction):
|
|
95
|
+
await interaction.response.send_message("Pong!")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Music Player
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from kroxy.discord.music import MusicPlayerManager, Track
|
|
102
|
+
|
|
103
|
+
manager = MusicPlayerManager()
|
|
104
|
+
player = manager.get_or_create(guild_id=111111111, channel_id=222222222)
|
|
105
|
+
|
|
106
|
+
track = Track(
|
|
107
|
+
title="My Song",
|
|
108
|
+
url="https://youtube.com/...",
|
|
109
|
+
stream_url="https://...",
|
|
110
|
+
duration=240,
|
|
111
|
+
requester_id=123456789,
|
|
112
|
+
)
|
|
113
|
+
player.queue.add(track)
|
|
114
|
+
await player.play_next()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT © kroxy
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kroxy.discord - Professional Discord utilities and helpers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from kroxy.discord.api import DiscordAPI
|
|
6
|
+
from kroxy.discord.commands import SlashCommand, PrefixCommand
|
|
7
|
+
from kroxy.discord.utils import Utils
|
|
8
|
+
from kroxy.discord.antinuke import AntiNuke
|
|
9
|
+
from kroxy.discord.checkers import Checkers
|
|
10
|
+
from kroxy.discord.giveaway import Giveaway
|
|
11
|
+
from kroxy.discord.music import MusicPlayer
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"DiscordAPI",
|
|
15
|
+
"SlashCommand",
|
|
16
|
+
"PrefixCommand",
|
|
17
|
+
"Utils",
|
|
18
|
+
"AntiNuke",
|
|
19
|
+
"Checkers",
|
|
20
|
+
"Giveaway",
|
|
21
|
+
"MusicPlayer",
|
|
22
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kroxy.discord.antinuke - Anti-nuke protection system for Discord servers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from typing import Dict, List, Optional, Callable, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActionLog:
|
|
12
|
+
"""Tracks recent actions per user for rate-limit detection."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, threshold: int, window: int):
|
|
15
|
+
self.threshold = threshold
|
|
16
|
+
self.window = window # seconds
|
|
17
|
+
self._log: Dict[int, List[float]] = defaultdict(list)
|
|
18
|
+
|
|
19
|
+
def record(self, user_id: int) -> int:
|
|
20
|
+
"""Record an action and return the count within the window."""
|
|
21
|
+
now = time.time()
|
|
22
|
+
self._log[user_id] = [t for t in self._log[user_id] if now - t < self.window]
|
|
23
|
+
self._log[user_id].append(now)
|
|
24
|
+
return len(self._log[user_id])
|
|
25
|
+
|
|
26
|
+
def exceeded(self, user_id: int) -> bool:
|
|
27
|
+
"""Return True if the user has exceeded the threshold."""
|
|
28
|
+
return self.record(user_id) >= self.threshold
|
|
29
|
+
|
|
30
|
+
def reset(self, user_id: int):
|
|
31
|
+
self._log.pop(user_id, None)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AntiNuke:
|
|
35
|
+
"""
|
|
36
|
+
Anti-nuke system that monitors destructive actions and auto-punishes abusers.
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
antinuke = AntiNuke(bot, whitelist=[owner_id])
|
|
40
|
+
antinuke.on_trigger = my_punishment_callback
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
DEFAULT_LIMITS = {
|
|
44
|
+
"ban": (3, 10), # 3 bans in 10s
|
|
45
|
+
"kick": (3, 10),
|
|
46
|
+
"channel_delete": (3, 10),
|
|
47
|
+
"channel_create": (5, 10),
|
|
48
|
+
"role_delete": (3, 10),
|
|
49
|
+
"role_create": (5, 10),
|
|
50
|
+
"webhook_create": (3, 10),
|
|
51
|
+
"webhook_delete": (3, 10),
|
|
52
|
+
"invite_delete": (5, 10),
|
|
53
|
+
"mass_mention": (5, 5),
|
|
54
|
+
"bot_add": (2, 30),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def __init__(self, whitelist: List[int] = None, limits: Dict = None):
|
|
58
|
+
self.whitelist: List[int] = whitelist or []
|
|
59
|
+
limits = limits or {}
|
|
60
|
+
merged = {**self.DEFAULT_LIMITS, **limits}
|
|
61
|
+
self._trackers: Dict[str, ActionLog] = {
|
|
62
|
+
action: ActionLog(threshold, window)
|
|
63
|
+
for action, (threshold, window) in merged.items()
|
|
64
|
+
}
|
|
65
|
+
self._punished: set = set()
|
|
66
|
+
self.on_trigger: Optional[Callable] = None
|
|
67
|
+
self.punishment: str = "ban" # "ban" | "kick" | "strip_roles"
|
|
68
|
+
|
|
69
|
+
def is_whitelisted(self, user_id: int) -> bool:
|
|
70
|
+
return user_id in self.whitelist
|
|
71
|
+
|
|
72
|
+
def add_whitelist(self, user_id: int):
|
|
73
|
+
if user_id not in self.whitelist:
|
|
74
|
+
self.whitelist.append(user_id)
|
|
75
|
+
|
|
76
|
+
def remove_whitelist(self, user_id: int):
|
|
77
|
+
self.whitelist = [u for u in self.whitelist if u != user_id]
|
|
78
|
+
|
|
79
|
+
async def check(self, action: str, user_id: int, guild: Any = None) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Check an action for a user. Returns True if limit exceeded.
|
|
82
|
+
Calls self.on_trigger(action, user_id, guild) if set.
|
|
83
|
+
"""
|
|
84
|
+
if self.is_whitelisted(user_id):
|
|
85
|
+
return False
|
|
86
|
+
if user_id in self._punished:
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
tracker = self._trackers.get(action)
|
|
90
|
+
if tracker is None:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
if tracker.exceeded(user_id):
|
|
94
|
+
self._punished.add(user_id)
|
|
95
|
+
if self.on_trigger:
|
|
96
|
+
try:
|
|
97
|
+
await self.on_trigger(action=action, user_id=user_id, guild=guild)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
return True
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def reset_user(self, user_id: int):
|
|
104
|
+
"""Clear all action logs and punishment flag for a user."""
|
|
105
|
+
for tracker in self._trackers.values():
|
|
106
|
+
tracker.reset(user_id)
|
|
107
|
+
self._punished.discard(user_id)
|
|
108
|
+
|
|
109
|
+
def get_stats(self) -> Dict:
|
|
110
|
+
"""Return current limits configuration."""
|
|
111
|
+
return {
|
|
112
|
+
action: {"threshold": t.threshold, "window": t.window}
|
|
113
|
+
for action, t in self._trackers.items()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ── Convenience event handlers (wire these to your event system) ──────────
|
|
117
|
+
|
|
118
|
+
async def on_member_ban(self, user_id: int, guild: Any = None):
|
|
119
|
+
return await self.check("ban", user_id, guild)
|
|
120
|
+
|
|
121
|
+
async def on_member_kick(self, user_id: int, guild: Any = None):
|
|
122
|
+
return await self.check("kick", user_id, guild)
|
|
123
|
+
|
|
124
|
+
async def on_channel_delete(self, user_id: int, guild: Any = None):
|
|
125
|
+
return await self.check("channel_delete", user_id, guild)
|
|
126
|
+
|
|
127
|
+
async def on_channel_create(self, user_id: int, guild: Any = None):
|
|
128
|
+
return await self.check("channel_create", user_id, guild)
|
|
129
|
+
|
|
130
|
+
async def on_role_delete(self, user_id: int, guild: Any = None):
|
|
131
|
+
return await self.check("role_delete", user_id, guild)
|
|
132
|
+
|
|
133
|
+
async def on_role_create(self, user_id: int, guild: Any = None):
|
|
134
|
+
return await self.check("role_create", user_id, guild)
|
|
135
|
+
|
|
136
|
+
async def on_webhook_create(self, user_id: int, guild: Any = None):
|
|
137
|
+
return await self.check("webhook_create", user_id, guild)
|
|
138
|
+
|
|
139
|
+
async def on_webhook_delete(self, user_id: int, guild: Any = None):
|
|
140
|
+
return await self.check("webhook_delete", user_id, guild)
|
|
141
|
+
|
|
142
|
+
async def on_bot_add(self, user_id: int, guild: Any = None):
|
|
143
|
+
return await self.check("bot_add", user_id, guild)
|
|
144
|
+
|
|
145
|
+
async def on_mass_mention(self, user_id: int, guild: Any = None):
|
|
146
|
+
return await self.check("mass_mention", user_id, guild)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kroxy.discord.api - Discord REST API wrapper helpers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
import asyncio
|
|
7
|
+
from typing import Optional, Any, Dict
|
|
8
|
+
|
|
9
|
+
DISCORD_API_BASE = "https://discord.com/api/v10"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DiscordAPI:
|
|
13
|
+
"""Async Discord REST API client."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, token: str, bot: bool = True):
|
|
16
|
+
prefix = "Bot" if bot else "Bearer"
|
|
17
|
+
self.headers = {
|
|
18
|
+
"Authorization": f"{prefix} {token}",
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
}
|
|
21
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
22
|
+
|
|
23
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
24
|
+
if self._session is None or self._session.closed:
|
|
25
|
+
self._session = aiohttp.ClientSession(headers=self.headers)
|
|
26
|
+
return self._session
|
|
27
|
+
|
|
28
|
+
async def close(self):
|
|
29
|
+
"""Close the underlying HTTP session."""
|
|
30
|
+
if self._session and not self._session.closed:
|
|
31
|
+
await self._session.close()
|
|
32
|
+
|
|
33
|
+
async def request(self, method: str, endpoint: str, **kwargs) -> Any:
|
|
34
|
+
"""Make a raw API request."""
|
|
35
|
+
session = await self._get_session()
|
|
36
|
+
url = f"{DISCORD_API_BASE}{endpoint}"
|
|
37
|
+
async with session.request(method, url, **kwargs) as resp:
|
|
38
|
+
resp.raise_for_status()
|
|
39
|
+
return await resp.json()
|
|
40
|
+
|
|
41
|
+
async def get_guild(self, guild_id: int) -> Dict:
|
|
42
|
+
"""Fetch a guild by ID."""
|
|
43
|
+
return await self.request("GET", f"/guilds/{guild_id}")
|
|
44
|
+
|
|
45
|
+
async def get_channel(self, channel_id: int) -> Dict:
|
|
46
|
+
"""Fetch a channel by ID."""
|
|
47
|
+
return await self.request("GET", f"/channels/{channel_id}")
|
|
48
|
+
|
|
49
|
+
async def get_user(self, user_id: int) -> Dict:
|
|
50
|
+
"""Fetch a user by ID."""
|
|
51
|
+
return await self.request("GET", f"/users/{user_id}")
|
|
52
|
+
|
|
53
|
+
async def send_message(self, channel_id: int, content: str = None,
|
|
54
|
+
embed: Dict = None, components: list = None) -> Dict:
|
|
55
|
+
"""Send a message to a channel."""
|
|
56
|
+
payload: Dict[str, Any] = {}
|
|
57
|
+
if content:
|
|
58
|
+
payload["content"] = content
|
|
59
|
+
if embed:
|
|
60
|
+
payload["embeds"] = [embed]
|
|
61
|
+
if components:
|
|
62
|
+
payload["components"] = components
|
|
63
|
+
return await self.request("POST", f"/channels/{channel_id}/messages", json=payload)
|
|
64
|
+
|
|
65
|
+
async def delete_message(self, channel_id: int, message_id: int) -> None:
|
|
66
|
+
"""Delete a message."""
|
|
67
|
+
await self.request("DELETE", f"/channels/{channel_id}/messages/{message_id}")
|
|
68
|
+
|
|
69
|
+
async def ban_member(self, guild_id: int, user_id: int,
|
|
70
|
+
reason: str = None, delete_message_days: int = 0) -> None:
|
|
71
|
+
"""Ban a member from a guild."""
|
|
72
|
+
payload = {"delete_message_days": delete_message_days}
|
|
73
|
+
headers = {}
|
|
74
|
+
if reason:
|
|
75
|
+
headers["X-Audit-Log-Reason"] = reason
|
|
76
|
+
await self.request("PUT", f"/guilds/{guild_id}/bans/{user_id}",
|
|
77
|
+
json=payload, headers=headers)
|
|
78
|
+
|
|
79
|
+
async def unban_member(self, guild_id: int, user_id: int) -> None:
|
|
80
|
+
"""Unban a member."""
|
|
81
|
+
await self.request("DELETE", f"/guilds/{guild_id}/bans/{user_id}")
|
|
82
|
+
|
|
83
|
+
async def kick_member(self, guild_id: int, user_id: int, reason: str = None) -> None:
|
|
84
|
+
"""Kick a member from a guild."""
|
|
85
|
+
headers = {}
|
|
86
|
+
if reason:
|
|
87
|
+
headers["X-Audit-Log-Reason"] = reason
|
|
88
|
+
await self.request("DELETE", f"/guilds/{guild_id}/members/{user_id}",
|
|
89
|
+
headers=headers)
|
|
90
|
+
|
|
91
|
+
async def add_role(self, guild_id: int, user_id: int, role_id: int) -> None:
|
|
92
|
+
"""Add a role to a member."""
|
|
93
|
+
await self.request("PUT", f"/guilds/{guild_id}/members/{user_id}/roles/{role_id}")
|
|
94
|
+
|
|
95
|
+
async def remove_role(self, guild_id: int, user_id: int, role_id: int) -> None:
|
|
96
|
+
"""Remove a role from a member."""
|
|
97
|
+
await self.request("DELETE", f"/guilds/{guild_id}/members/{user_id}/roles/{role_id}")
|
|
98
|
+
|
|
99
|
+
async def create_channel(self, guild_id: int, name: str,
|
|
100
|
+
channel_type: int = 0, **kwargs) -> Dict:
|
|
101
|
+
"""Create a guild channel."""
|
|
102
|
+
payload = {"name": name, "type": channel_type, **kwargs}
|
|
103
|
+
return await self.request("POST", f"/guilds/{guild_id}/channels", json=payload)
|
|
104
|
+
|
|
105
|
+
async def delete_channel(self, channel_id: int) -> Dict:
|
|
106
|
+
"""Delete a channel."""
|
|
107
|
+
return await self.request("DELETE", f"/channels/{channel_id}")
|
|
108
|
+
|
|
109
|
+
async def get_audit_logs(self, guild_id: int, limit: int = 50) -> Dict:
|
|
110
|
+
"""Fetch guild audit logs."""
|
|
111
|
+
return await self.request("GET", f"/guilds/{guild_id}/audit-logs",
|
|
112
|
+
params={"limit": limit})
|
|
113
|
+
|
|
114
|
+
async def get_invites(self, guild_id: int) -> list:
|
|
115
|
+
"""Get all guild invites."""
|
|
116
|
+
return await self.request("GET", f"/guilds/{guild_id}/invites")
|
|
117
|
+
|
|
118
|
+
async def delete_invite(self, invite_code: str) -> None:
|
|
119
|
+
"""Delete an invite by code."""
|
|
120
|
+
await self.request("DELETE", f"/invites/{invite_code}")
|
|
121
|
+
|
|
122
|
+
async def get_webhooks(self, guild_id: int) -> list:
|
|
123
|
+
"""Get all webhooks in a guild."""
|
|
124
|
+
return await self.request("GET", f"/guilds/{guild_id}/webhooks")
|
|
125
|
+
|
|
126
|
+
async def delete_webhook(self, webhook_id: int) -> None:
|
|
127
|
+
"""Delete a webhook."""
|
|
128
|
+
await self.request("DELETE", f"/webhooks/{webhook_id}")
|