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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-yappy
3
- Version: 0.1.0
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
- Create a file named `bot.py`:
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
- @bot.event("message")
47
+ @bot.event(
48
+ "message"
49
+ )
67
50
  async def on_message(ctx):
68
- if ctx.is_bot:
69
- return
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
- name="ping",
76
- description="replies with 'Pong!'",
55
+ name="ping",
56
+ description="replies with 'Pong!'"
77
57
  )
78
58
  async def ping(ctx):
79
- await ctx.reply("Pong!")
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.reply("Hello!")
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 api(session, method, path, body, tries - 1)
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 api:
33
+
34
+ class API:
33
35
  @staticmethod
34
- async def put(session, path="", body=None, tries=3):
35
- await api_push(session, "PUT", path, body, tries)
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 api_push(session, "GET", path, body, tries)
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 api_push(session, "POST", path, body, tries)
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 send(session, channel_id=0, data=None, tries=3):
44
- await api_push(session, "POST", f"/channels/{channel_id}/messages", data, tries)
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__(self, verbose=False):
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 = {"users":{}}
32
+ self.__cache = {
33
+ "users":{}, "channels":{},
34
+ "guilds": {}, "roles": {}
35
+ }
23
36
 
24
37
  async def _load_bot_info(self):
25
- my_info = await api.get(self.session, "/me")
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
- profile=Profile(
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 get_user(
46
- self,
47
- id,
48
- _data
55
+ async def _cache_members(
56
+ self, id
49
57
  ):
50
- if id in self.__cache["users"]:
51
- return self.__cache["users"]
52
- user = User(
53
- name=_data["author"],
54
- id=id
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
- self.__cache[id]=user
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 _build_context(self, ev):
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=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=self.get_user(
76
- d["author_id"], d
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
- if (cmd_name:=d.get("command")):
92
- if cmd_name in self.__commands:
93
- await self.__commands[cmd_name].func(
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 api.put(
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 api.get(self.session, "/events?after=0&wait=0")
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 api.get(self.session, f"/events?after={last}&wait=25")
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
- print("error handling", ev["type"], "event:", e)
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 Profile:
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
- profile: Profile | None = None,
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.profile = profile
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
- async def send(
97
- self,
98
- content: str = "Hi"
99
- ):
100
- await api.send(
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
@@ -0,0 +1,4 @@
1
+ def get_asset_url(type: str, path: str):
2
+ if not path: return None
3
+
4
+ return f"https://files.yapper.website/{type}s/{path}"
@@ -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,,