Skip to content

Add msl methods #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 122 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
35d0493
add MSL methods
MattyTheHacker Aug 18, 2024
247a011
actually do it this time
MattyTheHacker Aug 18, 2024
e4ad85b
add anyio
MattyTheHacker Aug 18, 2024
5365811
improve referencing
MattyTheHacker Aug 18, 2024
2b174c3
implement is_user_member
MattyTheHacker Aug 18, 2024
d758e20
make urls class variable
MattyTheHacker Aug 18, 2024
8192c85
refactor
MattyTheHacker Aug 18, 2024
7831001
yeet the classes
MattyTheHacker Aug 18, 2024
da09b7b
Merge branch 'main' into add-msl-methods
MattyTheHacker Aug 18, 2024
fca2bcb
refactor with config changes
MattyTheHacker Aug 18, 2024
81117f3
more
MattyTheHacker Aug 18, 2024
0eae636
refactor makemember
MattyTheHacker Aug 18, 2024
7de0d21
update comment
MattyTheHacker Aug 18, 2024
10d49ae
add await and remove old link
MattyTheHacker Aug 18, 2024
8f3256e
not working yet
MattyTheHacker Aug 19, 2024
fb8d6a0
refactor
MattyTheHacker Aug 22, 2024
87b19ec
yeet
MattyTheHacker Aug 22, 2024
60a4cb2
Merge branch 'main' into add-msl-methods
MattyTheHacker Aug 22, 2024
2c1122c
Merge branch 'main' into add-msl-methods
MattyTheHacker Aug 24, 2024
67872d7
refactor
MattyTheHacker Aug 24, 2024
6cd4016
build out activities
MattyTheHacker Aug 24, 2024
cf57587
Merge branch 'main' into add-msl-methods
MattyTheHacker Aug 27, 2024
7c7de5d
update deps
MattyTheHacker Aug 27, 2024
d346560
start finances
MattyTheHacker Aug 29, 2024
293d471
Merge branch 'main' into add-msl-methods
MattyTheHacker Aug 30, 2024
c18994e
add some stuff
MattyTheHacker Aug 30, 2024
1597ea6
not implemented
MattyTheHacker Aug 31, 2024
4415d36
formatting
MattyTheHacker Sep 1, 2024
71571d3
Merge branch 'main' into add-msl-methods
MattyTheHacker Sep 5, 2024
edce5da
update lock
MattyTheHacker Sep 5, 2024
0c35949
Merge branch 'main' into add-msl-methods
MattyTheHacker Sep 6, 2024
6002c63
Merge branch 'main' into add-msl-methods
MattyTheHacker Sep 11, 2024
7d3d89f
update deps
MattyTheHacker Sep 11, 2024
6ed18ff
minor finance work
MattyTheHacker Sep 12, 2024
9459613
update deps
MattyTheHacker Sep 12, 2024
798d191
Merge branch 'main' into add-msl-methods
MattyTheHacker Sep 17, 2024
2d798c4
update deps
MattyTheHacker Sep 17, 2024
4f5760e
Merge branch 'main' into add-msl-methods
MattyTheHacker Sep 25, 2024
c7cfd8d
Update dependencies
MattyTheHacker Sep 25, 2024
9cc67b9
add todo
MattyTheHacker Oct 2, 2024
aeaaf8a
Add caching to member list fetching
MattyTheHacker Oct 4, 2024
d39008d
Merge branch 'main' into add-msl-methods
MattyTheHacker Oct 7, 2024
7bb1861
update dependencies
MattyTheHacker Oct 7, 2024
900d911
not working yet
MattyTheHacker Oct 14, 2024
d949e17
Merge branch 'main' into add-msl-methods
MattyTheHacker Oct 14, 2024
c49cb42
update deps
MattyTheHacker Oct 14, 2024
af27c5c
Merge branch 'main' into add-msl-methods
MattyTheHacker Oct 16, 2024
9b656e3
update deps
MattyTheHacker Oct 16, 2024
3c8d312
ruff can suck my nuts
MattyTheHacker Oct 16, 2024
6e73fe2
Merge branch 'main' into add-msl-methods
MattyTheHacker Oct 19, 2024
921041e
update deps
MattyTheHacker Oct 19, 2024
97ca9a4
bump lock fle2
MattyTheHacker Dec 28, 2024
6f6fffa
fix ruff errors
MattyTheHacker Dec 28, 2024
ccccb10
Merge branch 'main' into add-msl-methods
MattyTheHacker Dec 28, 2024
9f89a88
Fix bits
MattyTheHacker Dec 28, 2024
8ffb008
update lock file
MattyTheHacker Jan 1, 2025
8545349
Merge branch 'main' into add-msl-methods
MattyTheHacker Jan 1, 2025
46b6b9f
fix
MattyTheHacker Jan 1, 2025
a760051
fix ruff errors
MattyTheHacker Jan 1, 2025
f0d0aac
Merge remote-tracking branch 'origin/main' into add-msl-methods
MattyTheHacker Mar 1, 2025
7dceeed
Change org id name to match main
MattyTheHacker Mar 1, 2025
0118730
Minor fixes
MattyTheHacker Mar 1, 2025
e251927
Merge branch 'main' into add-msl-methods
MattyTheHacker Mar 17, 2025
f2d0587
fix mypy
MattyTheHacker Mar 17, 2025
0e31b43
Add anyio back in
MattyTheHacker Mar 17, 2025
5d34aa5
run ruff format
MattyTheHacker Mar 17, 2025
6a56715
fix mypy
MattyTheHacker Mar 17, 2025
1b006da
Merge branch 'main' into add-msl-methods
MattyTheHacker Mar 18, 2025
75858c5
Merge branch 'main' into add-msl-methods
MattyTheHacker Apr 9, 2025
ea001c1
fix some stuff
MattyTheHacker Apr 9, 2025
88cd906
Fix bad handling
MattyTheHacker Apr 9, 2025
2fd3a1b
Fix toml error
MattyTheHacker Apr 9, 2025
24daeea
fix dependencies
MattyTheHacker Apr 9, 2025
8b8fb6f
fix lint
MattyTheHacker Apr 9, 2025
e3eae1d
fix syntax
MattyTheHacker Apr 9, 2025
e859d49
Merge branch 'main' into add-msl-methods
MattyTheHacker Apr 13, 2025
7c1ee0f
Add event stuff
MattyTheHacker May 2, 2025
e1bcd11
Merge branch 'main' into add-msl-methods
MattyTheHacker May 2, 2025
3e29e09
fix ruff errors
MattyTheHacker May 2, 2025
67d5e0e
fix it even more
MattyTheHacker May 2, 2025
5634c47
seriously getting very annoyed now
MattyTheHacker May 2, 2025
7403c0b
going to kms
MattyTheHacker May 2, 2025
fa92f5b
Merge branch 'main' into add-msl-methods
MattyTheHacker May 4, 2025
4ccfd95
Merge branch 'main' into add-msl-methods
MattyTheHacker May 5, 2025
d07d33c
Merge branch 'main' into add-msl-methods
MattyTheHacker May 9, 2025
1aa6570
Merge branch 'main' into add-msl-methods
MattyTheHacker May 12, 2025
cf958c1
Merge branch 'main' into add-msl-methods
MattyTheHacker May 14, 2025
ecb7c06
it works but it's awful
MattyTheHacker May 16, 2025
dd1bbd4
fix some stuff
MattyTheHacker May 16, 2025
5b3437c
begin work on activitiy fetching
MattyTheHacker May 16, 2025
2a09a25
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] May 16, 2025
afef663
Merge branch 'main' into add-msl-methods
MattyTheHacker May 20, 2025
ecc626b
fix error
MattyTheHacker May 20, 2025
c833f23
ruff reformat
MattyTheHacker May 20, 2025
12215b6
Merge branch 'main' into add-msl-methods
MattyTheHacker May 25, 2025
2083206
Merge branch 'main' into add-msl-methods
MattyTheHacker Jun 2, 2025
050b12a
Merge main into add-msl-methods
cssbhamdev Jun 12, 2025
dd30ea7
Merge main into add-msl-methods
cssbhamdev Jun 13, 2025
d22c9c5
Merge main into add-msl-methods
cssbhamdev Jun 13, 2025
e0944c2
Merge main into add-msl-methods
cssbhamdev Jun 14, 2025
1a6ff75
Allow committee-elect to update actions (and appear in auto-complete)…
Thatsmusic99 Jun 15, 2025
7239805
Merge main into add-msl-methods
cssbhamdev Jun 15, 2025
ccf6309
Merge main into add-msl-methods
cssbhamdev Jun 15, 2025
8e7dba8
Merge main into add-msl-methods
cssbhamdev Jun 15, 2025
19e4c3e
Merge main into add-msl-methods
cssbhamdev Jun 15, 2025
519c031
Merge main into add-msl-methods
cssbhamdev Jun 16, 2025
a9d83df
Merge main into add-msl-methods
cssbhamdev Jun 16, 2025
0cfeaeb
Merge main into add-msl-methods
cssbhamdev Jun 17, 2025
a815a0c
Merge main into add-msl-methods
cssbhamdev Jun 19, 2025
eb64bb8
Merge main into add-msl-methods
cssbhamdev Jun 19, 2025
6c096fe
Merge main into add-msl-methods
cssbhamdev Jun 22, 2025
9a4e079
Merge main into add-msl-methods
cssbhamdev Jun 24, 2025
9e25dcb
Merge main into add-msl-methods
cssbhamdev Jun 24, 2025
cd347d7
Merge main into add-msl-methods
cssbhamdev Jun 24, 2025
3eeabd9
Merge main into add-msl-methods
cssbhamdev Jun 25, 2025
4dcf6d4
Merge main into add-msl-methods
cssbhamdev Jun 30, 2025
331c82f
Merge main into add-msl-methods
automatic-pr-updater[bot] Jun 30, 2025
b58728e
Merge main into add-msl-methods
automatic-pr-updater[bot] Jul 2, 2025
2c3fa57
Merge main into add-msl-methods
automatic-pr-updater[bot] Jul 2, 2025
446095d
Merge main into add-msl-methods
automatic-pr-updater[bot] Jul 3, 2025
b5b63c7
Merge main into add-msl-methods
automatic-pr-updater[bot] Jul 3, 2025
9af9d12
Merge main into add-msl-methods
automatic-pr-updater[bot] Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cogs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .remind_me import ClearRemindersBacklogTaskCog, RemindMeCommandCog
from .send_get_roles_reminders import SendGetRolesRemindersTaskCog
from .send_introduction_reminders import SendIntroductionRemindersTaskCog
from .society_events import SocietyEventsSlashCommandsCog
from .source import SourceCommandCog
from .startup import StartupCog
from .stats import StatsCommandsCog
Expand Down Expand Up @@ -77,6 +78,7 @@
"RemindMeCommandCog",
"SendGetRolesRemindersTaskCog",
"SendIntroductionRemindersTaskCog",
"SocietyEventsSlashCommandsCog",
"SourceCommandCog",
"StartupCog",
"StatsCommandsCog",
Expand Down Expand Up @@ -119,6 +121,7 @@ def setup(bot: "TeXBot") -> None:
SendGetRolesRemindersTaskCog,
SendIntroductionRemindersTaskCog,
SourceCommandCog,
SocietyEventsSlashCommandsCog,
StartupCog,
StatsCommandsCog,
StrikeCommandCog,
Expand Down
164 changes: 26 additions & 138 deletions cogs/make_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import re
from typing import TYPE_CHECKING

import aiohttp
import bs4
import discord
from bs4 import BeautifulSoup
from django.core.exceptions import ValidationError

from config import settings
Expand All @@ -22,6 +19,8 @@

from utils import TeXBotApplicationContext

from utils.msl import get_membership_count, is_student_id_member

__all__: "Sequence[str]" = ("MakeMemberCommandCog", "MemberCountCommandCog")

logger: "Final[Logger]" = logging.getLogger("TeX-Bot")
Expand Down Expand Up @@ -157,92 +156,32 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st
)
return

guild_member_ids: set[str] = set()

http_session: aiohttp.ClientSession = aiohttp.ClientSession(
headers=REQUEST_HEADERS,
cookies=REQUEST_COOKIES,
)
async with http_session, http_session.get(GROUPED_MEMBERS_URL) as http_response:
response_html: str = await http_response.text()

MEMBER_HTML_TABLE_IDS: Final[frozenset[str]] = frozenset(
{
"ctl00_Main_rptGroups_ctl05_gvMemberships",
"ctl00_Main_rptGroups_ctl03_gvMemberships",
"ctl00_ctl00_Main_AdminPageContent_rptGroups_ctl03_gvMemberships",
"ctl00_ctl00_Main_AdminPageContent_rptGroups_ctl05_gvMemberships",
},
if not await is_student_id_member(student_id=group_member_id):
await self.command_send_error(
ctx=ctx,
message=(
f"You must be a member of {self.bot.group_full_name} "
"to use this command.\n"
f"The provided {_GROUP_MEMBER_ID_ARGUMENT_NAME} must match "
f"the {self.bot.group_member_id_type} ID "
f"that you purchased your {self.bot.group_short_name} membership with."
),
)
table_id: str
for table_id in MEMBER_HTML_TABLE_IDS:
parsed_html: bs4.Tag | bs4.NavigableString | None = BeautifulSoup(
response_html,
"html.parser",
).find(
"table",
{"id": table_id},
)

if parsed_html is None or isinstance(parsed_html, bs4.NavigableString):
continue

guild_member_ids.update(
row.contents[2].text
for row in parsed_html.find_all(
"tr",
{"class": ["msl_row", "msl_altrow"]},
)
try:
await GroupMadeMember.objects.acreate(group_member_id=group_member_id) # type: ignore[misc]
except ValidationError as create_group_made_member_error:
error_is_already_exists: bool = (
"hashed_group_member_id" in create_group_made_member_error.message_dict
and any(
"already exists" in error
for error in create_group_made_member_error.message_dict[
"hashed_group_member_id"
]
)

guild_member_ids.discard("")
guild_member_ids.discard("\n")
guild_member_ids.discard(" ")

if not guild_member_ids:
await self.command_send_error(
ctx,
error_code="E1041",
logging_message=OSError(
"The guild member IDs could not be retrieved from "
"the MEMBERS_LIST_URL.",
),
)
return

if group_member_id not in guild_member_ids:
await self.command_send_error(
ctx,
message=(
f"You must be a member of {self.bot.group_full_name} "
"to use this command.\n"
f"The provided {_GROUP_MEMBER_ID_ARGUMENT_NAME} must match "
f"the {self.bot.group_member_id_type} ID "
f"that you purchased your {self.bot.group_short_name} membership with."
),
)
return

# NOTE: The "Member" role must be added to the user **before** the "Guest" role to ensure that the welcome message does not include the suggestion to purchase membership
await interaction_member.add_roles(
member_role,
reason=f'{ctx.user} used TeX Bot slash-command: "/makemember"',
)

try:
await GroupMadeMember.objects.acreate(group_member_id=group_member_id) # type: ignore[misc]
except ValidationError as create_group_made_member_error:
error_is_already_exists: bool = (
"hashed_group_member_id" in create_group_made_member_error.message_dict
and any(
"already exists" in error
for error in create_group_made_member_error.message_dict[
"hashed_group_member_id"
]
)
)
if not error_is_already_exists:
raise
if not error_is_already_exists:
raise create_group_made_member_error from create_group_made_member_error

await ctx.followup.send(content="Successfully made you a member!", ephemeral=True)

Expand Down Expand Up @@ -285,58 +224,7 @@ async def member_count(self, ctx: "TeXBotApplicationContext") -> None: # type:
await ctx.defer(ephemeral=False)

async with ctx.typing():
http_session: aiohttp.ClientSession = aiohttp.ClientSession(
headers=REQUEST_HEADERS,
cookies=REQUEST_COOKIES,
)
async with http_session, http_session.get(BASE_MEMBERS_URL) as http_response:
response_html: str = await http_response.text()

member_list_div: bs4.Tag | bs4.NavigableString | None = BeautifulSoup(
response_html,
"html.parser",
).find(
"div",
{"class": "memberlistcol"},
)

if member_list_div is None or isinstance(member_list_div, bs4.NavigableString):
await self.command_send_error(
ctx=ctx,
error_code="E1041",
logging_message=OSError(
"The member count could not be retrieved from the MEMBERS_LIST_URL.",
),
)
return

if "showing 100 of" in member_list_div.text.lower():
member_count: str = member_list_div.text.split(" ")[3]
await ctx.followup.send(
content=f"{self.bot.group_full_name} has {member_count} members! :tada:",
)
return

member_table: bs4.Tag | bs4.NavigableString | None = BeautifulSoup(
response_html,
"html.parser",
).find(
"table",
{"id": "ctl00_ctl00_Main_AdminPageContent_gvMembers"},
)

if member_table is None or isinstance(member_table, bs4.NavigableString):
await self.command_send_error(
ctx=ctx,
error_code="E1041",
logging_message=OSError(
"The member count could not be retrieved from the MEMBERS_LIST_URL."
),
)
return

await ctx.followup.send(
content=f"{self.bot.group_full_name} has {
len(member_table.find_all('tr', {'class': ['msl_row', 'msl_altrow']}))
} members! :tada:"
content=f"{settings['GROUP_NAME']} has {await get_membership_count()} "
"members! :tada:",
)
49 changes: 49 additions & 0 deletions cogs/society_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Module for handling society events in a Discord bot."""

import logging
from datetime import datetime
from typing import TYPE_CHECKING

import discord

from utils import TeXBotBaseCog
from utils.msl import fetch_guild_activities

if TYPE_CHECKING:
from collections.abc import Sequence
from logging import Logger
from typing import Final

from utils import TeXBotApplicationContext


__all__: "Sequence[str]" = ("SocietyEventsSlashCommandsCog",)


logger: "Final[Logger]" = logging.getLogger("TeX-Bot")


class SocietyEventsSlashCommandsCog(TeXBotBaseCog):
"""Cog Class for handling society event commands."""

society_events: discord.SlashCommandGroup = discord.SlashCommandGroup(
name="society-events",
description="Commands for managing society events.",
)

@society_events.command(
name="list-all",
desciption="List all society events.",
)
async def list_all_events(self, ctx: "TeXBotApplicationContext") -> None:
"""List all society events."""
await ctx.defer(ephemeral=True)
activities: dict[str, str] = await fetch_guild_activities(
from_date=datetime.strptime("2023-01-01", "%Y-%m-%d"), # noqa: DTZ007
to_date=datetime.strptime("2026-01-01", "%Y-%m-%d"), # noqa: DTZ007
)

await ctx.followup.send(
content=str(activities),
ephemeral=True,
)
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ test = ["pytest-cov>=6.1", "pytest>=8.3"]
type-check = ["django-stubs[compatible-mypy]>=5.1", "mypy>=1.13", "types-beautifulsoup4>=4.12"]

[project] # TODO: Remove [project] table once https://github.com/astral-sh/uv/issues/8582 is completed
dependencies = ["aiohttp>=3.11.14", "anyio>=4.9.0"]
name = "TeX-Bot-Py-V2"
requires-python = ">=3.12,<3.13" # TODO: Allow Python 3.13 once py-cord makes a new release with support for it
requires-python = ">=3.12,<3.13" # TODO: Allow Python 3.13 once py-cord makes a new release with support for it
version = "0.1.0"


Expand Down
35 changes: 35 additions & 0 deletions utils/msl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""MSL utility classes & functions provided for use across the whole of the project."""

from typing import TYPE_CHECKING

from .activities import fetch_guild_activities
from .events import create_event, get_all_guild_events
from .finances import (
fetch_financial_transactions,
fetch_transaction_from_id,
get_account_balance,
)
from .memberships import get_full_membership_list, get_membership_count, is_student_id_member
from .reports import (
get_product_customisations,
get_product_sales,
update_current_year_sales_report,
)

if TYPE_CHECKING:
from collections.abc import Sequence

__all__: "Sequence[str]" = (
"create_event",
"fetch_financial_transactions",
"fetch_guild_activities",
"fetch_transaction_from_id",
"get_account_balance",
"get_all_guild_events",
"get_full_membership_list",
"get_membership_count",
"get_product_customisations",
"get_product_sales",
"is_student_id_member",
"update_current_year_sales_report",
)
Loading