Skip to content

Commit d7be2da

Browse files
committedSep 11, 2023
Add sticker controller and other minor fixes
1 parent e4998e0 commit d7be2da

23 files changed

+874
-48
lines changed
 

Diff for: ‎.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ build
1414
test*
1515
*png
1616
*.sqlite
17-
downloads/
17+
downloads/
18+
*sh

Diff for: ‎swibots/api/bot/models/bot_info.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,25 @@ def __init__(
3333
self.commands: List[BotCommand] = commands or []
3434
self.description: Optional[str] = description
3535

36+
def to_json_request(self) -> JSONDict:
37+
return {
38+
"commands": [command.to_json() for command in self.commands],
39+
"description": self.description,
40+
}
41+
3642
def from_json(self, data: Optional[JSONDict] = None) -> "BotInfo":
3743
super().from_json(data)
3844
if data is not None:
39-
self.commands = [BotCommand().from_json(x) for x in data.get("commands", [])]
45+
self.commands = [
46+
BotCommand().from_json(x) for x in data.get("commands", [])
47+
]
4048
self.description = data.get("description")
4149
return self
4250

4351
def to_json(self) -> JSONDict:
4452
data = super().to_json()
4553
if not data.get("commands"):
46-
data['commands'] = [x.to_json() for x in self.commands]
54+
data["commands"] = [x.to_json() for x in self.commands]
4755
if not data.get("description"):
48-
data['description'] = self.description
56+
data["description"] = self.description
4957
return data

Diff for: ‎swibots/api/chat/chat_client.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
MessageController,
66
ChatController,
77
MediaController,
8+
StickerController
89
)
910
from swibots.api.chat.events import (
1011
ChatEvent,
@@ -56,6 +57,7 @@ def __init__(
5657
self._messages: MessageController = None
5758
self._post: ChatController = None
5859
self._media: MediaController = None
60+
self._stickers: StickerController = None
5961
self._ws: SwitchWSAsyncClient = None
6062
self._started = False
6163

@@ -65,6 +67,12 @@ def ws(self) -> SwitchWSAsyncClient:
6567
self._ws = SwitchWSAsyncClient(self._ws_url, self.token)
6668
return self._ws
6769

70+
@property
71+
def stickers(self) -> StickerController:
72+
if self._stickers is None:
73+
self._stickers = StickerController(self)
74+
return self._stickers
75+
6876
@property
6977
def messages(self) -> MessageController:
7078
"""Get the message controller"""
@@ -119,11 +127,10 @@ async def subscribe_to_notifications(self, callback=None) -> AsyncWsSubscription
119127
120128
This is a shortcut for :meth:`subscribe` with the endpoint set to ``/chat/queue/events``
121129
"""
122-
subscription = await self.ws.subscribe(
130+
return await self.ws.subscribe(
123131
"/chat/queue/events",
124132
callback=lambda event: self._parse_event(event, callback),
125133
)
126-
return subscription
127134

128135
async def _parse_event(self, raw_message: WsMessage, callback) -> ChatEvent:
129136
try:

Diff for: ‎swibots/api/chat/controllers/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .message_controller import MessageController
22
from .chat_controller import ChatController
33
from .media_controller import MediaController
4+
from .sticker_controller import StickerController
45

5-
__all__ = ["MessageController", "ChatController", "MediaController"]
6+
__all__ = ["MessageController", "ChatController", "MediaController", "StickerController"]

Diff for: ‎swibots/api/chat/controllers/media_controller.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import json, mimetypes
44
import logging
5+
from io import BytesIO
56
from typing import TYPE_CHECKING, List, Optional
67

78
from swibots.utils.types import (
@@ -31,7 +32,7 @@ def __init__(self, client: "ChatClient"):
3132

3233
async def upload_media(
3334
self,
34-
path: str,
35+
path: str | BytesIO,
3536
caption: Optional[str] = None,
3637
description: Optional[str] = None,
3738
mime_type: Optional[str] = None,
@@ -41,12 +42,18 @@ async def upload_media(
4142
"""upload media from path"""
4243

4344
url = f"{BASE_PATH}/upload-multipart"
45+
if isinstance(path, BytesIO):
46+
file_name = path.name
47+
else:
48+
file_name = path
49+
50+
if not mime_type:
51+
mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
52+
4453
form_data = {
4554
"caption": caption,
4655
"description": description,
4756
"mimeType": mime_type
48-
or mimetypes.guess_type(path)[0]
49-
or "application/octet-stream",
5057
}
5158

5259
reader = ReadCallbackStream(path, None)

Diff for: ‎swibots/api/chat/controllers/sticker_controller.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import logging, mimetypes
2+
from swibots.utils import ReadCallbackStream
3+
from typing import TYPE_CHECKING, List, Optional
4+
from urllib.parse import urlencode
5+
from ..models import Sticker, StickerPack
6+
from io import BytesIO
7+
8+
if TYPE_CHECKING:
9+
from swibots.api.chat import ChatClient
10+
11+
log = logging.getLogger(__name__)
12+
13+
BASE_PATH = "/v1/sticker"
14+
15+
16+
class StickerController:
17+
"""Stickers controller"""
18+
19+
def __init__(self, client: "ChatClient"):
20+
self.client = client
21+
22+
async def get_stickers(
23+
self, pack_id: str, limit: int = 30, offset: int = 0
24+
) -> List[Sticker]:
25+
data = {"stickerPackId": pack_id, "offset": offset, "limit": limit}
26+
response = await self.client.get(f"{BASE_PATH}?{urlencode(data)}")
27+
return self.client.build_list(Sticker, response.data)
28+
29+
async def create_sticker(
30+
self,
31+
sticker: str | BytesIO,
32+
name: str,
33+
description: str,
34+
emoji: str,
35+
pack_id: str,
36+
) -> Sticker:
37+
file_name = sticker.name if isinstance(sticker, BytesIO) else sticker
38+
print(emoji, pack_id, name, description)
39+
response = await self.client.post(
40+
BASE_PATH,
41+
form_data={
42+
"stickerPackId": pack_id,
43+
"linkedEmoji": emoji,
44+
"name": name,
45+
"description": description,
46+
},
47+
files={
48+
"uploadMediaRequest.file": (
49+
file_name,
50+
ReadCallbackStream(sticker),
51+
mimetypes.guess_type(file_name)[0],
52+
)
53+
},
54+
)
55+
print(response.data)
56+
return self.client.build_object(Sticker, response.data)
57+
58+
async def delete_sticker(self, sticker_id: int) -> bool:
59+
await self.client.delete(f"{BASE_PATH}?id={sticker_id}")
60+
return True
61+
62+
# region
63+
64+
async def create_sticker_pack(
65+
self, name: str, pack_type: str, access: str, thumb: str | BytesIO
66+
) -> StickerPack:
67+
form_data = {
68+
"name": name,
69+
"packType": pack_type,
70+
"accessControl": access,
71+
}
72+
files = None
73+
if thumb:
74+
thumb_name = thumb.name if isinstance(thumb, BytesIO) else thumb
75+
files = {
76+
"uploadMediaRequest.file": (
77+
thumb_name,
78+
ReadCallbackStream(thumb),
79+
mimetypes.guess_type(thumb_name)[0],
80+
)
81+
}
82+
print(form_data, files)
83+
response = await self.client.post(
84+
f"{BASE_PATH}/pack", form_data=form_data, files=files
85+
)
86+
return self.client.build_object(StickerPack, response.data)
87+
88+
async def delete_sticker_pack(self, pack_id: int) -> bool:
89+
await self.client.delete(f"{BASE_PATH}/pack?id={pack_id}")
90+
return True
91+
92+
async def search_sticker_packs(
93+
self, query: str, limit: int = 30, offset: int = 0
94+
) -> List[StickerPack]:
95+
data = {"query": query, "offset": offset, "limit": limit}
96+
response = await self.client.get(f"{BASE_PATH}/pack/search?{urlencode(data)}")
97+
return self.client.build_list(StickerPack, response.data)
98+
99+
async def get_all_sticker_packs(self, limit: int = 10, offset: int = 0):
100+
data = {"limit": limit, "offset": offset}
101+
response = await self.client.get(f"{BASE_PATH}/pack?{urlencode(data)}")
102+
return self.client.build_list(StickerPack, response.data)
103+
104+
async def sort_stickers(
105+
self, pack_id: str, sorted_stickers: List[str] = None
106+
) -> StickerPack:
107+
response = await self.client.post(
108+
f"{BASE_PATH}/pack/sort",
109+
data={
110+
"sortedStickers": sorted_stickers,
111+
"id": pack_id,
112+
},
113+
)
114+
return self.client.build_object(StickerPack, response.data)
115+
116+
# endregion

Diff for: ‎swibots/api/chat/methods/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from .answer_inline_query import AnswerInlineQuery
2121
from .download_media import DownloadMedia
2222
from .upload_media import UploadMedia
23+
from .sticker_methods import StickerMethods
24+
2325

2426
class ChatMethods(
2527
ClearConversation,
@@ -42,6 +44,7 @@ class ChatMethods(
4244
AnswerInlineQuery,
4345
DownloadMedia,
4446
UploadMedia,
45-
GetUser
47+
GetUser,
48+
StickerMethods
4649
):
4750
pass

Diff for: ‎swibots/api/chat/methods/sticker_methods.py

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from typing import Type, TypeVar, Optional, List
2+
import swibots
3+
from io import BytesIO
4+
from swibots.api.chat.models import Sticker, StickerPack
5+
6+
7+
class StickerMethods:
8+
async def create_sticker(
9+
self: "swibots.ApiClient",
10+
sticker: str | BytesIO,
11+
name: str,
12+
description: str,
13+
emoji: str,
14+
pack_id: str,
15+
) -> Sticker:
16+
"""Create Sticker
17+
18+
Args:
19+
sticker (str | BytesIO): path to file or BytesIO Object with .name
20+
name (str): Name of the sticker
21+
description (str): Description of the sticker.
22+
emoji (str): emoji linked with sticker.
23+
pack_id (str): pack Id to which sticker belongs.
24+
25+
Returns:
26+
Sticker: the sticker object.
27+
"""
28+
return await self.chat_service.stickers.create_sticker(
29+
sticker=sticker,
30+
name=name,
31+
description=description,
32+
emoji=emoji,
33+
pack_id=pack_id,
34+
)
35+
36+
async def get_stickers(
37+
self: "swibots.ApiClient",
38+
pack_id: str,
39+
limit: Optional[int] = 30,
40+
offset: Optional[int] = 0,
41+
) -> List[Sticker]:
42+
"""Get All stickers from the pack
43+
44+
Args:
45+
pack_id (str): Pack id of sticker.
46+
limit (Optional[int], optional): limit . Defaults to 30.
47+
offset (Optional[int], optional): offset to fetch. Defaults to 0.
48+
49+
Returns:
50+
List[Sticker]
51+
"""
52+
return await self.chat_service.stickers.get_stickers(
53+
pack_id, limit=limit, offset=offset
54+
)
55+
56+
async def create_sticker_pack(
57+
self: "swibots.ApiClient",
58+
name: str,
59+
pack_type: str,
60+
access: str = "GLOBAL",
61+
thumb: Optional[str | BytesIO] = None,
62+
) -> StickerPack:
63+
"""Create Sticker pack
64+
65+
Args:
66+
name (str): Name of sticker pack.
67+
pack_type (str): pack type for the sticker pack (STATIC, ANIMATED, VIDEO).
68+
access (str, optional): access mode for the pack. Defaults to "GLOBAL".
69+
thumb (Optional[str | BytesIO], optional): path to sticker pack thumbnail. Defaults to None.
70+
71+
Returns:
72+
StickerPack: the resultant StickerPack
73+
"""
74+
return await self.chat_service.stickers.create_sticker_pack(
75+
name=name, pack_type=pack_type, access=access, thumb=thumb
76+
)
77+
78+
async def delete_sticker(self: "swibots.ApiClient", sticker_id: str) -> bool:
79+
"""Delete sticker
80+
81+
Args:
82+
sticker_id (str): Sticker ID
83+
84+
Returns:
85+
bool: whether sticker was deleted
86+
"""
87+
return await self.chat_service.stickers.delete_sticker(sticker_id)
88+
89+
async def delete_sticker_pack(self: "swibots.ApiClient", pack_id: str) -> bool:
90+
"""Delete sticker pack
91+
92+
Args:
93+
pack_id (str): Pack ID
94+
95+
Returns:
96+
bool: whether sticker pack was deleted
97+
"""
98+
return await self.chat_service.stickers.delete_sticker_pack(pack_id)
99+
100+
async def search_sticker_packs(
101+
self: "swibots.ApiClient",
102+
query: str,
103+
limit: Optional[int] = 20,
104+
offset: Optional[int] = 0,
105+
) -> List[StickerPack]:
106+
"""Search Sticker Packs
107+
108+
Args:
109+
query (str): query to search
110+
limit (Optional[int], optional): Defaults to 20.
111+
offset (Optional[int], optional): Defaults to 0.
112+
113+
Returns:
114+
List[StickerPack]: List of sticker packs.
115+
"""
116+
return await self.chat_service.stickers.search_sticker_packs(
117+
query=query, limit=limit, offset=offset
118+
)
119+
120+
async def get_all_sticker_packs(
121+
self: "swibots.ApiClient", limit: Optional[int] = 20, offset: Optional[int] = 0
122+
) -> List[StickerPack]:
123+
"""Get All Sticker packs
124+
125+
Args:
126+
limit (Optional[int], optional): Defaults to 20.
127+
offset (Optional[int], optional): Defaults to 0.
128+
129+
Returns:
130+
List[StickerPack]
131+
"""
132+
return await self.chat_service.stickers.get_all_sticker_packs(
133+
limit=limit, offset=offset
134+
)
135+
136+
async def sort_stickers(
137+
self: "swibots.ApiClient", pack_id: str, sorted_stickers: List[str]
138+
) -> StickerPack:
139+
"""Sort stickers in sticker pack
140+
141+
Args:
142+
pack_id (str): Sticker Pack ID
143+
sorted_stickers (List[str]): Sorted stickers.
144+
145+
Returns:
146+
StickerPack: Sticker pack with sorted stickers.
147+
"""
148+
return await self.chat_service.stickers.sort_stickers(
149+
pack_id=pack_id, sorted_stickers=sorted_stickers
150+
)

Diff for: ‎swibots/api/chat/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .inline_keyboard_button import InlineKeyboardButton
44
from .group_chat_history import GroupChatHistory
55
from .inline import *
6+
from .sticker import Sticker, StickerPack

Diff for: ‎swibots/api/chat/models/sticker.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from swibots.base import SwitchObject
2+
from swibots.utils.types import JSONDict
3+
from ....api.common.models import Media
4+
from typing import List, Optional, Union
5+
6+
import swibots
7+
8+
9+
class Sticker(SwitchObject):
10+
def __init__(
11+
self,
12+
app: "swibots.App" = None,
13+
id: Optional[str] = None,
14+
name: Optional[str] = None,
15+
description: Optional[str] = None,
16+
emoji: Optional[str] = None,
17+
sticker_pack_id: Optional[str] = None,
18+
created_by: Optional[int] = None,
19+
media_id: Optional[int] = None,
20+
sticker_info: Optional[Media] = None,
21+
):
22+
super().__init__(app)
23+
self.id = id
24+
self.name = name
25+
self.description = description
26+
self.emoji = emoji
27+
self.sticker_pack_id = sticker_pack_id
28+
self.sticker_info = sticker_info
29+
self.created_by = created_by
30+
self.media_id = media_id
31+
32+
def from_json(self, data: dict = None) -> "Sticker":
33+
if data is not None:
34+
self.id = data.get("id")
35+
self.name = data.get("name")
36+
self.description = data.get("description")
37+
self.emoji = data.get("linkedEmoji")
38+
self.sticker_info = Media.build_from_json(
39+
data.get("stickerMediaInfo", self.sticker_info)
40+
)
41+
self.sticker_pack_id = data.get("stickerPackId")
42+
self.created_by = int(data.get("createdBy", 0))
43+
self.media_id = data.get("stickerMediaId")
44+
return self
45+
46+
def to_json(self) -> dict:
47+
return {
48+
"id": self.id,
49+
"name": self.name,
50+
"description": self.description,
51+
"linkedEmoji": self.emoji,
52+
"stickerPackId": self.sticker_pack_id,
53+
"createdBy": self.created_by,
54+
"stickerMediaId": self.media_id,
55+
"stickerMediaInfo": self.sticker_info.to_json()
56+
if self.sticker_info
57+
else None,
58+
}
59+
60+
61+
class StickerPack(SwitchObject):
62+
def __init__(
63+
self,
64+
app: "swibots.App" = None,
65+
id: Optional[str] = None,
66+
name: Optional[str] = None,
67+
pack_type: Optional[str] = None,
68+
created_by: Optional[int] = None,
69+
thumb_media_id: Optional[int] = None,
70+
thumb_info: Optional[Media] = None,
71+
):
72+
super().__init__(app)
73+
self.id = id
74+
self.name = name
75+
self.created_by = created_by
76+
self.thumb_media_id = thumb_media_id
77+
self.thumb_info = thumb_info
78+
self.pack_type = pack_type
79+
self.stickers: List[Sticker] = None
80+
81+
def from_json(self, data: JSONDict | None) -> "StickerPack":
82+
if data is not None:
83+
self.id = data.get("id")
84+
self.name = data.get("name")
85+
self.created_by = int(data.get("createdBy", 0))
86+
self.pack_type = data.get("packType")
87+
self.thumb_media_id = data.get("thumbnailMediaId")
88+
self.thumb_info = Media.build_from_json(
89+
data.get("thumbnailMediaInfo"), self.app
90+
)
91+
self.stickers = data.get("sortedStickers")
92+
return self

Diff for: ‎swibots/api/common/models/media.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ def __init__(
1212
caption: Optional[str] = None,
1313
description: Optional[str] = None,
1414
thumbnail_url: Optional[str] = None,
15+
type_name: Optional[str] = None,
1516
source_id: Optional[bool] = None,
16-
media_type: Optional[bool] = None,
17-
mime_type: Optional[str] = None,
17+
media_type: Optional[int] = None,
18+
mime_type: Optional[str] = 0,
1819
file_name: Optional[bool] = None,
1920
file_size: Optional[bool] = None,
2021
url: Optional[bool] = None,
@@ -25,11 +26,16 @@ def __init__(
2526
self.description = description
2627
self.thumbnail_url = thumbnail_url
2728
self.source_id = source_id
29+
self.type_name = type_name
2830
self.media_type = media_type
2931
self.mime_type = mime_type
3032
self.file_name = file_name
3133
self.file_size = file_size
3234
self.url = url
35+
36+
@property
37+
def is_sticker(self) -> bool:
38+
return 200 <= (self.media_type) <= 202
3339

3440
def to_json(self) -> JSONDict:
3541
return {
@@ -42,6 +48,7 @@ def to_json(self) -> JSONDict:
4248
"mimeType": self.mime_type,
4349
"fileName": self.file_name,
4450
"fileSize": self.file_size,
51+
"typeName": self.type_name,
4552
"downloadUrl": self.url,
4653
}
4754

Diff for: ‎swibots/api/community/community_client.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
PermissionController,
1717
RoleMemberController,
1818
RestrictController,
19-
BanController
19+
BanController,
20+
QuestsController
2021
)
2122
import swibots
2223

@@ -36,13 +37,20 @@ def __init__(
3637
self._channels = None
3738
self._groups = None
3839
self._communities = None
40+
self._quests = None
3941
self._roles = None
4042
self._rolemember = None
4143
self._permission = None
4244
self._ban = None
4345
self._restrict = None
4446
self._ws: SwitchWSAsyncClient = None
4547
self._started = False
48+
49+
@property
50+
def quests(self) -> QuestsController:
51+
if self._quests is None:
52+
self._quests = QuestsController(self)
53+
return self._quests
4654

4755
@property
4856
def channels(self) -> ChannelController:
@@ -133,34 +141,24 @@ def _parse_event(self, raw_message: WsMessage) -> CommunityEvent:
133141
evt = None
134142
if type == EventType.COMMUNITY_CHANNEL_CREATE.value:
135143
evt = self.build_object(ChannelCreatedEvent, json_data)
136-
# return ChannelCreatedEvent.build_from_json(json_data)
137144
elif type == EventType.COMMUNITY_CHANNEL_UPDATE.value:
138145
evt = self.build_object(ChannelUpdatedEvent, json_data)
139-
# return ChannelUpdatedEvent.build_from_json(json_data)
140146
elif type == EventType.COMMUNITY_CHANNEL_DELETE.value:
141147
evt = self.build_object(ChannelDeletedEvent, json_data)
142-
# return ChannelDeletedEvent.build_from_json(json_data)
143148
elif type == EventType.COMMUNITY_UPDATE.value:
144149
evt = self.build_object(CommunityUpdatedEvent, json_data)
145-
# return CommunityUpdatedEvent.build_from_json(json_data)
146150
elif type == EventType.COMMUNITY_GROUP_CREATE.value:
147151
evt = self.build_object(GroupCreatedEvent, json_data)
148-
# return GroupCreatedEvent.build_from_json(json_data)
149152
elif type == EventType.COMMUNITY_GROUP_UPDATE.value:
150153
evt = self.build_object(GroupUpdatedEvent, json_data)
151-
# return GroupUpdatedEvent.build_from_json(json_data)
152154
elif type == EventType.COMMUNITY_GROUP_DELETE.value:
153155
evt = self.build_object(GroupDeletedEvent, json_data)
154-
# return GroupDeletedEvent.build_from_json(json_data)
155156
elif type == EventType.COMMUNITY_USER_BAN.value:
156157
evt = self.build_object(UserBannedEvent, json_data)
157-
# return UserBannedEvent.build_from_json(json_data)
158158
elif type == EventType.COMMUNITY_MEMBER_JOIN.value:
159159
evt = self.build_object(MemberJoinedEvent, json_data)
160-
# return MemberJoinedEvent.build_from_json(json_data)
161160
elif type == EventType.COMMUNITY_MEMBER_LEAVE.value:
162161
evt = self.build_object(MemberLeftEvent, json_data)
163-
# return MemberLeftEvent.build_from_json(json_data)
164162
else:
165163
evt = self.build_object(CommunityEvent, json_data)
166164

Diff for: ‎swibots/api/community/controllers/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
from .permissions_controller import *
66
from .rolemember_controller import *
77
from .restrict_controller import *
8-
from .ban_controller import *
8+
from .ban_controller import *
9+
from .quest_controller import *

Diff for: ‎swibots/api/community/controllers/community_controller.py

+6
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,10 @@ async def is_admin(self, community_id: str, user_id: int) -> bool:
114114
)
115115
return response.data.get("result", False)
116116

117+
async def is_community_member(self, community_id: str, user_id: int) -> bool:
118+
response = await self.client.get(
119+
f"{BASE_PATH}/validate/user/member?communityId={community_id}&user_id={user_id}"
120+
)
121+
return response.data.get("result", {}).get("member")
122+
117123
# endregion
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import logging
2+
from typing import TYPE_CHECKING, Optional, List
3+
from swibots.api.common.models import User
4+
from swibots.api.community.models import Quest, QuestCategory
5+
from swibots.responses import CommunityQuestResponse
6+
from urllib.parse import urlencode
7+
8+
if TYPE_CHECKING:
9+
from swibots.api.community import CommunityClient
10+
11+
log = logging.getLogger(__name__)
12+
13+
BASE_PATH = "/v1/community/quest"
14+
15+
16+
class QuestsController:
17+
def __init__(self, client: "CommunityClient"):
18+
self.client = client
19+
20+
async def create_quest(self, quest: Quest):
21+
response = await self.client.post(BASE_PATH, data=quest.to_json())
22+
return self.client.build_object(Quest, response.data)
23+
24+
async def update_quest(self, quest: Quest):
25+
response = await self.client.put(BASE_PATH, data=quest.to_json())
26+
return self.client.build_object(Quest, response.data)
27+
28+
async def get_quest(self, quest_id: int):
29+
return self.client.build_object(
30+
Quest, (await self.client.get(f"{BASE_PATH}/{quest_id}")).data
31+
)
32+
33+
async def submit_quest(self, quest_id: int, community_id: str, description: str):
34+
response = await self.client.post(
35+
f"{BASE_PATH}/submitQuest",
36+
data={
37+
"questId": quest_id,
38+
"communityId": community_id,
39+
"description": description,
40+
},
41+
)
42+
return response.data
43+
44+
async def approve_quest(
45+
self, community_id: str, quest_id: int, winner_id: int
46+
) -> Quest:
47+
response = await self.client.post(
48+
f"{BASE_PATH}/approveQuest",
49+
data={
50+
"communityId": community_id,
51+
"questId": quest_id,
52+
"winnerMemberId": winner_id,
53+
},
54+
)
55+
return self.client.build_object(Quest, response.data)
56+
57+
async def delete_quest(self, quest_id: str) -> bool:
58+
response = await self.client.delete(f"{BASE_PATH}/{quest_id}")
59+
return response.data
60+
61+
async def get_quest_participants(self, quest_id: int):
62+
response = await self.client.get(
63+
f"{BASE_PATH}/getParticipants", data={"questId": quest_id}
64+
)
65+
return self.client.build_object(User, response.data)
66+
67+
async def get_quests_by_community(self, community_id: str):
68+
response = await self.client.get(BASE_PATH, data={"communityId": community_id})
69+
return self.client.build_object(CommunityQuestResponse, response.data)
70+
71+
# region
72+
73+
async def get_quest_categories(self, community_id: str) -> List[QuestCategory]:
74+
return self.client.build_list(
75+
QuestCategory,
76+
(
77+
await self.client.get(
78+
f"{BASE_PATH}/categories", data={"communityId": community_id}
79+
)
80+
).data,
81+
)
82+
83+
async def get_quest_category(self, category_id: str) -> QuestCategory:
84+
return self.client.build_object(
85+
QuestCategory,
86+
(
87+
await self.client.get(
88+
f"{BASE_PATH}/category", data={"categoryId": category_id}
89+
)
90+
).data,
91+
)
92+
93+
async def create_quest_category(
94+
self, category_name: str, category_id: Optional[str] = None
95+
) -> QuestCategory:
96+
response = await self.client.post(
97+
f"{BASE_PATH}/category",
98+
data={
99+
"categoryName": category_name,
100+
"categoryId": category_id or category_name,
101+
},
102+
)
103+
return self.client.build_object(QuestCategory, response.data)
104+
105+
async def delete_quest_category(self, category_id: str) -> bool:
106+
response = await self.client.delete(
107+
f"{BASE_PATH}/category",
108+
data={
109+
"categoryId": category_id,
110+
},
111+
)
112+
return response.data
113+
114+
# endregion

Diff for: ‎swibots/api/community/methods/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .community_methods import CommunityMethods
99
from .restrict_user import RestrictUser
1010
from .deduct_xp import DeductXP
11+
from .quest_methods import QuestsMethods
1112

1213
class CommunityMethods(
1314
CommunityMethods,
@@ -19,6 +20,7 @@ class CommunityMethods(
1920
UnbanUser,
2021
ChannelMethods,
2122
RestrictUser,
22-
DeductXP
23+
DeductXP,
24+
QuestsMethods
2325
):
2426
pass

Diff for: ‎swibots/api/community/methods/quest_methods.py

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import swibots
2+
3+
from typing import Optional, List
4+
5+
from swibots.responses import CommunityQuestResponse
6+
from swibots.api.community.models import Quest, QuestCategory
7+
from swibots.api.common.models import User
8+
9+
class QuestsMethods:
10+
"""
11+
Methods for managing permissions in a community.
12+
13+
Args:
14+
client (swibots.ApiClient): The API client.
15+
"""
16+
17+
async def create_quest(self: "swibots.ApiClient", quest: Quest) -> Quest:
18+
"""Create a quest
19+
20+
Args:
21+
quest (Quest): the reference to quest
22+
23+
Returns:
24+
`Quest`: the resultant
25+
"""
26+
return await self.community_service.quests.create_quest(quest)
27+
28+
async def update_quest(self: "swibots.ApiClient", quest: Quest):
29+
"""Update a quest
30+
31+
Args:
32+
quest (Quest): the reference to quest
33+
34+
Returns:
35+
`Quest`: the updated quest
36+
"""
37+
return await self.community_service.quests.update_quest(quest=quest)
38+
39+
async def get_quest(self: "swibots.ApiClient", quest_id: int) -> Quest:
40+
"""Get quest by quest id
41+
42+
Args:
43+
quest_id (int): Quest ID
44+
45+
Returns:
46+
`Quest`: the resulting quest.
47+
"""
48+
return await self.community_service.quests.get_quest(quest_id)
49+
50+
async def submit_quest(
51+
self: "swibots.ApiClient", quest_id: int, community_id: str, description: str
52+
):
53+
"""Submit quest in the community
54+
55+
Args:
56+
quest_id (int): Quest ID.
57+
community_id (str): Community ID.
58+
description (str): Description
59+
60+
Returns:
61+
_type_: _description_
62+
"""
63+
return await self.community_service.quests.submit_quest(
64+
quest_id=quest_id, community_id=community_id, description=description
65+
)
66+
67+
async def approve_quest(
68+
self: "swibots.ApiClient", community_id: str, quest_id: int, winner_id: int
69+
) -> Quest:
70+
"""Select a winner for the quest.
71+
72+
Args:
73+
community_id (str): Community ID
74+
quest_id (int): Quest ID.
75+
winner_id (int): User id of winner.
76+
77+
Returns:
78+
Quest: the resulting quest
79+
"""
80+
return await self.community_service.quests.approve_quest(
81+
community_id=community_id, quest_id=quest_id, winner_id=winner_id
82+
)
83+
84+
async def delete_quest(self: "swibots.ApiClient", quest_id: str) -> bool:
85+
"""Delete a quest by quest id
86+
87+
Args:
88+
quest_id (str): Quest ID
89+
90+
Returns:
91+
bool: whether quest was deleted or not
92+
"""
93+
return await self.community_service.quests.delete_quest(quest_id=quest_id)
94+
95+
async def get_quest_participants(self: "swibots.ApiClient", quest_id: int) -> List[User]:
96+
"""Get Participants of the quest by quest id.
97+
98+
Args:
99+
quest_id (int): Quest ID
100+
101+
Returns:
102+
List[User]
103+
"""
104+
return await self.community_service.quests.get_quest_participants(
105+
quest_id=quest_id
106+
)
107+
108+
async def get_quests_by_community(self: "swibots.ApiClient", community_id: str):
109+
"""Get Quests by community id
110+
111+
Args:
112+
community_id (str): _description_
113+
114+
Returns:
115+
`CommunityQuestResponse`
116+
"""
117+
return await self.community_service.quests.get_quests_by_community(
118+
community_id=community_id
119+
)
120+
121+
async def get_quest_categories(
122+
self: "swibots.ApiClient", community_id: str
123+
) -> List[QuestCategory]:
124+
"""Get quests categories by community id.
125+
126+
Args:
127+
community_id (str): Community ID.
128+
129+
Returns:
130+
List[QuestCategory]: List of quest categories.
131+
"""
132+
return await self.community_service.quests.get_quest_categories(
133+
community_id=community_id
134+
)
135+
136+
async def get_quest_category(
137+
self: "swibots.ApiClient", category_id: str
138+
) -> QuestCategory:
139+
"""Get quest category by category id.
140+
141+
Args:
142+
category_id (str): Category ID.
143+
144+
Returns:
145+
QuestCategory
146+
"""
147+
return await self.community_service.quests.get_quest_category(
148+
category_id=category_id
149+
)
150+
151+
async def create_quest_category(
152+
self: "swibots.ApiClient", category_name: str, category_id: Optional[str] = None
153+
) -> QuestCategory:
154+
"""Create quest category
155+
156+
Args:
157+
category_name (str): Category Name
158+
category_id (Optional[str], optional): Category ID. Defaults to `category_name`
159+
160+
Returns:
161+
QuestCategory:
162+
"""
163+
return await self.community_service.quests.create_quest_category(
164+
category_name=category_name, category_id=category_id
165+
)
166+
167+
async def delete_quest_category(
168+
self: "swibots.ApiClient", category_id: str
169+
) -> bool:
170+
"""Delete Quest category by category ID.
171+
172+
Args:
173+
category_id (str): Category ID
174+
175+
Returns:
176+
bool: Whether category was deleted or not.
177+
"""
178+
return await self.community_service.quests.delete_quest_category(
179+
category_id=category_id
180+
)

Diff for: ‎swibots/api/community/methods/rolemember.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class RoleMemberMethods:
1313
client (swibots.ApiClient): The API client.
1414
"""
1515

16-
1716
async def get_members(self: "swibots.ApiClient", role_id: str) -> List[RoleMember]:
1817
"""
1918
Get the members of the role with the specified role ID.
@@ -27,13 +26,12 @@ async def get_members(self: "swibots.ApiClient", role_id: str) -> List[RoleMembe
2726

2827
return await self.community_service.rolemember.get_members(role_id)
2928

30-
3129
async def add_member_to_role(
32-
self: "swibots.ApiClient",
33-
community_id: str,
34-
member_id: int,
35-
role_ids: List[int],
36-
) -> bool:
30+
self: "swibots.ApiClient",
31+
community_id: str,
32+
member_id: int,
33+
role_ids: List[int],
34+
) -> bool:
3735
"""
3836
Add a member to the role with the specified role ID.
3937
@@ -46,15 +44,16 @@ async def add_member_to_role(
4644
True if the member was added successfully, False otherwise.
4745
"""
4846

49-
return await self.community_service.rolemember.add_member_to_role(community_id, member_id, role_ids)
50-
47+
return await self.community_service.rolemember.add_member_to_role(
48+
community_id, member_id, role_ids
49+
)
5150

5251
async def delete_role_member(
53-
self: "swibots.ApiClient",
54-
id: Optional[int] = None,
55-
member_id: Optional[int] = None,
56-
role_id: Optional[int] = None,
57-
) -> bool:
52+
self: "swibots.ApiClient",
53+
id: Optional[int] = None,
54+
member_id: Optional[int] = None,
55+
role_id: Optional[int] = None,
56+
) -> bool:
5857
"""
5958
Delete the role member with the specified ID.
6059
@@ -67,4 +66,6 @@ async def delete_role_member(
6766
True if the role member was deleted successfully, False otherwise.
6867
"""
6968

70-
return await self.community_service.rolemember.delete_role_member(id, member_id, role_id)
69+
return await self.community_service.rolemember.delete_role_member(
70+
id, member_id, role_id
71+
)

Diff for: ‎swibots/api/community/models/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
from .baninfo import BanInfo
88
from .community_member import CommunityMember
99
from .restricteduser import RestrictedUser
10+
from .quest import Quest, QuestCategory
1011

11-
__all__ = ["Channel", "Community", "Group", "Role", "RolePermission", "RoleMember", "BanInfo", "CommunityMember", "RestrictedUser"]
12+
__all__ = ["Channel", "Community", "Group", "Role", "RolePermission", "RoleMember", "BanInfo", "CommunityMember", "RestrictedUser", "Quest", "QuestCategory"]

Diff for: ‎swibots/api/community/models/quest.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from typing import Optional, List
2+
from swibots.utils.types import JSONDict
3+
from swibots.base.switch_object import SwitchObject
4+
import swibots
5+
6+
7+
class Quest(SwitchObject):
8+
def __init__(
9+
self,
10+
app: "swibots.App" = None,
11+
category: Optional[str] = None,
12+
category_id: Optional[str] = None,
13+
category_name: Optional[str] = None,
14+
claimed: Optional[bool] = None,
15+
community_id: Optional[str] = None,
16+
created_by: Optional[int] = None,
17+
description: Optional[str] = None,
18+
enabled: Optional[bool] = None,
19+
internal: Optional[bool] = None,
20+
name: Optional[str] = None,
21+
quest: Optional[str] = None,
22+
winner_ids: List[int] = None,
23+
xp: List[int] = None,
24+
):
25+
super().__init__(app)
26+
self.category = category
27+
self.category_id = category_id
28+
self.category_name = category_name
29+
self.claimed = claimed
30+
self.community_id = community_id
31+
self.created_by = created_by
32+
self.description = description
33+
self.enabled = enabled
34+
self.internal = internal
35+
self.name = name
36+
self.quest = quest
37+
self.winner_ids = winner_ids
38+
self.xp = xp
39+
40+
def to_json(self) -> JSONDict:
41+
return {
42+
"communityId": self.community_id,
43+
"enabled": self.enabled,
44+
"internal": self.internal,
45+
"quest": self.quest,
46+
"claimed": self.claimed,
47+
"winnerIds": self.winner_ids,
48+
"description": self.description,
49+
"category": self.category,
50+
"categoryName": self.category_name,
51+
"categoryId": self.category_id,
52+
"xp": self.xp,
53+
"createdBy": self.created_by,
54+
}
55+
56+
def from_json(self, data: JSONDict | None) -> "Quest":
57+
if data is not None:
58+
self.community_id = data.get("communityId")
59+
self.enabled = data.get("enabled")
60+
self.internal = data.get("internal")
61+
self.name = data.get("name")
62+
self.quest = data.get("quest")
63+
self.claimed = data.get("claimed")
64+
self.winner_ids = data.get("winnerIds")
65+
self.description = data.get("description")
66+
self.category = data.get("category")
67+
self.category_id = data.get("categoryId")
68+
self.category_name = data.get("categoryName")
69+
self.xp = data.get("xp")
70+
self.created_by = int(data.get("createdBy") or 0)
71+
return self
72+
73+
74+
class QuestCategory(SwitchObject):
75+
def __init__(
76+
self,
77+
app: "swibots.App" = None,
78+
category_id: Optional[str] = None,
79+
category_name: Optional[str] = None,
80+
community_id: Optional[str] = None,
81+
created_at: Optional[int] = None,
82+
updated_at: Optional[int] = None,
83+
):
84+
super().__init__(app)
85+
self.community_id = community_id
86+
self.category_name = category_name
87+
self.created_at = created_at
88+
self.updated_at = updated_at
89+
self.category_id = category_id
90+
91+
def from_json(self, data: JSONDict | None) -> "QuestCategory":
92+
if data is not None:
93+
self.category_id = data.get("categoryId")
94+
self.category_name = data.get("categoryName")
95+
self.community_id = data.get("communityId")
96+
self.created_at = data.get("createdAt")
97+
self.updated_at = data.get("updatedAt")
98+
return self

Diff for: ‎swibots/base/switch_object.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class SwitchObject(Generic[T]):
11-
def __init__(self, app:"swibots.App"=None, **kwargs):
11+
def __init__(self, app: "swibots.App" = None, **kwargs):
1212
self._app = app
1313
for key, value in kwargs.items():
1414
setattr(self, key, value)
@@ -18,13 +18,17 @@ def app(self) -> "swibots.App":
1818
return self._app
1919

2020
@classmethod
21-
def build_from_json(cls, data: Optional[JSONDict] = None, app: Optional["swibots.App"] = None) -> Optional[T]:
21+
def build_from_json(
22+
cls, data: Optional[JSONDict] = None, app: Optional["swibots.App"] = None
23+
) -> Optional[T]:
2224
if data is None:
2325
return None
2426
return cls(app).from_json(data)
2527

2628
@classmethod
27-
def build_from_json_list(cls, data: Optional[JSONDict], app: Optional["swibots.App"] = None) -> List[T]:
29+
def build_from_json_list(
30+
cls, data: Optional[JSONDict], app: Optional["swibots.App"] = None
31+
) -> List[T]:
2832
return [cls.build_from_json(item, app) for item in data]
2933

3034
def to_json_request(self) -> JSONDict:
@@ -39,5 +43,9 @@ def from_json(self, data: Optional[JSONDict]) -> T:
3943
return self
4044

4145
def __repr__(self) -> str:
42-
filter_dict = {x: y for x, y in self.to_json().items() if y}
43-
return f"{self.__class__.__name__} {json.dumps(filter_dict, indent=1)}"
46+
filter_dict = {
47+
x: y.to_json() if hasattr(y, "to_json") else y
48+
for x, y in self.to_json().items()
49+
if y and x != "_app"
50+
}
51+
return f"{self.__class__.__name__} {json.dumps(filter_dict, indent=1)}"

Diff for: ‎swibots/responses.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import List, Optional
2+
from .base.switch_object import SwitchObject
3+
from .api.community.models import QuestCategory, Quest
4+
from .utils.types import JSONDict
5+
6+
7+
class CommunityQuestResponse(SwitchObject):
8+
def __init__(
9+
self, categories: List[QuestCategory] = None, quests: List[Quest] = None
10+
):
11+
super().__init__()
12+
self.categories = categories
13+
self.quests = quests
14+
15+
def from_json(self, data: JSONDict | None) -> "CommunityQuestResponse":
16+
if data is not None:
17+
self.quests = [
18+
QuestCategory.build_from_json(quest) for quest in data.get("quests")
19+
]
20+
self.categories = [
21+
QuestCategory.build_from_json(category)
22+
for category in data.get("availableCategories")
23+
]
24+
return self

Diff for: ‎swibots/utils/types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ReadCallbackStream(object):
100100
http://code.activestate.com/recipes/578669-wrap-a-string-in-a-file-like-object-that-calls-a-u/
101101
"""
102102

103-
def __init__(self, file_like, callback):
103+
def __init__(self, file_like, callback: Callable = None):
104104
if isinstance(file_like, str):
105105
self.file_like = open(file_like, "rb")
106106
else:

0 commit comments

Comments
 (0)
Please sign in to comment.