python-yappy 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- {python_yappy-0.1.0.dist-info → python_yappy-0.1.1.dist-info}/METADATA +28 -35
- python_yappy-0.1.1.dist-info/RECORD +11 -0
- yappy/api.py +27 -9
- yappy/app.py +256 -59
- yappy/core.py +197 -30
- yappy/parser.py +69 -0
- yappy/utils.py +4 -0
- python_yappy-0.1.0.dist-info/RECORD +0 -9
- {python_yappy-0.1.0.dist-info → python_yappy-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-yappy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: API Bindings for Yapper!
|
|
5
5
|
Author: H1387Lmao
|
|
6
6
|
Requires-Dist: aiohttp>=3.14.5
|
|
@@ -28,56 +28,47 @@ YapPy brings a Python-friendly interface to [Yapper](https://yapper.website), a
|
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
31
|
-
Install YapPy using your project's preferred method.
|
|
32
|
-
|
|
33
|
-
If the package is available on PyPI:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pip install yappy
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
If you are working from a local checkout, install the project from its repository directory:
|
|
40
31
|
|
|
41
32
|
```bash
|
|
42
|
-
pip install
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The example below uses [`python-dotenv`](https://pypi.org/project/python-dotenv/) to load the bot token from a `.env` file:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
pip install python-dotenv
|
|
33
|
+
pip install python-yappy
|
|
49
34
|
```
|
|
50
35
|
|
|
51
36
|
## Quick start
|
|
52
37
|
|
|
53
|
-
|
|
38
|
+
This example uses the dotenv module from `pip install load_dotenv`
|
|
39
|
+
Create a file named `main.py`:
|
|
54
40
|
|
|
55
41
|
```python
|
|
42
|
+
from yappy import App
|
|
56
43
|
import os
|
|
57
|
-
|
|
58
44
|
from dotenv import load_dotenv
|
|
59
|
-
from yappy import App
|
|
60
|
-
|
|
61
45
|
load_dotenv()
|
|
62
|
-
|
|
63
46
|
bot = App(verbose=True)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
@bot.event(
|
|
48
|
+
"message"
|
|
49
|
+
)
|
|
67
50
|
async def on_message(ctx):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await ctx.reply("Hi!")
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
if ctx.is_bot:
|
|
52
|
+
return
|
|
53
|
+
await bot.process_command(ctx)
|
|
74
54
|
@bot.command(
|
|
75
|
-
|
|
76
|
-
description="replies with 'Pong!'"
|
|
55
|
+
name="ping",
|
|
56
|
+
description="replies with 'Pong!'"
|
|
77
57
|
)
|
|
78
58
|
async def ping(ctx):
|
|
79
|
-
|
|
59
|
+
await ctx.reply("Pong!")
|
|
80
60
|
|
|
61
|
+
@bot.command(
|
|
62
|
+
name="react",
|
|
63
|
+
description="React your message with a certain emoji!"
|
|
64
|
+
)
|
|
65
|
+
async def react(ctx, emoji: str):
|
|
66
|
+
await ctx.message.react(emoji)
|
|
67
|
+
@bot.event(
|
|
68
|
+
"ready"
|
|
69
|
+
)
|
|
70
|
+
async def on_ready():
|
|
71
|
+
print("Logged as", bot.user.name)
|
|
81
72
|
|
|
82
73
|
bot.run(os.getenv("TOKEN"))
|
|
83
74
|
```
|
|
@@ -123,7 +114,7 @@ async def on_message(ctx):
|
|
|
123
114
|
if ctx.is_bot:
|
|
124
115
|
return
|
|
125
116
|
|
|
126
|
-
await ctx.
|
|
117
|
+
await ctx.process.command(ctx)
|
|
127
118
|
```
|
|
128
119
|
|
|
129
120
|
The event name and handler arguments should match the event interface supported by the installed YapPy version.
|
|
@@ -153,6 +144,8 @@ YapPy currently provides these core models:
|
|
|
153
144
|
| `Message` | Represents a message. |
|
|
154
145
|
| `Channel` | Represents a channel. |
|
|
155
146
|
| `User` | Represents a user. |
|
|
147
|
+
| `Guild` | Represents a guild / server |
|
|
148
|
+
| `Role` | Represents a role |
|
|
156
149
|
|
|
157
150
|
The example uses the context object (`ctx`) to check `ctx.is_bot` and send a reply with `ctx.reply(...)`. Consult the source code for the currently available attributes and methods of each model.
|
|
158
151
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
yappy/__init__.py,sha256=BZU8ktPMVc9XeJ5YJeeCAPCjRyaxYIU_zusvjiSi5_Q,21
|
|
2
|
+
yappy/api.py,sha256=_v1r9xPqrqmNwiz7ESnIsrRrzXNrm34eem7XUa4UAWU,2392
|
|
3
|
+
yappy/app.py,sha256=vT4MKygpFmZoc6yu8Uu16hxldNjt5cE2NB-EgFvhifc,11651
|
|
4
|
+
yappy/consts.py,sha256=0pIUIY1Ayo6_q3XhWr9hvKLgOT7TC9RgsjPZNVYy8QQ,51
|
|
5
|
+
yappy/core.py,sha256=quCOfJPjVzUbwdK2NeH3IN3Yr8sNOaQ6JZoMVtELPDk,8110
|
|
6
|
+
yappy/errors.py,sha256=BZ42Du8XWA_SY9tR5Ye8gVWWVKAvRDZZ8Grt_CoOOZs,133
|
|
7
|
+
yappy/parser.py,sha256=XgMXkXHyuL_-oH6rpsbQcnmO5KQrNADIT20GH4-C1LM,2583
|
|
8
|
+
yappy/utils.py,sha256=s7wmPTflqTAIgeAtf0a-gWgEfkQaZGKWYEqm2i4MsUM,129
|
|
9
|
+
python_yappy-0.1.1.dist-info/WHEEL,sha256=lrO5MD1WVAWzcbNy_L2BwtfPrcM3KfUFpbKYXLkJX4A,80
|
|
10
|
+
python_yappy-0.1.1.dist-info/METADATA,sha256=gNceBXMHD6CuuMQO07gR6nTMTINHeVP98VmGIG7la98,4663
|
|
11
|
+
python_yappy-0.1.1.dist-info/RECORD,,
|
yappy/api.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from .consts import BASE_URL
|
|
2
2
|
import aiohttp
|
|
3
|
+
import random
|
|
4
|
+
import asyncio
|
|
3
5
|
|
|
4
6
|
async def api_push(session: aiohttp.ClientSession, method: str, path: str, body=None, tries=3):
|
|
5
7
|
headers = {
|
|
@@ -25,20 +27,36 @@ async def api_push(session: aiohttp.ClientSession, method: str, path: str, body=
|
|
|
25
27
|
if 0 < wait <= 60 and tries > 1:
|
|
26
28
|
print(f"{'busy' if busy else 'rate limited'}, retrying in {wait}s")
|
|
27
29
|
await asyncio.sleep(wait * (1 + random.random() * 0.5))
|
|
28
|
-
return await
|
|
30
|
+
return await api_push(session, method, path, body, tries - 1)
|
|
29
31
|
|
|
30
32
|
raise RuntimeError(f"{method} {path} failed ({e.status}): {msg or e.message}") from None
|
|
31
|
-
|
|
32
|
-
class
|
|
33
|
+
|
|
34
|
+
class API:
|
|
33
35
|
@staticmethod
|
|
34
|
-
async def
|
|
35
|
-
await api_push(session,
|
|
36
|
+
async def _request(session, method, path="", body=None, tries=3):
|
|
37
|
+
return await api_push(session, method, path, body, tries)
|
|
38
|
+
|
|
36
39
|
@staticmethod
|
|
37
40
|
async def get(session, path="", body=None, tries=3):
|
|
38
|
-
return await
|
|
41
|
+
return await API._request(session, "GET", path, body, tries)
|
|
42
|
+
|
|
39
43
|
@staticmethod
|
|
40
44
|
async def post(session, path="", body=None, tries=3):
|
|
41
|
-
await
|
|
45
|
+
return await API._request(session, "POST", path, body, tries)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
async def put(session, path="", body=None, tries=3):
|
|
49
|
+
return await API._request(session, "PUT", path, body, tries)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
async def patch(session, path="", body=None, tries=3):
|
|
53
|
+
return await API._request(session, "PATCH", path, body, tries)
|
|
54
|
+
|
|
42
55
|
@staticmethod
|
|
43
|
-
async def
|
|
44
|
-
await
|
|
56
|
+
async def delete(session, path="", body=None, tries=3):
|
|
57
|
+
return await API._request(session, "DELETE", path, body, tries)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
async def send(session, channel_id=0, body=None, tries=3):
|
|
61
|
+
return await API._request(session, "POST", f"/channels/{channel_id}/messages", body, tries)
|
|
62
|
+
|
yappy/app.py
CHANGED
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
from .core import *
|
|
2
2
|
from .consts import BASE_URL
|
|
3
3
|
from .api import *
|
|
4
|
-
from .errors import ImproperToken, InvalidSignature
|
|
4
|
+
from .errors import ImproperToken, InvalidSignature, UnknownCommand
|
|
5
|
+
from .parser import parser
|
|
5
6
|
|
|
6
7
|
import inspect
|
|
7
8
|
import asyncio
|
|
8
9
|
import aiohttp
|
|
10
|
+
import traceback
|
|
9
11
|
from functools import wraps
|
|
10
12
|
from typing import Callable
|
|
11
13
|
|
|
12
14
|
class App:
|
|
13
|
-
def __init__(
|
|
15
|
+
def __init__(
|
|
16
|
+
self, verbose=False,
|
|
17
|
+
prefix = "!"
|
|
18
|
+
):
|
|
14
19
|
self.__commands = {}
|
|
15
|
-
self.__event_handlers = {
|
|
20
|
+
self.__event_handlers = {
|
|
21
|
+
"message": self.on_message
|
|
22
|
+
}
|
|
23
|
+
self.__aliases = {}
|
|
16
24
|
|
|
17
25
|
self.user = None
|
|
26
|
+
self.prefix = prefix
|
|
27
|
+
|
|
18
28
|
self._token = None
|
|
19
29
|
|
|
20
30
|
self._verbose = verbose
|
|
21
31
|
|
|
22
|
-
self.__cache = {
|
|
32
|
+
self.__cache = {
|
|
33
|
+
"users":{}, "channels":{},
|
|
34
|
+
"guilds": {}, "roles": {}
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
async def _load_bot_info(self):
|
|
25
|
-
my_info = await
|
|
26
|
-
|
|
27
|
-
if self._verbose:
|
|
28
|
-
print(my_info)
|
|
38
|
+
my_info = await API.get(self.session, "/me")
|
|
29
39
|
|
|
30
40
|
return Bot(
|
|
31
41
|
name=my_info["username"],
|
|
32
42
|
display_name=my_info["display_name"],
|
|
33
43
|
id=my_info["id"],
|
|
34
|
-
|
|
44
|
+
assets=Assets(
|
|
35
45
|
avatar=my_info["avatar_url"],
|
|
36
46
|
banner=my_info["banner_url"],
|
|
37
47
|
),
|
|
@@ -42,29 +52,193 @@ class App:
|
|
|
42
52
|
is_bot=True
|
|
43
53
|
)
|
|
44
54
|
|
|
45
|
-
def
|
|
46
|
-
self,
|
|
47
|
-
id,
|
|
48
|
-
_data
|
|
55
|
+
async def _cache_members(
|
|
56
|
+
self, id
|
|
49
57
|
):
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
_members_data = (await API.get(
|
|
59
|
+
self.session, f"/servers/{id}/members"
|
|
60
|
+
))["members"]
|
|
61
|
+
|
|
62
|
+
members = []
|
|
63
|
+
|
|
64
|
+
for _user_data in _members_data:
|
|
65
|
+
roles = [
|
|
66
|
+
self.__cache["roles"][role_id]
|
|
67
|
+
for role_id in _user_data["roles"]
|
|
68
|
+
if role_id in self.__cache["roles"]
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
members.append(
|
|
72
|
+
Member(
|
|
73
|
+
bot=self,
|
|
74
|
+
guild=None, roles=roles,
|
|
75
|
+
id=id,
|
|
76
|
+
name = _user_data["username"],
|
|
77
|
+
display_name = _user_data["display_name"],
|
|
78
|
+
badges = _user_data["badges"],
|
|
79
|
+
joined_at = _user_data["joined_at"],
|
|
80
|
+
nickname = _user_data["nickname"],
|
|
81
|
+
assets = Assets(
|
|
82
|
+
avatar = "https://files.yapper.website/avatars/"+_user_data.get("avatar_path",""),
|
|
83
|
+
banner = "https://files.yapper.website/banners/"+_user_data.get("banner_path", "")
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return members
|
|
89
|
+
|
|
90
|
+
async def _cache_roles(
|
|
91
|
+
self, id
|
|
92
|
+
):
|
|
93
|
+
_roles_data = (await API.get(
|
|
94
|
+
self.session, f"/servers/{id}/roles"
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
roles = []
|
|
98
|
+
|
|
99
|
+
for role in _roles_data["roles"]:
|
|
100
|
+
roles.append(
|
|
101
|
+
r:= Role(
|
|
102
|
+
id = role["id"],
|
|
103
|
+
name = role["name"],
|
|
104
|
+
color = role["color"],
|
|
105
|
+
permissions = Permissions(
|
|
106
|
+
manage_server=role["manage_server"],
|
|
107
|
+
manage_messages=role["manage_messages"],
|
|
108
|
+
**role["perms"]
|
|
109
|
+
),
|
|
110
|
+
auto = role["auto"],
|
|
111
|
+
position = role["position"]
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
self.__cache["roles"][id]=r
|
|
115
|
+
return roles
|
|
116
|
+
|
|
117
|
+
def get_role(self, rid):
|
|
118
|
+
return self.__cache["roles"].get(id)
|
|
119
|
+
|
|
120
|
+
async def fetch_guild(
|
|
121
|
+
self, id
|
|
122
|
+
) -> Guild:
|
|
123
|
+
_guild_data = (await API.get(
|
|
124
|
+
self.session, f"/servers/{id}"
|
|
125
|
+
))["server"]
|
|
126
|
+
|
|
127
|
+
roles = await self._cache_roles(id)
|
|
128
|
+
members = await self._cache_members(id)
|
|
129
|
+
|
|
130
|
+
return Guild(
|
|
131
|
+
self,
|
|
132
|
+
id = id,
|
|
133
|
+
name=_guild_data["name"],
|
|
134
|
+
owner=await self.find_user(
|
|
135
|
+
_guild_data["owner_id"]
|
|
136
|
+
),
|
|
137
|
+
member_count = _guild_data["member_count"],
|
|
138
|
+
discoverable = _guild_data["discoverable"],
|
|
139
|
+
created_at = _guild_data["created_at"],
|
|
140
|
+
assets = Assets(
|
|
141
|
+
icon = _guild_data["icon_path"],
|
|
142
|
+
banner = _guild_data["banner_path"]
|
|
143
|
+
),
|
|
144
|
+
roles = roles,
|
|
145
|
+
members = members
|
|
146
|
+
)
|
|
147
|
+
async def find_guild(
|
|
148
|
+
self, id
|
|
149
|
+
):
|
|
150
|
+
guild = self.get_guild(id)
|
|
151
|
+
if guild is None:
|
|
152
|
+
guild = await self.fetch_guild(id)
|
|
153
|
+
self.__cache["guilds"]=guild
|
|
154
|
+
return guild
|
|
155
|
+
|
|
156
|
+
def get_guild(
|
|
157
|
+
self, id
|
|
158
|
+
):
|
|
159
|
+
return self.__cache["guilds"].get(id)
|
|
160
|
+
|
|
161
|
+
async def fetch_channel(
|
|
162
|
+
self, id
|
|
163
|
+
) -> Channel:
|
|
164
|
+
_channel_data = (await API.get(
|
|
165
|
+
self.session, f"/channels/{id}"
|
|
166
|
+
))["channel"]
|
|
167
|
+
|
|
168
|
+
return Channel(
|
|
169
|
+
self,
|
|
170
|
+
id=_channel_data["id"],
|
|
171
|
+
kind=_channel_data["kind"],
|
|
172
|
+
name=_channel_data["name"],
|
|
173
|
+
slowmode=_channel_data["slowmode"],
|
|
174
|
+
private=_channel_data["private"],
|
|
175
|
+
guild=await self.find_guild(
|
|
176
|
+
_channel_data["server_id"]
|
|
177
|
+
),
|
|
178
|
+
read_only=_channel_data["read_only"],
|
|
179
|
+
category_id=_channel_data["category_id"]
|
|
55
180
|
)
|
|
56
181
|
|
|
57
|
-
|
|
182
|
+
async def find_channel(
|
|
183
|
+
self, id
|
|
184
|
+
):
|
|
185
|
+
channel = self.get_channel(id)
|
|
186
|
+
if channel is None:
|
|
187
|
+
channel = await self.fetch_channel(id)
|
|
188
|
+
self.__cache["channels"][id] = channel
|
|
189
|
+
return channel
|
|
190
|
+
|
|
191
|
+
def get_channel(
|
|
192
|
+
self, id
|
|
193
|
+
):
|
|
194
|
+
return self.__cache["channels"].get(id)
|
|
195
|
+
|
|
196
|
+
async def fetch_user(
|
|
197
|
+
self,
|
|
198
|
+
id
|
|
199
|
+
) -> User:
|
|
200
|
+
_user_data = (await API.get(
|
|
201
|
+
self.session, f"/users/{id}"
|
|
202
|
+
))["user"]
|
|
203
|
+
|
|
204
|
+
return User(
|
|
205
|
+
bot=self,
|
|
206
|
+
id=id,
|
|
207
|
+
name = _user_data["username"],
|
|
208
|
+
display_name = _user_data["display_name"],
|
|
209
|
+
badges = _user_data["badges"],
|
|
210
|
+
created_at = _user_data["created_at"],
|
|
211
|
+
bio = _user_data["bio"],
|
|
212
|
+
pronouns = _user_data["pronouns"],
|
|
213
|
+
assets = Assets(
|
|
214
|
+
avatar = _user_data["avatar_path"],
|
|
215
|
+
banner = _user_data["banner_path"]
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def find_user(
|
|
220
|
+
self,
|
|
221
|
+
id,
|
|
222
|
+
) -> User:
|
|
223
|
+
user = self.get_user(id)
|
|
224
|
+
|
|
225
|
+
if user is None:
|
|
226
|
+
user = await self.fetch_user(
|
|
227
|
+
id
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
self.__cache["users"][id]=user
|
|
58
231
|
return user
|
|
59
232
|
|
|
60
|
-
def
|
|
233
|
+
def get_user(
|
|
234
|
+
self, id
|
|
235
|
+
):
|
|
236
|
+
return self.__cache["users"].get(id)
|
|
237
|
+
|
|
238
|
+
async def _build_context(self, ev):
|
|
61
239
|
d = ev["data"]
|
|
62
240
|
|
|
63
|
-
channel=
|
|
64
|
-
self,
|
|
65
|
-
id=d["channel_id"],
|
|
66
|
-
name=d["channel_name"]
|
|
67
|
-
)
|
|
241
|
+
channel = await self.find_channel(d["channel_id"])
|
|
68
242
|
|
|
69
243
|
return Context(
|
|
70
244
|
self,
|
|
@@ -72,8 +246,8 @@ class App:
|
|
|
72
246
|
message=Message(
|
|
73
247
|
self,
|
|
74
248
|
id=d["id"],
|
|
75
|
-
author=
|
|
76
|
-
d["author_id"]
|
|
249
|
+
author=await channel.guild.find_member(
|
|
250
|
+
d["author_id"]
|
|
77
251
|
),
|
|
78
252
|
channel=channel,
|
|
79
253
|
created_at=d["created_at"],
|
|
@@ -82,35 +256,55 @@ class App:
|
|
|
82
256
|
channel=channel
|
|
83
257
|
)
|
|
84
258
|
|
|
259
|
+
async def _parse_arguments(self, content):
|
|
260
|
+
return await parser.parse_args(self, content)
|
|
261
|
+
|
|
262
|
+
async def _handle_prefix_command(self, ctx):
|
|
263
|
+
if ctx.message.content.startswith(self.prefix):
|
|
264
|
+
if " " in ctx.message.content:
|
|
265
|
+
name, raw = ctx.message.content.removeprefix(self.prefix).split(" ", maxsplit=1)
|
|
266
|
+
else:
|
|
267
|
+
name = ctx.message.content.removeprefix(self.prefix)
|
|
268
|
+
raw = ""
|
|
269
|
+
arguments = await self._parse_arguments(
|
|
270
|
+
raw
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
cmd = None
|
|
274
|
+
if name in self.__commands:
|
|
275
|
+
cmd = self.__commands[name.lower()]
|
|
276
|
+
elif name in self.__aliases:
|
|
277
|
+
cmd = self.__aliases[name.lower()]
|
|
278
|
+
else:
|
|
279
|
+
raise UnknownCommand(
|
|
280
|
+
f"Unhandled command '{name.lower()}'!"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
await cmd.func(
|
|
284
|
+
ctx, *arguments
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return True
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
async def process_command(self, ctx):
|
|
291
|
+
await self._handle_prefix_command(
|
|
292
|
+
ctx
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def on_message(self, ctx):
|
|
296
|
+
if ctx.is_bot: return
|
|
297
|
+
await process_command(ctx)
|
|
298
|
+
|
|
85
299
|
async def _handle_event(self, session: aiohttp.ClientSession, ev):
|
|
86
|
-
if self._verbose:
|
|
87
|
-
print(ev)
|
|
88
300
|
d = ev["data"]
|
|
89
301
|
|
|
90
302
|
if ev["type"] in self.__event_handlers:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
self._build_context(
|
|
95
|
-
ev
|
|
96
|
-
)
|
|
97
|
-
)
|
|
98
|
-
elif cmd_name in self.__aliases:
|
|
99
|
-
await self.__aliases[cmd_name](
|
|
100
|
-
self._build_context(
|
|
101
|
-
ev
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
raise UnknownCommand(
|
|
106
|
-
f"Unhandled command '{cmd_name}'!"
|
|
107
|
-
)
|
|
108
|
-
else:
|
|
109
|
-
await self.__event_handlers[ev["type"]](
|
|
110
|
-
self._build_context(
|
|
111
|
-
ev
|
|
112
|
-
)
|
|
303
|
+
await self.__event_handlers[ev["type"]](
|
|
304
|
+
await self._build_context(
|
|
305
|
+
ev
|
|
113
306
|
)
|
|
307
|
+
)
|
|
114
308
|
|
|
115
309
|
async def _init_commands(self):
|
|
116
310
|
_commands = []
|
|
@@ -119,7 +313,7 @@ class App:
|
|
|
119
313
|
{"name": name, "description": func.description}
|
|
120
314
|
)
|
|
121
315
|
|
|
122
|
-
await
|
|
316
|
+
await API.put(
|
|
123
317
|
self.session,
|
|
124
318
|
"/commands",
|
|
125
319
|
{"commands": _commands}
|
|
@@ -128,17 +322,20 @@ class App:
|
|
|
128
322
|
async def _event_loop(self):
|
|
129
323
|
async with aiohttp.ClientSession() as self.session:
|
|
130
324
|
self.session._bearer_token=self._token
|
|
131
|
-
|
|
325
|
+
|
|
132
326
|
self.user = await self._load_bot_info()
|
|
133
327
|
await self._init_commands()
|
|
134
328
|
|
|
135
|
-
events_init = await
|
|
329
|
+
events_init = await API.get(self.session, "/events?after=0&wait=0")
|
|
136
330
|
last = events_init["last"]
|
|
137
331
|
fails = 0
|
|
138
332
|
|
|
333
|
+
if "ready" in self.__event_handlers:
|
|
334
|
+
await self.__event_handlers["ready"]()
|
|
335
|
+
|
|
139
336
|
while True:
|
|
140
337
|
try:
|
|
141
|
-
out = await
|
|
338
|
+
out = await API.get(self.session, f"/events?after={last}&wait=25")
|
|
142
339
|
fails = 0
|
|
143
340
|
except Exception as e:
|
|
144
341
|
fails += 1
|
|
@@ -157,7 +354,7 @@ class App:
|
|
|
157
354
|
try:
|
|
158
355
|
await self._handle_event(self.session, ev)
|
|
159
356
|
except Exception as e:
|
|
160
|
-
|
|
357
|
+
traceback.print_exc()
|
|
161
358
|
|
|
162
359
|
|
|
163
360
|
def event(self, event_type: str):
|
|
@@ -180,7 +377,7 @@ class App:
|
|
|
180
377
|
):
|
|
181
378
|
if not inspect.iscoroutinefunction(func):
|
|
182
379
|
raise InvalidSignature(f"Expected command handler for '{name}' to be a coroutine!")
|
|
183
|
-
self.__commands[name]=Command(
|
|
380
|
+
self.__commands[name.lower()]=Command(
|
|
184
381
|
name=name,
|
|
185
382
|
aliases=aliases,
|
|
186
383
|
description=description,
|
|
@@ -188,8 +385,7 @@ class App:
|
|
|
188
385
|
)
|
|
189
386
|
|
|
190
387
|
for alias in aliases:
|
|
191
|
-
self.__aliases[alias] = self.__commands[name]
|
|
192
|
-
|
|
388
|
+
self.__aliases[alias.lower()] = self.__commands[name.lower()]
|
|
193
389
|
return wrapper
|
|
194
390
|
|
|
195
391
|
def run(self, token: str):
|
|
@@ -202,3 +398,4 @@ class App:
|
|
|
202
398
|
self._yappy_loop.run_until_complete(self._event_loop())
|
|
203
399
|
finally:
|
|
204
400
|
self._yappy_loop.close()
|
|
401
|
+
__all__=["App"]
|
yappy/core.py
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
from .api import *
|
|
2
2
|
from typing import Callable
|
|
3
|
+
import urllib.parse
|
|
4
|
+
from .utils import get_asset_url
|
|
3
5
|
|
|
4
|
-
class
|
|
6
|
+
class Assets:
|
|
5
7
|
def __init__(self,
|
|
6
8
|
avatar: str = "",
|
|
7
|
-
banner: str = ""
|
|
9
|
+
banner: str = "",
|
|
10
|
+
icon: str = ""
|
|
8
11
|
):
|
|
9
|
-
self.avatar = avatar
|
|
10
|
-
self.banner = banner
|
|
12
|
+
self.avatar = get_asset_url("avatar", avatar)
|
|
13
|
+
self.banner = get_asset_url("banner", banner)
|
|
14
|
+
self.icon = get_asset_url("icon", icon)
|
|
11
15
|
|
|
12
16
|
class User:
|
|
13
17
|
def __init__(self,
|
|
18
|
+
bot = None,
|
|
14
19
|
name: str ="",
|
|
15
20
|
display_name: str = "",
|
|
16
21
|
id=0,
|
|
17
|
-
|
|
22
|
+
assets: Assets | None = None,
|
|
18
23
|
created_at: str = "",
|
|
19
24
|
bio: str = "",
|
|
25
|
+
pronouns: str = "",
|
|
26
|
+
badges: list = [],
|
|
20
27
|
is_bot: bool = True
|
|
21
28
|
):
|
|
29
|
+
self.bot = bot
|
|
22
30
|
self.name = name
|
|
23
31
|
self.id = id
|
|
24
|
-
self.
|
|
25
|
-
self.display_name = display_name
|
|
32
|
+
self.assets = assets
|
|
33
|
+
self.display_name = display_name or name
|
|
26
34
|
self.is_bot = is_bot
|
|
27
35
|
self.bio = bio
|
|
28
36
|
|
|
@@ -43,15 +51,163 @@ class Command:
|
|
|
43
51
|
self.description = description
|
|
44
52
|
self.func = func
|
|
45
53
|
|
|
54
|
+
class Permissions:
|
|
55
|
+
def __init__(self, **perms):
|
|
56
|
+
self._perms = perms
|
|
57
|
+
def has_perm(self, perm):
|
|
58
|
+
return self._perms.get(perm, False)
|
|
59
|
+
|
|
60
|
+
class Role:
|
|
61
|
+
def __init__(self,
|
|
62
|
+
id: str = "",
|
|
63
|
+
name: str = "",
|
|
64
|
+
color: str = "",
|
|
65
|
+
permissions: Permissions | None = None,
|
|
66
|
+
auto: None = None, # unused
|
|
67
|
+
position: int = 0,
|
|
68
|
+
|
|
69
|
+
):
|
|
70
|
+
self.id = id
|
|
71
|
+
self.name = name
|
|
72
|
+
self.color = color
|
|
73
|
+
self.permissions = permissions
|
|
74
|
+
self.auto = auto
|
|
75
|
+
self.position = position
|
|
76
|
+
|
|
77
|
+
class Guild:
|
|
78
|
+
def __init__(self,
|
|
79
|
+
bot,
|
|
80
|
+
id: str = "",
|
|
81
|
+
name: str = "",
|
|
82
|
+
created_at: str = "",
|
|
83
|
+
discoverable: bool = True,
|
|
84
|
+
member_count: int = 1,
|
|
85
|
+
owner: User | None = None,
|
|
86
|
+
assets: Assets | None = None,
|
|
87
|
+
roles: list[Role] = [],
|
|
88
|
+
members: list[Member] = []
|
|
89
|
+
):
|
|
90
|
+
self.id = id
|
|
91
|
+
self.name = name
|
|
92
|
+
self.created_at = created_at
|
|
93
|
+
self.member_count = member_count
|
|
94
|
+
self.owner = owner
|
|
95
|
+
self.assets = assets
|
|
96
|
+
self.discoverable = discoverable
|
|
97
|
+
self.roles = roles
|
|
98
|
+
self.bot = bot
|
|
99
|
+
|
|
100
|
+
self.members = members
|
|
101
|
+
|
|
102
|
+
self._cache={"members": {}, "roles": {
|
|
103
|
+
r.id: r for r in roles
|
|
104
|
+
}}
|
|
105
|
+
|
|
106
|
+
for m in members:
|
|
107
|
+
self._cache[m.id] = m
|
|
108
|
+
m. guild = self
|
|
109
|
+
async def find_member(self, mid):
|
|
110
|
+
member = self.get_member(mid)
|
|
111
|
+
if member is None:
|
|
112
|
+
_user_data = (await API.get(
|
|
113
|
+
self.bot.session,
|
|
114
|
+
f"/servers/{self.id}/members/{mid}"
|
|
115
|
+
))["member"]
|
|
116
|
+
|
|
117
|
+
roles = [
|
|
118
|
+
self._cache["roles"][role_id]
|
|
119
|
+
for role_id in _user_data["roles"]
|
|
120
|
+
if role_id in self._cache["roles"]
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
member = Member(
|
|
124
|
+
bot=self.bot,
|
|
125
|
+
guild=self, roles=roles,
|
|
126
|
+
id=id,
|
|
127
|
+
name = _user_data["username"],
|
|
128
|
+
display_name = _user_data["display_name"],
|
|
129
|
+
badges = _user_data["badges"],
|
|
130
|
+
joined_at = _user_data["joined_at"],
|
|
131
|
+
nickname = _user_data["nickname"],
|
|
132
|
+
assets = Assets(
|
|
133
|
+
avatar = "https://files.yapper.website/avatars/"+_user_data.get("avatar_path",""),
|
|
134
|
+
banner = "https://files.yapper.website/banners/"+_user_data.get("banner_path", "")
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
self._cache["members"][mid] = member
|
|
139
|
+
|
|
140
|
+
return member
|
|
141
|
+
def get_member(self, mid):
|
|
142
|
+
return self._cache.get(mid, None)
|
|
143
|
+
|
|
46
144
|
class Channel:
|
|
47
145
|
def __init__(self,
|
|
48
146
|
bot,
|
|
49
147
|
id=0,
|
|
50
|
-
name: str = ""
|
|
148
|
+
name: str = "",
|
|
149
|
+
kind: str = "",
|
|
150
|
+
private: bool = False,
|
|
151
|
+
slowmode: int = 0,
|
|
152
|
+
category_id: str = "",
|
|
153
|
+
guild: Guild | None = None,
|
|
154
|
+
read_only: bool = False
|
|
51
155
|
):
|
|
52
156
|
self.id = id
|
|
53
157
|
self.bot = bot
|
|
54
158
|
self.name = name
|
|
159
|
+
self.kind = kind
|
|
160
|
+
self.private = private
|
|
161
|
+
self.slowmode = slowmode
|
|
162
|
+
self.category_id = category_id
|
|
163
|
+
self.guild = guild
|
|
164
|
+
self.read_only = read_only
|
|
165
|
+
|
|
166
|
+
async def send(self, content, embeds=[], components=[]):
|
|
167
|
+
await API.send(
|
|
168
|
+
self.bot.session, self.id, {
|
|
169
|
+
"content": content,
|
|
170
|
+
"embeds": embeds,
|
|
171
|
+
"components": components,
|
|
172
|
+
"reply_to": None
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
class Member(User):
|
|
177
|
+
def __init__(
|
|
178
|
+
self,
|
|
179
|
+
guild: guild | None = None,
|
|
180
|
+
roles: list[Role] = [],
|
|
181
|
+
joined_at: str = "",
|
|
182
|
+
nickname: str = "",
|
|
183
|
+
*args, **kwargs
|
|
184
|
+
):
|
|
185
|
+
super().__init__(*args, **kwargs)
|
|
186
|
+
self.guild = guild
|
|
187
|
+
self.roles = roles
|
|
188
|
+
self.joined_at = joined_at
|
|
189
|
+
self.nickname = nickname or self.display_name
|
|
190
|
+
|
|
191
|
+
async def add_role(self, role):
|
|
192
|
+
role_id = role if isinstance(role, str) else role.id
|
|
193
|
+
return API.put(self.bot.session, f"/servers/{self.guild.id}/members/{self.id}/roles/{role_id}")
|
|
194
|
+
|
|
195
|
+
async def remove_role(self, role):
|
|
196
|
+
role_id = role if isinstance(role, str) else role.id
|
|
197
|
+
return API.delete(self.bot.session, f"/servers/{self.guild.id}/members/{self.id}/roles/{role_id}")
|
|
198
|
+
|
|
199
|
+
async def kick(self):
|
|
200
|
+
return API.post(self.bot.session, f"/servers/{self.guild.id}/", {"user_id": self.id})
|
|
201
|
+
|
|
202
|
+
async def ban(self):
|
|
203
|
+
return API.post(self.bot.session, f"/servers/{self.guild.id}/bans", {"user_id": self.id})
|
|
204
|
+
|
|
205
|
+
async def unban(self):
|
|
206
|
+
return API.delete(self.bot.session, f"/servers/{self.guild.id}/bans/{self.id}")
|
|
207
|
+
|
|
208
|
+
async def timeout(self, minutes, reason=""):
|
|
209
|
+
return API.post(self.bot.session, f"/servers/{self.guild.id}/timeouts", {"user_id": self._id, "minutes": minutes, "reason": reason})
|
|
210
|
+
|
|
55
211
|
|
|
56
212
|
class Message:
|
|
57
213
|
def __init__(self,
|
|
@@ -69,37 +225,48 @@ class Message:
|
|
|
69
225
|
self.content = content
|
|
70
226
|
|
|
71
227
|
self.author = author
|
|
228
|
+
async def reply(self, content, embeds=[], components=[]):
|
|
229
|
+
await API.send(
|
|
230
|
+
self.bot.session, self.channel.id, {
|
|
231
|
+
"content": content,
|
|
232
|
+
"embeds": embeds,
|
|
233
|
+
"components": components,
|
|
234
|
+
"reply_to": self.id
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
async def delete(self):
|
|
238
|
+
await API.delete(self.bot.session, f"/channels/{self.channel.id}/messages/{self.id}")
|
|
239
|
+
async def edit(self, content, embeds=[], components=[]):
|
|
240
|
+
await API.patch(self.bot.session, f"/channels/{self.channels.id}/messages/{self.id}", {
|
|
241
|
+
"content": content,
|
|
242
|
+
"embeds": embeds,
|
|
243
|
+
"components": components
|
|
244
|
+
})
|
|
245
|
+
async def send(self, content, embeds=[], components=[]):
|
|
246
|
+
await self.channel.send(*args, **kwargs)
|
|
247
|
+
async def react(self, emoji):
|
|
248
|
+
return await API.put(
|
|
249
|
+
self.bot.session,
|
|
250
|
+
f"/channels/{self.channel.id}/messages/{self.id}/reactions/{urllib.parse.quote(emoji)}"
|
|
251
|
+
)
|
|
252
|
+
|
|
72
253
|
|
|
73
254
|
class Context:
|
|
74
255
|
def __init__(self,
|
|
75
256
|
bot,
|
|
76
257
|
is_bot: bool = False,
|
|
77
258
|
message: Message | None = None,
|
|
78
|
-
channel: Channel | None = None
|
|
259
|
+
channel: Channel | None = None,
|
|
260
|
+
author: Member | None = None
|
|
79
261
|
):
|
|
80
262
|
self.bot = bot
|
|
81
263
|
self.is_bot = is_bot
|
|
82
264
|
self.message = message
|
|
83
265
|
self.channel = channel
|
|
84
|
-
|
|
85
|
-
async def reply(
|
|
86
|
-
self,
|
|
87
|
-
content: str = "Hi"
|
|
88
|
-
):
|
|
89
|
-
await api.send(
|
|
90
|
-
self.bot.session, self.channel.id, data={
|
|
91
|
-
"content": content,
|
|
92
|
-
"reply_to": self.message.id
|
|
93
|
-
}
|
|
94
|
-
)
|
|
266
|
+
self.author = author
|
|
95
267
|
|
|
96
|
-
|
|
97
|
-
self
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
self.bot.session, self.channel.id, data={
|
|
102
|
-
"content": content,
|
|
103
|
-
"reply_to": None
|
|
104
|
-
}
|
|
105
|
-
)
|
|
268
|
+
self.reply = self.message.reply
|
|
269
|
+
self.delete = self.message.delete
|
|
270
|
+
self.edit = self.message.edit
|
|
271
|
+
self.react = self.message.react
|
|
272
|
+
self.send = self.message.send
|
yappy/parser.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, List, Union
|
|
3
|
+
|
|
4
|
+
class CommandParser:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.token_regex = re.compile(
|
|
7
|
+
r'"([^"\\]*(?:\\.[^"\\]*)*)"|'
|
|
8
|
+
r"'([^'\\]*(?:\\.[^'\\]*)*)'|"
|
|
9
|
+
r'(<[#@&!]+[^\s>]+>)|'
|
|
10
|
+
r'([^\s"\']+)'
|
|
11
|
+
)
|
|
12
|
+
self.user_regex = re.compile(r'^<@!?([0-9\-]+)>$|^<@!(.+)>$')
|
|
13
|
+
self.channel_regex = re.compile(r'^<#([0-9\-]+)>$|^<#(.+)>$')
|
|
14
|
+
self.role_regex = re.compile(r'^<@&([0-9\-]+)>$|^<@&(.+)>$')
|
|
15
|
+
|
|
16
|
+
async def parse_args(self, bot, text: str) -> List[Any]:
|
|
17
|
+
parsed_arguments = []
|
|
18
|
+
|
|
19
|
+
for match in self.token_regex.finditer(text):
|
|
20
|
+
dq_str, sq_str, _raw, unquoted = match.groups()
|
|
21
|
+
|
|
22
|
+
if dq_str is not None:
|
|
23
|
+
parsed_arguments.append(dq_str.encode().decode('unicode_escape'))
|
|
24
|
+
continue
|
|
25
|
+
if sq_str is not None:
|
|
26
|
+
parsed_arguments.append(sq_str.encode().decode('unicode_escape'))
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
if _raw is not None:
|
|
30
|
+
parsed_arguments.append(await self._process_mentions(bot, _raw))
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if unquoted is not None:
|
|
34
|
+
parsed_arguments.append(self._parse_primitive(unquoted))
|
|
35
|
+
|
|
36
|
+
return parsed_arguments
|
|
37
|
+
|
|
38
|
+
async def _process_mentions(self, bot, token: str) -> Any:
|
|
39
|
+
if user_match := self.user_regex.match(token):
|
|
40
|
+
user_id = user_match.group(1) or user_match.group(2)
|
|
41
|
+
return await self._parse_user(bot, user_id)
|
|
42
|
+
|
|
43
|
+
if channel_match := self.channel_regex.match(token):
|
|
44
|
+
channel_id = channel_match.group(1) or channel_match.group(2)
|
|
45
|
+
return await self._parse_channel(bot, channel_id)
|
|
46
|
+
|
|
47
|
+
if role_match := self.role_regex.match(token):
|
|
48
|
+
role_id = role_match.group(1) or role_match.group(2)
|
|
49
|
+
return await self._parse_role(bot, role_id)
|
|
50
|
+
|
|
51
|
+
return token
|
|
52
|
+
|
|
53
|
+
def _parse_primitive(self, token: str) -> Union[int, float, str]:
|
|
54
|
+
if re.match(r'^-?\d+$', token):
|
|
55
|
+
return int(token)
|
|
56
|
+
if re.match(r'^-?\d*\.\d+$', token):
|
|
57
|
+
return float(token)
|
|
58
|
+
return token
|
|
59
|
+
|
|
60
|
+
async def _parse_user(self, bot, user_id: str):
|
|
61
|
+
return await bot.find_user(user_id)
|
|
62
|
+
|
|
63
|
+
async def _parse_channel(self, bot, channel_id: str):
|
|
64
|
+
return await bot.find_channel(channel_id)
|
|
65
|
+
|
|
66
|
+
async def _parse_role(self, bot, role_id: str):
|
|
67
|
+
return await bot.find_role(role_id)
|
|
68
|
+
|
|
69
|
+
parser = CommandParser()
|
yappy/utils.py
ADDED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
yappy/__init__.py,sha256=BZU8ktPMVc9XeJ5YJeeCAPCjRyaxYIU_zusvjiSi5_Q,21
|
|
2
|
-
yappy/api.py,sha256=KP7ma6tpqgsExCx4KccUetbQC3PWnio0Mo1bvnh4JGs,1862
|
|
3
|
-
yappy/app.py,sha256=zM17fXaZne5f9n7dp2SqcNxIkD28QP40SEPRd39fKa4,5932
|
|
4
|
-
yappy/consts.py,sha256=0pIUIY1Ayo6_q3XhWr9hvKLgOT7TC9RgsjPZNVYy8QQ,51
|
|
5
|
-
yappy/core.py,sha256=xdPKbg_evRvadOP1bzNOuiV8BJxhnWLmqX99qobfCL0,2347
|
|
6
|
-
yappy/errors.py,sha256=BZ42Du8XWA_SY9tR5Ye8gVWWVKAvRDZZ8Grt_CoOOZs,133
|
|
7
|
-
python_yappy-0.1.0.dist-info/WHEEL,sha256=lrO5MD1WVAWzcbNy_L2BwtfPrcM3KfUFpbKYXLkJX4A,80
|
|
8
|
-
python_yappy-0.1.0.dist-info/METADATA,sha256=1Y5i62UYKOYX1MxGmDFAyh_qzzlDHo-wA81ustUSxYg,4668
|
|
9
|
-
python_yappy-0.1.0.dist-info/RECORD,,
|
|
File without changes
|