44import re
55from typing import TYPE_CHECKING
66
7- import aiohttp
8- import bs4
97import discord
10- from bs4 import BeautifulSoup
118from django .core .exceptions import ValidationError
129
1310from config import settings
1411from db .core .models import GroupMadeMember
1512from 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
1715
1816if TYPE_CHECKING :
19- from collections .abc import Mapping , Sequence
17+ from collections .abc import Sequence
2018 from logging import Logger
2119 from typing import Final
2220
2321 from utils import TeXBotApplicationContext
2422
23+
2524__all__ : "Sequence[str]" = ("MakeMemberCommandCog" , "MemberCountCommandCog" )
2625
26+
2727logger : "Final[Logger]" = logging .getLogger ("TeX-Bot" )
2828
29+
2930_GROUP_MEMBER_ID_ARGUMENT_DESCRIPTIVE_NAME : "Final[str]" = f"""{
3031 "Student"
3132 if (
4950 _GROUP_MEMBER_ID_ARGUMENT_DESCRIPTIVE_NAME .lower ().replace (" " , "" )
5051)
5152
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-
6753
6854class MakeMemberCommandCog (TeXBotBaseCog ):
6955 """Cog class that defines the "/make-member" command and its call-back method."""
@@ -101,10 +87,12 @@ class MakeMemberCommandCog(TeXBotBaseCog):
10187 required = True ,
10288 max_length = 7 ,
10389 min_length = 7 ,
104- parameter_name = "group_member_id " ,
90+ parameter_name = "raw_group_member_id " ,
10591 )
10692 @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 :
10896 """
10997 Definition & callback response of the "make_member" command.
11098
@@ -116,6 +104,20 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
116104 member_role : discord .Role = await self .bot .member_role
117105 interaction_member : discord .Member = await ctx .bot .get_main_guild_member (ctx .user )
118106
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+
119121 await ctx .defer (ephemeral = True )
120122 async with ctx .typing ():
121123 if member_role in interaction_member .roles :
@@ -128,16 +130,6 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
128130 )
129131 return
130132
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-
141133 if await GroupMadeMember .objects .filter (
142134 hashed_group_member_id = GroupMadeMember .hash_group_member_id (
143135 group_member_id , self .bot .group_member_id_type
@@ -154,56 +146,7 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
154146 )
155147 return
156148
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 ):
207150 await self .command_send_error (
208151 ctx ,
209152 message = (
@@ -222,7 +165,7 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
222165 )
223166
224167 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]
226169 except ValidationError as create_group_made_member_error :
227170 error_is_already_exists : bool = (
228171 "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:
276219 await ctx .defer (ephemeral = False )
277220
278221 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-
324222 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+ )
328227 )
0 commit comments