4
4
import re
5
5
from typing import TYPE_CHECKING
6
6
7
- import aiohttp
8
- import bs4
9
7
import discord
10
- from bs4 import BeautifulSoup
11
8
from django .core .exceptions import ValidationError
12
9
13
10
from config import settings
14
11
from db .core .models import GroupMadeMember
15
12
from exceptions import ApplicantRoleDoesNotExistError , GuestRoleDoesNotExistError
16
- from utils import GLOBAL_SSL_CONTEXT , CommandChecks , TeXBotBaseCog
13
+ from utils import CommandChecks , TeXBotBaseCog
14
+ from utils .msl import fetch_community_group_members_count , is_id_a_community_group_member
17
15
18
16
if TYPE_CHECKING :
19
- from collections .abc import Mapping , Sequence
17
+ from collections .abc import Sequence
20
18
from logging import Logger
21
19
from typing import Final
22
20
23
21
from utils import TeXBotApplicationContext
24
22
23
+
25
24
__all__ : "Sequence[str]" = ("MakeMemberCommandCog" , "MemberCountCommandCog" )
26
25
26
+
27
27
logger : "Final[Logger]" = logging .getLogger ("TeX-Bot" )
28
28
29
+
29
30
_GROUP_MEMBER_ID_ARGUMENT_DESCRIPTIVE_NAME : "Final[str]" = f"""{
30
31
"Student"
31
32
if (
49
50
_GROUP_MEMBER_ID_ARGUMENT_DESCRIPTIVE_NAME .lower ().replace (" " , "" )
50
51
)
51
52
52
- REQUEST_HEADERS : "Final[Mapping[str, str]]" = {
53
- "Cache-Control" : "no-cache" ,
54
- "Pragma" : "no-cache" ,
55
- "Expires" : "0" ,
56
- }
57
-
58
- REQUEST_COOKIES : "Final[Mapping[str, str]]" = {
59
- ".ASPXAUTH" : settings ["SU_PLATFORM_ACCESS_COOKIE" ]
60
- }
61
-
62
- BASE_MEMBERS_URL : "Final[str]" = (
63
- f"https://guildofstudents.com/organisation/memberlist/{ settings ['ORGANISATION_ID' ]} "
64
- )
65
- GROUPED_MEMBERS_URL : "Final[str]" = f"{ BASE_MEMBERS_URL } /?sort=groups"
66
-
67
53
68
54
class MakeMemberCommandCog (TeXBotBaseCog ):
69
55
"""Cog class that defines the "/make-member" command and its call-back method."""
@@ -101,10 +87,12 @@ class MakeMemberCommandCog(TeXBotBaseCog):
101
87
required = True ,
102
88
max_length = 7 ,
103
89
min_length = 7 ,
104
- parameter_name = "group_member_id " ,
90
+ parameter_name = "raw_group_member_id " ,
105
91
)
106
92
@CommandChecks .check_interaction_user_in_main_guild
107
- async def make_member (self , ctx : "TeXBotApplicationContext" , group_member_id : str ) -> None : # type: ignore[misc]
93
+ async def make_member ( # type: ignore[misc]
94
+ self , ctx : "TeXBotApplicationContext" , raw_group_member_id : str
95
+ ) -> None :
108
96
"""
109
97
Definition & callback response of the "make_member" command.
110
98
@@ -116,6 +104,20 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
116
104
member_role : discord .Role = await self .bot .member_role
117
105
interaction_member : discord .Member = await ctx .bot .get_main_guild_member (ctx .user )
118
106
107
+ INVALID_GROUP_MEMBER_ID_MESSAGE : Final [str ] = (
108
+ f"{ raw_group_member_id !r} is not a valid { self .bot .group_member_id_type } ID."
109
+ )
110
+
111
+ if not re .fullmatch (r"\A\d{7}\Z" , raw_group_member_id ):
112
+ await self .command_send_error (ctx , message = (INVALID_GROUP_MEMBER_ID_MESSAGE ))
113
+ return
114
+
115
+ try :
116
+ group_member_id : int = int (raw_group_member_id )
117
+ except ValueError :
118
+ await self .command_send_error (ctx , message = INVALID_GROUP_MEMBER_ID_MESSAGE )
119
+ return
120
+
119
121
await ctx .defer (ephemeral = True )
120
122
async with ctx .typing ():
121
123
if member_role in interaction_member .roles :
@@ -128,16 +130,6 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
128
130
)
129
131
return
130
132
131
- if not re .fullmatch (r"\A\d{7}\Z" , group_member_id ):
132
- await self .command_send_error (
133
- ctx ,
134
- message = (
135
- f"{ group_member_id !r} is not a valid "
136
- f"{ self .bot .group_member_id_type } ID."
137
- ),
138
- )
139
- return
140
-
141
133
if await GroupMadeMember .objects .filter (
142
134
hashed_group_member_id = GroupMadeMember .hash_group_member_id (
143
135
group_member_id , self .bot .group_member_id_type
@@ -154,56 +146,7 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
154
146
)
155
147
return
156
148
157
- guild_member_ids : set [str ] = set ()
158
-
159
- async with (
160
- aiohttp .ClientSession (
161
- headers = REQUEST_HEADERS , cookies = REQUEST_COOKIES
162
- ) as http_session ,
163
- http_session .get (
164
- url = GROUPED_MEMBERS_URL , ssl = GLOBAL_SSL_CONTEXT
165
- ) as http_response ,
166
- ):
167
- response_html : str = await http_response .text ()
168
-
169
- MEMBER_HTML_TABLE_IDS : Final [frozenset [str ]] = frozenset (
170
- {
171
- "ctl00_Main_rptGroups_ctl05_gvMemberships" ,
172
- "ctl00_Main_rptGroups_ctl03_gvMemberships" ,
173
- "ctl00_ctl00_Main_AdminPageContent_rptGroups_ctl03_gvMemberships" ,
174
- "ctl00_ctl00_Main_AdminPageContent_rptGroups_ctl05_gvMemberships" ,
175
- }
176
- )
177
- table_id : str
178
- for table_id in MEMBER_HTML_TABLE_IDS :
179
- parsed_html : bs4 .Tag | bs4 .NavigableString | None = BeautifulSoup (
180
- response_html , "html.parser"
181
- ).find ("table" , {"id" : table_id })
182
-
183
- if parsed_html is None or isinstance (parsed_html , bs4 .NavigableString ):
184
- continue
185
-
186
- guild_member_ids .update (
187
- row .contents [2 ].text
188
- for row in parsed_html .find_all ("tr" , {"class" : ["msl_row" , "msl_altrow" ]})
189
- )
190
-
191
- guild_member_ids .discard ("" )
192
- guild_member_ids .discard ("\n " )
193
- guild_member_ids .discard (" " )
194
-
195
- if not guild_member_ids :
196
- await self .command_send_error (
197
- ctx ,
198
- error_code = "E1041" ,
199
- logging_message = OSError (
200
- "The guild member IDs could not be retrieved from "
201
- "the MEMBERS_LIST_URL."
202
- ),
203
- )
204
- return
205
-
206
- if group_member_id not in guild_member_ids :
149
+ if not await is_id_a_community_group_member (member_id = group_member_id ):
207
150
await self .command_send_error (
208
151
ctx ,
209
152
message = (
@@ -222,7 +165,7 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
222
165
)
223
166
224
167
try :
225
- await GroupMadeMember .objects .acreate (group_member_id = group_member_id ) # type: ignore[misc]
168
+ await GroupMadeMember .objects .acreate (group_member_id = raw_group_member_id ) # type: ignore[misc]
226
169
except ValidationError as create_group_made_member_error :
227
170
error_is_already_exists : bool = (
228
171
"hashed_group_member_id" in create_group_made_member_error .message_dict
@@ -276,53 +219,9 @@ async def member_count(self, ctx: "TeXBotApplicationContext") -> None: # type:
276
219
await ctx .defer (ephemeral = False )
277
220
278
221
async with ctx .typing ():
279
- async with (
280
- aiohttp .ClientSession (
281
- headers = REQUEST_HEADERS , cookies = REQUEST_COOKIES
282
- ) as http_session ,
283
- http_session .get (
284
- url = BASE_MEMBERS_URL , ssl = GLOBAL_SSL_CONTEXT
285
- ) as http_response ,
286
- ):
287
- response_html : str = await http_response .text ()
288
-
289
- member_list_div : bs4 .Tag | bs4 .NavigableString | None = BeautifulSoup (
290
- response_html , "html.parser"
291
- ).find ("div" , {"class" : "memberlistcol" })
292
-
293
- if member_list_div is None or isinstance (member_list_div , bs4 .NavigableString ):
294
- await self .command_send_error (
295
- ctx ,
296
- error_code = "E1041" ,
297
- logging_message = OSError (
298
- "The member count could not be retrieved from the MEMBERS_LIST_URL."
299
- ),
300
- )
301
- return
302
-
303
- if "showing 100 of" in member_list_div .text .lower ():
304
- member_count : str = member_list_div .text .split (" " )[3 ]
305
- await ctx .followup .send (
306
- content = f"{ self .bot .group_full_name } has { member_count } members! :tada:"
307
- )
308
- return
309
-
310
- member_table : bs4 .Tag | bs4 .NavigableString | None = BeautifulSoup (
311
- response_html , "html.parser"
312
- ).find ("table" , {"id" : "ctl00_ctl00_Main_AdminPageContent_gvMembers" })
313
-
314
- if member_table is None or isinstance (member_table , bs4 .NavigableString ):
315
- await self .command_send_error (
316
- ctx ,
317
- error_code = "E1041" ,
318
- logging_message = OSError (
319
- "The member count could not be retrieved from the MEMBERS_LIST_URL."
320
- ),
321
- )
322
- return
323
-
324
222
await ctx .followup .send (
325
- content = f"{ self .bot .group_full_name } has {
326
- len (member_table .find_all ('tr' , {'class' : ['msl_row' , 'msl_altrow' ]}))
327
- } members! :tada:"
223
+ content = (
224
+ f"{ self .bot .group_full_name } has "
225
+ f"{ await fetch_community_group_members_count ()} members! :tada:"
226
+ )
328
227
)
0 commit comments