Skip to content

Commit c3c22e6

Browse files
committed
add interested functionality
1 parent 98476ea commit c3c22e6

File tree

13 files changed

+348
-55
lines changed

13 files changed

+348
-55
lines changed

app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from utils.slack import app
22
from utils.env import env
3+
from threading import Thread
4+
from utils.rsvp_checker import rsvp_checker
35

46
if __name__ == "__main__":
7+
rsvp_thread = Thread(target=rsvp_checker, daemon=True)
8+
rsvp_thread.start()
9+
510
app.start(env.port)

events/buttons/approve_event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ def handle_approve_event_btn(ack: Callable, body: dict[str, Any], client: WebCli
4646
text=f"<@{user_id}> approved {event['fields']['Title']} for <@{event['fields']['Leader Slack ID']}>.",
4747
)
4848

49-
client.views_publish(user_id=user_id, view=get_home(user_id))
49+
client.views_publish(user_id=user_id, view=get_home(user_id, client))

events/buttons/rsvp.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Any, Callable
2+
from slack_sdk import WebClient
3+
from utils.env import env
4+
from views.app_home import get_home
5+
6+
7+
def handle_rsvp_btn(ack: Callable, body: dict[str, Any], client: WebClient):
8+
ack()
9+
user_id = body["user"]["id"]
10+
event_id = body["actions"][0]["value"]
11+
user = env.airtable.rsvp_to_event(event_id, user_id)
12+
event = env.airtable.get_event(event_id)
13+
if event["id"] not in user["fields"].get("Interesting Events", []):
14+
client.chat_postMessage(
15+
channel=user_id,
16+
text=f"You're no longer interested in {event['fields']['Title']}! :(",
17+
)
18+
else:
19+
client.chat_postMessage(
20+
channel=user_id,
21+
text=f"You're interested in {event['fields']['Title']}! We'll let you know when it starts.",
22+
)
23+
24+
client.views_publish(user_id=user_id, view=get_home(user_id, client))

events/views/create_event.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from utils.env import env
66
from utils.utils import rich_text_to_md, md_to_mrkdwn
7+
from views.app_home import get_home
78

89

910
def handle_create_event_view(ack: Callable, body: dict[str, Any], client: WebClient):
@@ -16,8 +17,11 @@ def handle_create_event_view(ack: Callable, body: dict[str, Any], client: WebCli
1617
start_time = (values["start_time"]["start_time"]["selected_date_time"],)
1718
end_time = (values["end_time"]["end_time"]["selected_date_time"],)
1819
host_id = values["host"]["host"]["selected_user"]
19-
location = values.get("location", {}).get("location", {}).get("value") or "https://hackclub.slack.com/archives/C07TNAZGMHS"
20-
20+
location = (
21+
values.get("location", {}).get("location", {}).get("value")
22+
or "https://hackclub.slack.com/archives/C07TNAZGMHS"
23+
)
24+
2125
user = client.users_info(user=host_id)
2226
host_name = user["user"]["real_name"]
2327
host_pfp = user["user"]["profile"]["image_192"]
@@ -54,3 +58,5 @@ def handle_create_event_view(ack: Callable, body: dict[str, Any], client: WebCli
5458
}
5559
],
5660
)
61+
62+
client.views_publish(user_id=user_id, view=get_home(user_id, client))

requirements.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
annotated-types==0.7.0
2+
black==24.10.0
3+
certifi==2024.8.30
4+
charset-normalizer==3.4.0
5+
click==8.1.7
6+
idna==3.10
7+
inflection==0.5.1
8+
mypy-extensions==1.0.0
9+
packaging==24.2
10+
pathspec==0.12.1
11+
platformdirs==4.3.6
112
pyairtable==2.3.3
13+
pydantic==2.9.2
14+
pydantic_core==2.23.4
215
python-dotenv==1.0.1
3-
slack_bolt==1.20.1
16+
requests==2.32.3
17+
schedule==1.2.2
18+
slack_bolt==1.20.1
19+
slack_sdk==3.33.3
20+
typing_extensions==4.12.2
21+
urllib3==2.2.3

utils/airtable.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,40 @@ def update_event(
6666
):
6767
event = self.events_table.update(id, updates)
6868
return event
69+
70+
def create_user(self, slack_id: str):
71+
user = self.users_table.create(
72+
{
73+
"Slack ID": slack_id,
74+
}
75+
)
76+
return user
77+
78+
def get_user(self, slack_id: str):
79+
user = self.users_table.first(formula=f"{{Slack ID}} = '{slack_id}'")
80+
if not user:
81+
user = self.create_user(slack_id)
82+
return user
83+
84+
def update_user(self, slack_id: str, **updates: dict):
85+
user = self.users_table.update(slack_id, updates)
86+
return user
87+
88+
def get_rsvps_from_event(self, event_id: str):
89+
rsvps = self.users_table.all()
90+
rsvps = [
91+
rsvp
92+
for rsvp in rsvps
93+
if event_id in rsvp["fields"].get("Interesting Events", [])
94+
]
95+
return rsvps
96+
97+
def rsvp_to_event(self, event_id: str, slack_id: str):
98+
user = self.get_user(slack_id)
99+
events = user["fields"].get("Interesting Events", [])
100+
if event_id in events:
101+
events.remove(event_id)
102+
else:
103+
events.append(event_id)
104+
user = self.update_user(user["id"], **{"Interesting Events": events})
105+
return user

utils/email.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import smtplib
2+
from email.message import EmailMessage
3+
4+
5+
class Email:
6+
"""Connect to SMTP server and send emails
7+
8+
Keyword arguments:
9+
recipient -- str: email address of recipient
10+
sender -- str: email address of sender
11+
password -- str: password of sender
12+
server -- str: SMTP server (default 'smtp.yandex.com')
13+
port -- int: SMTP port (default 465)
14+
15+
"""
16+
17+
def __init__(
18+
self,
19+
sender: str,
20+
password: str,
21+
server: str = "smtp.gmail.com",
22+
port: int = 465,
23+
):
24+
"""Connect to SMTP server and set self variables"""
25+
self.sender = sender
26+
self.server = smtplib.SMTP_SSL(server, port)
27+
self.password = password
28+
try:
29+
self.server.connect(server, port)
30+
self.server.login(self.sender, password)
31+
except Exception as e:
32+
print(e)
33+
34+
def send_email(self, recipient: str, subject: str, message: str):
35+
"""Send an email
36+
37+
Keyword arguments:
38+
subject -- str: Email subject
39+
message -- str: Email content/message
40+
"""
41+
# email = f'Name: {self.name}\nEmail: {self.email}\nSubject: {self.subject}\nMessage: {message}'
42+
msg = EmailMessage()
43+
msg.set_content(message)
44+
msg["Subject"] = subject
45+
msg["From"] = f"Isabelle <{self.sender}>"
46+
msg["To"] = recipient
47+
try:
48+
with self.server as server:
49+
server.login(self.sender, self.password)
50+
server.send_message(msg)
51+
print("Email successfully sent")
52+
return True
53+
except Exception as e:
54+
print("Error", e)
55+
return False

utils/env.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .airtable import AirtableManager
22
from dotenv import load_dotenv
33
import os
4+
from .email import Email
45

56
load_dotenv()
67

@@ -13,6 +14,8 @@ def __init__(self):
1314
self.slack_sad_channel = os.environ.get("SLACK_SAD_CHANNEL")
1415
self.airtable_api_key = os.environ.get("AIRTABLE_API_KEY")
1516
self.airtable_base_id = os.environ.get("AIRTABLE_BASE_ID")
17+
google_username = os.environ.get("GOOGLE_USERNAME")
18+
google_password = os.environ.get("GOOGLE_PASSWORD")
1619

1720
self.port = int(os.environ.get("PORT", 3000))
1821

@@ -28,10 +31,17 @@ def __init__(self):
2831
raise Exception("AIRTABLE_API_KEY is not set")
2932
if not self.airtable_base_id:
3033
raise Exception("AIRTABLE_BASE_ID is not set")
34+
if not google_username:
35+
raise Exception("GOOGLE_USERNAME is not set")
36+
if not google_password:
37+
raise Exception("GOOGLE_PASSWORD is not set")
3138

3239
self.airtable = AirtableManager(
3340
api_key=self.airtable_api_key, base_id=self.airtable_base_id
3441
)
42+
43+
self.mailer = Email(sender=google_username, password=google_password)
44+
3545
self.authorised_users = [
3646
"U054VC2KM9P", # Amber
3747
"U0409FSKU82", # Arpan

utils/rsvp_checker.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from slack_sdk import WebClient
2+
from typing import Any
3+
import schedule
4+
import time
5+
from datetime import datetime
6+
7+
from .env import env
8+
9+
client = WebClient(token=env.slack_bot_token)
10+
11+
12+
def send_reminder(
13+
user_id: str, message: str, event: dict[str, Any], email: bool = False
14+
):
15+
client.chat_postMessage(channel=user_id, text=message)
16+
if email:
17+
email_addr = client.users_info(user=user_id)["user"]["profile"]["email"]
18+
env.mailer.send_email(
19+
email_addr, f"{event["fields"]["Title"]} Reminder!", message
20+
)
21+
22+
23+
def check_rsvps():
24+
events = env.airtable.get_all_events()
25+
26+
for event in events:
27+
if not event["fields"].get("Approved", False):
28+
continue
29+
start_time = datetime.fromisoformat(event["fields"]["Start Time"]).timestamp()
30+
rsvps = env.airtable.get_rsvps_from_event(event["id"])
31+
32+
# Handle 1 day reminders
33+
if start_time - time.time() <= 86400 and not event["fields"].get(
34+
"Sent 1 Day Reminder", False
35+
):
36+
for user in rsvps:
37+
send_reminder(
38+
user["fields"]["Slack ID"],
39+
f"Hey! Just a reminder that {event['fields']['Title']} run by {event['fields']['Leader']} is tomorrow! Hope to see you there!",
40+
event,
41+
)
42+
env.airtable.update_event(event["id"], **{"Sent 1 Day Reminder": True})
43+
44+
# Handle 1 hour reminders
45+
elif start_time - time.time() <= 3600 and not event["fields"].get(
46+
"Sent 1 Hour Reminder", False
47+
):
48+
for user in rsvps:
49+
send_reminder(
50+
user["fields"]["Slack ID"],
51+
f"Hey! Just a reminder that {event['fields']['Title']} run by {event['fields']['Leader']} starts in 1 hour! Hope to see you there!\nYou can join the event at {event['fields'].get('Event Link', 'the Slack!')}",
52+
event,
53+
)
54+
env.airtable.update_event(event["id"], **{"Sent 1 Hour Reminder": True})
55+
56+
elif start_time - time.time() <= 0 and not event["fields"].get(
57+
"Sent Started Reminder", False
58+
):
59+
for user in rsvps:
60+
send_reminder(
61+
user["fields"]["Slack ID"],
62+
f"Hey! Just a reminder that {event['fields']['Title']} run by {event['fields']['Leader']} has started!\nYou can join the event at {event['fields'].get('Event Link', 'the Slack!')}\nHope you enjoy it!",
63+
event,
64+
email=True,
65+
)
66+
env.airtable.update_event(event["id"], **{"Sent Starting Reminder": True})
67+
68+
69+
def rsvp_checker():
70+
schedule.every(1).minutes.do(check_rsvps)
71+
while True:
72+
schedule.run_pending()
73+
time.sleep(1)

utils/slack.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from events.views.create_event import handle_create_event_view
88
from events.buttons.propose_event import handle_propose_event_btn
99
from events.buttons.approve_event import handle_approve_event_btn
10+
from events.buttons.rsvp import handle_rsvp_btn
1011
from views.app_home import get_home
1112

1213
from typing import Any, Callable
@@ -51,3 +52,8 @@ def create_event(ack: Callable, body: dict[str, Any], client: WebClient):
5152
@app.action("add-to-gcal")
5253
def add_to_gcal(ack: Callable):
5354
ack()
55+
56+
57+
@app.action("rsvp")
58+
def rsvp(ack: Callable, body: dict[str, Any], client: WebClient):
59+
handle_rsvp_btn(ack, body, client)

0 commit comments

Comments
 (0)