yaylib 0.0.0__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.
yaylib/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """
2
+
3
+ Yay! (nanameue, Inc.) API Client
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+
6
+ A powerful API wrapper for Yay! (yay.space) written in Python.
7
+
8
+ :copyright: (c) 2023 ekkx
9
+ :license: MIT, see LICENSE for more details.
10
+
11
+ https://github.com/ekkx/yaylib
12
+
13
+ """
14
+
15
+ __title__ = "yaylib"
16
+ __author__ = "ekkx"
17
+ __license__ = "MIT"
18
+ __copyright__ = "Copyright (c) 2023 ekkx"
19
+ __version__ = "1.5.0.dev4"
20
+
21
+ from .client import Client
22
+ from .constants import *
23
+ from .errors import *
24
+ from .models import *
25
+ from .responses import *
26
+ from .state import State
27
+ from .utils import mention
28
+ from .ws import *
29
+
30
+ __all__ = (
31
+ "Client",
32
+ "constants",
33
+ "errors",
34
+ "models",
35
+ "responses",
36
+ "State",
37
+ "mention",
38
+ "ws",
39
+ )
yaylib/api/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ """
2
+
3
+ Yay! (nanameue, Inc.) API Client
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+
6
+ A powerful API wrapper for Yay! (yay.space) written in Python.
7
+
8
+ :copyright: (c) 2023 ekkx
9
+ :license: MIT, see LICENSE for more details.
10
+
11
+ https://github.com/ekkx/yaylib
12
+
13
+ """
14
+
15
+ from .auth import *
16
+ from .call import *
17
+ from .chat import *
18
+ from .group import *
19
+ from .misc import *
20
+ from .notification import *
21
+ from .post import *
22
+ from .review import *
23
+ from .thread import *
24
+ from .user import *
25
+
26
+ __all__ = [
27
+ "auth",
28
+ "call",
29
+ "chat",
30
+ "group",
31
+ "misc",
32
+ "notification",
33
+ "post",
34
+ "review",
35
+ "thread",
36
+ "user",
37
+ ]
yaylib/api/auth.py ADDED
@@ -0,0 +1,231 @@
1
+ """
2
+ MIT License
3
+
4
+ Copyright (c) 2023 ekkx
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ from datetime import datetime
26
+ from typing import Optional
27
+
28
+ from cryptography import fernet
29
+
30
+ from .. import config
31
+ from ..responses import LoginUpdateResponse, LoginUserResponse, Response, TokenResponse
32
+ from ..state import LocalUser
33
+ from ..utils import md5
34
+
35
+
36
+ class AuthApi:
37
+ """認証 API
38
+
39
+ Args:
40
+ client (Client):
41
+ """
42
+
43
+ def __init__(self, client) -> None:
44
+ # pylint: disable=import-outside-toplevel
45
+ from ..client import Client
46
+
47
+ self.__client: Client = client
48
+
49
+ async def change_email(self, **params) -> LoginUpdateResponse:
50
+ """メールアドレスを変更する
51
+
52
+ Args:
53
+ email (str):
54
+ password (str):
55
+ email_grant_token (str, optional):
56
+
57
+ Returns:
58
+ LoginUpdateResponse:
59
+ """
60
+ return await self.__client.request(
61
+ "PUT",
62
+ config.API_HOST + "/v1/users/change_email",
63
+ json=params,
64
+ return_type=LoginUpdateResponse,
65
+ )
66
+
67
+ async def change_password(self, **params) -> LoginUpdateResponse:
68
+ """パスワードを変更する
69
+
70
+ Args:
71
+ current_password (str):
72
+ new_password (str):
73
+
74
+ Returns:
75
+ LoginUpdateResponse:
76
+ """
77
+ return await self.__client.request(
78
+ "PUT",
79
+ config.API_HOST + "/v1/users/change_email",
80
+ json=params,
81
+ return_type=LoginUpdateResponse,
82
+ )
83
+
84
+ async def get_token(self, **params) -> TokenResponse:
85
+ """認証トークンを取得する
86
+
87
+ Args:
88
+ grant_type (str):
89
+ refresh_token (str, optional):
90
+ email (str, optional):
91
+ password (str, optional):
92
+
93
+ Returns:
94
+ TokenResponse:
95
+ """
96
+ return await self.__client.request(
97
+ "POST",
98
+ config.API_HOST + "/api/v1/oauth/token",
99
+ json=params,
100
+ return_type=TokenResponse,
101
+ )
102
+
103
+ async def login(
104
+ self, email: str, password: str, two_fa_code: Optional[str] = None
105
+ ) -> LoginUserResponse:
106
+ """メールアドレスでログインする
107
+
108
+ Args:
109
+ email (str):
110
+ password (str):
111
+ two_fa_code (str, optional):
112
+
113
+ Returns:
114
+ LoginUserResponse:
115
+ """
116
+ if not self.__client.state.has_encryption_key():
117
+ self.__client.state.set_encryption_key(password)
118
+
119
+ user = self.__client.state.get_user_by_email(email)
120
+ if user is not None:
121
+ try:
122
+ self.__client.state.set_user(self.__client.state.decrypt(user))
123
+ except fernet.InvalidToken as exc:
124
+ self.__client.state.destory(user.user_id)
125
+ self.__client.logger.error(
126
+ # pylint: disable=line-too-long
127
+ "Failed to decrypt the credentials stored locally. This might be due to a recent password change. Please try logging in again."
128
+ )
129
+ raise exc
130
+
131
+ self.__client.logger.info(
132
+ f"User found in local storage - UID: {user.user_id}"
133
+ )
134
+
135
+ return LoginUserResponse(
136
+ {
137
+ "access_token": self.__client.access_token,
138
+ "refresh_token": self.__client.refresh_token,
139
+ "user_id": self.__client.user_id,
140
+ }
141
+ )
142
+
143
+ payload = {
144
+ "api_key": config.API_KEY,
145
+ "email": email,
146
+ "password": password,
147
+ "uuid": self.__client.device_uuid,
148
+ }
149
+ if two_fa_code is not None:
150
+ payload["two_fa_code"] = two_fa_code
151
+
152
+ response: LoginUserResponse = await self.__client.request(
153
+ "POST",
154
+ config.API_HOST + "/v3/users/login_with_email",
155
+ json=payload,
156
+ return_type=LoginUserResponse,
157
+ )
158
+
159
+ self.__client.state.set_user(
160
+ LocalUser(
161
+ user_id=response.user_id,
162
+ email=email,
163
+ device_uuid=self.__client.device_uuid,
164
+ access_token=response.access_token,
165
+ refresh_token=response.refresh_token,
166
+ )
167
+ )
168
+ self.__client.state.save()
169
+
170
+ self.__client.logger.info(
171
+ f"Authentication successful! - UID: {response.user_id}"
172
+ )
173
+
174
+ return response
175
+
176
+ async def resend_confirm_email(self) -> Response:
177
+ """確認メールを再送信する
178
+
179
+ Returns:
180
+ Response:
181
+ """
182
+ return await self.__client.request(
183
+ "POST",
184
+ config.API_HOST + "/v2/users/resend_confirm_email",
185
+ return_type=Response,
186
+ )
187
+
188
+ async def restore_user(self, **params) -> LoginUserResponse:
189
+ """ユーザーを復元する
190
+
191
+ Args:
192
+ user_id (int):
193
+
194
+ Returns:
195
+ LoginUserResponse:
196
+ """
197
+ return await self.__client.request(
198
+ "POST",
199
+ config.API_HOST + "/v2/users/restore",
200
+ json={
201
+ "api_key": config.API_KEY,
202
+ "uuid": self.__client.device_uuid,
203
+ "timestamp": int(datetime.now().timestamp()),
204
+ "signed_info": md5(
205
+ self.__client.device_uuid,
206
+ int(datetime.now().timestamp()),
207
+ False,
208
+ ),
209
+ }.update(params),
210
+ )
211
+
212
+ async def save_account_with_email(self, **params) -> LoginUpdateResponse:
213
+ """メールアドレスでアカウントを保存する
214
+
215
+ Args:
216
+ email (str):
217
+ password (str, optional):
218
+ current_password (str, optional):
219
+ email_grant_token (str, optional):
220
+
221
+ Returns:
222
+ LoginUpdateResponse:
223
+ """
224
+ return await self.__client.request(
225
+ "POST",
226
+ config.API_HOST + "/v3/users/login_update",
227
+ json={
228
+ "api_key": config.API_KEY,
229
+ }.update(params),
230
+ return_type=LoginUpdateResponse,
231
+ )
yaylib/api/call.py ADDED
@@ -0,0 +1,360 @@
1
+ """
2
+ MIT License
3
+
4
+ Copyright (c) 2023 ekkx
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ from typing import List
26
+
27
+ from .. import config
28
+ from ..responses import (
29
+ BgmsResponse,
30
+ CallActionSignatureResponse,
31
+ CallStatusResponse,
32
+ ConferenceCallResponse,
33
+ GamesResponse,
34
+ GenresResponse,
35
+ PostResponse,
36
+ PostsResponse,
37
+ Response,
38
+ UsersByTimestampResponse,
39
+ )
40
+
41
+
42
+ class CallApi:
43
+ """通話 API"""
44
+
45
+ def __init__(self, client) -> None:
46
+ # pylint: disable=import-outside-toplevel
47
+ from ..client import Client
48
+
49
+ self.__client: Client = client
50
+
51
+ async def get_user_active_call(self, user_id: int) -> PostResponse:
52
+ """ユーザーが参加中の通話を取得する
53
+
54
+ Args:
55
+ user_id (int):
56
+
57
+ Returns:
58
+ PostResponse:
59
+ """
60
+ return await self.__client.request(
61
+ "GET",
62
+ config.API_HOST + "/v1/posts/active_call",
63
+ params={"user_id": user_id},
64
+ return_type=PostResponse,
65
+ )
66
+
67
+ async def get_bgms(self) -> BgmsResponse:
68
+ """通話のBGMを取得する
69
+
70
+ Returns:
71
+ BgmsResponse:
72
+ """
73
+ return await self.__client.request(
74
+ "GET",
75
+ config.API_HOST + "/v1/calls/bgm",
76
+ return_type=BgmsResponse,
77
+ )
78
+
79
+ async def get_call(self, call_id: int) -> ConferenceCallResponse:
80
+ """通話を取得する
81
+
82
+ Args:
83
+ call_id (int):
84
+
85
+ Returns:
86
+ ConferenceCallResponse:
87
+ """
88
+ return await self.__client.request(
89
+ "GET",
90
+ config.API_HOST + f"/v1/calls/conferences/{call_id}",
91
+ return_type=ConferenceCallResponse,
92
+ )
93
+
94
+ async def get_call_invitable_users(
95
+ self, call_id: int, **params
96
+ ) -> UsersByTimestampResponse:
97
+ """通話に招待可能なユーザーを取得する
98
+
99
+ Args:
100
+ call_id (int):
101
+ from_timestamp (int, optional):
102
+
103
+ Returns:
104
+ UsersByTimestampResponse:
105
+ """
106
+ # @Nullable @Query("user[nickname]")
107
+ return await self.__client.request(
108
+ "GET",
109
+ config.API_HOST + f"/v1/calls/{call_id}/users/invitable",
110
+ params=params,
111
+ return_type=UsersByTimestampResponse,
112
+ )
113
+
114
+ async def get_call_status(self, opponent_id: int) -> CallStatusResponse:
115
+ """通話の状態を取得します
116
+
117
+ Args:
118
+ opponent_id (int):
119
+
120
+ Returns:
121
+ CallStatusResponse:
122
+ """
123
+ return await self.__client.request(
124
+ "GET",
125
+ config.API_HOST + f"/v1/calls/phone_status/{opponent_id}",
126
+ return_type=CallStatusResponse,
127
+ )
128
+
129
+ async def get_games(self, **params) -> GamesResponse:
130
+ """通話に設定可能なゲームを取得する
131
+
132
+ Args:
133
+ number (int, optional):
134
+ ids (List[int], optional):
135
+ from_id (int, optional):
136
+
137
+ Returns:
138
+ GamesResponse:
139
+ """
140
+ return await self.__client.request(
141
+ "GET",
142
+ config.API_HOST + "/v1/games/apps",
143
+ params=params,
144
+ return_type=GamesResponse,
145
+ )
146
+
147
+ async def get_genres(self, **params) -> GenresResponse:
148
+ """通話のジャンルを取得する
149
+
150
+ Args:
151
+ number (int, optional):
152
+ from (int, optional):
153
+
154
+ Returns:
155
+ GenresResponse:
156
+ """
157
+ return await self.__client.request(
158
+ "GET",
159
+ config.API_HOST + "/v1/genres",
160
+ params=params,
161
+ return_type=GenresResponse,
162
+ )
163
+
164
+ async def get_group_calls(self, **params) -> PostsResponse:
165
+ """サークル内の通話を取得する
166
+
167
+ Args:
168
+ number (int, optional):
169
+ group_category_id (int, optional):
170
+ from_timestamp (int, optional):
171
+ scope (str, optional):
172
+
173
+ Returns:
174
+ PostsResponse:
175
+ """
176
+ return await self.__client.request(
177
+ "GET",
178
+ config.API_HOST + "/v1/posts/group_calls",
179
+ params=params,
180
+ return_type=PostsResponse,
181
+ )
182
+
183
+ async def invite_online_followings_to_call(
184
+ self, call_id: int, **params
185
+ ) -> Response:
186
+ """オンラインの友達をまとめて通話に招待します
187
+
188
+ Args:
189
+ call_id (int, optional):
190
+ group_id (str, optional):
191
+
192
+ Returns:
193
+ Response:
194
+ """
195
+ return await self.__client.request(
196
+ "POST",
197
+ config.API_HOST + f"/v1/calls/{call_id}/bulk_invite",
198
+ params=params,
199
+ return_type=Response,
200
+ )
201
+
202
+ async def invite_users_to_call(self, call_id: int, user_ids: List[int]) -> Response:
203
+ """通話に複数のユーザーを招待する
204
+
205
+ Args:
206
+ call_id (int):
207
+ user_ids (List[int]):
208
+
209
+ Returns:
210
+ Response:
211
+ """
212
+ return await self.__client.request(
213
+ "POST",
214
+ config.API_HOST + f"/v1/calls/conference_calls/{call_id}/invite",
215
+ payload={"call_id": call_id, "user_ids": user_ids},
216
+ return_type=Response,
217
+ )
218
+
219
+ async def invite_users_to_chat_call(self, **params) -> Response:
220
+ """ユーザーをチャット通話に招待する
221
+
222
+ Args:
223
+ chat_room_id (int):
224
+ room_id (int):
225
+ room_url (str):
226
+
227
+ Returns:
228
+ Response:
229
+ """
230
+ return await self.__client.request(
231
+ "POST",
232
+ config.API_HOST + "/v2/calls/invite",
233
+ payload=params,
234
+ return_type=Response,
235
+ )
236
+
237
+ async def kick_user_from_call(
238
+ self, call_id: int, **params
239
+ ) -> CallActionSignatureResponse:
240
+ """ユーザーを通話からキックする
241
+
242
+ Args:
243
+ call_id (int):
244
+ uuid (int):
245
+ ban (bool):
246
+
247
+ Returns:
248
+ Response:
249
+ """
250
+ return await self.__client.request(
251
+ "POST",
252
+ config.API_HOST + f"/v3/calls/conference_calls/{call_id}/kick",
253
+ payload=params,
254
+ return_type=CallActionSignatureResponse,
255
+ )
256
+
257
+ async def start_call(self, call_id: int, **params) -> Response:
258
+ """通話を開始する
259
+
260
+ Args:
261
+ call_id (int):
262
+ joinable_by (str):
263
+ game_title (str, optional):
264
+ category_id (str, optional):
265
+
266
+ Returns:
267
+ Response:
268
+ """
269
+ return await self.__client.request(
270
+ "PUT",
271
+ config.API_HOST + f"/v1/calls/{call_id}",
272
+ payload=params,
273
+ return_type=Response,
274
+ )
275
+
276
+ async def set_user_role(self, call_id: int, user_id: int, role: str) -> Response:
277
+ """通話の参加者に役職を付与する
278
+
279
+ Args:
280
+ call_id (int):
281
+ user_id (int):
282
+ role (str):
283
+
284
+ Returns:
285
+ Response:
286
+ """
287
+ return await self.__client.request(
288
+ "PUT",
289
+ config.API_HOST + f"/v1/calls/{call_id}/users/{user_id}",
290
+ payload={"role": role},
291
+ return_type=Response,
292
+ )
293
+
294
+ async def join_call(self, **params) -> ConferenceCallResponse:
295
+ """通話に参加する
296
+
297
+ Args:
298
+ conference_id (int):
299
+ call_sid (str, optional):
300
+
301
+ Returns:
302
+ ConferenceCallResponse:
303
+ """
304
+ return await self.__client.request(
305
+ "POST",
306
+ config.API_HOST + "/v1/calls/start_conference_call",
307
+ payload=params,
308
+ return_type=ConferenceCallResponse,
309
+ )
310
+
311
+ async def leave_call(self, **params) -> Response:
312
+ """通話から退出する
313
+
314
+ Args:
315
+ conference_id (int):
316
+ call_sid (str, optional):
317
+
318
+ Returns:
319
+ Response:
320
+ """
321
+ return await self.__client.request(
322
+ "POST",
323
+ config.API_HOST + "/v1/calls/leave_conference_call",
324
+ payload=params,
325
+ return_type=Response,
326
+ )
327
+
328
+ async def join_call_as_anonymous(self, **params) -> ConferenceCallResponse:
329
+ """匿名で通話に参加する
330
+
331
+ Args:
332
+ conference_id (int):
333
+ agora_uid (str):
334
+
335
+ Returns:
336
+ ConferenceCallResponse:
337
+ """
338
+ return await self.__client.request(
339
+ "POST",
340
+ config.API_HOST + "/v1/anonymous_calls/start_conference_call",
341
+ payload=params,
342
+ return_type=ConferenceCallResponse,
343
+ )
344
+
345
+ async def leave_call_as_anonymous(self, **params) -> Response:
346
+ """匿名で参加した通話を退出する
347
+
348
+ Args:
349
+ conference_id (int):
350
+ agora_uid (str, optional):
351
+
352
+ Returns:
353
+ Response: _description_
354
+ """
355
+ return await self.__client.request(
356
+ "POST",
357
+ config.API_HOST + "/v1/anonymous_calls/leave_conference_call",
358
+ payload=params,
359
+ return_type=Response,
360
+ )