Coverage for src/couchers/notifications/render.py: 87%
247 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-08-28 14:55 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-08-28 14:55 +0000
1import logging
2from dataclasses import dataclass
4from couchers import urls
5from couchers.notifications.quick_links import generate_quick_decline_link, generate_unsub_topic_action
6from couchers.templates.v2 import v2avatar, v2date, v2esc, v2phone, v2timestamp
7from couchers.utils import now, to_aware_datetime
8from proto import notification_data_pb2
10logger = logging.getLogger(__name__)
13@dataclass(kw_only=True)
14class RenderedNotification:
15 # whether the notification is critical and cannot be turned off
16 is_critical: bool = False
17 # whether this email can be sent to someone who is deleted
18 allow_deleted: bool = False
19 # email subject
20 email_subject: str
21 # shows up when listing emails in many clients
22 email_preview: str
23 # corresponds to .mjml + .txt file in templates/v2
24 email_template_name: str
25 # other template args
26 email_template_args: dict
27 # the link label on the topic_action unsubscribe link
28 email_topic_action_unsubscribe_text: str = None
29 # the link label on the topic_key unsubscribe link
30 email_topic_key_unsubscribe_text: str = None
31 # url to unsubscribe with one click
32 email_list_unsubscribe_url: str = None
33 # push notification title
34 push_title: str
35 # push notification content
36 push_body: str
37 # url to an icon for push notifications
38 push_icon: str
39 # url to where clicking on the notification should take you
40 push_url: str
43def render_notification(user, notification) -> RenderedNotification:
44 data = notification.topic_action.data_type.FromString(notification.data)
45 if notification.topic == "host_request":
46 view_link = urls.host_request(host_request_id=data.host_request.host_request_id)
47 if notification.action == "missed_messages":
48 their_your = "their" if data.am_host else "your"
49 other = data.user
50 # "declined your host request", or similar
51 message = f"{other.name} sent you message(s) in {their_your} host request"
52 return RenderedNotification(
53 email_subject=message,
54 email_preview=message,
55 email_template_name="host_request__plain",
56 email_template_args={
57 "view_link": view_link,
58 "host_request": data.host_request,
59 "message": message,
60 "other": other,
61 },
62 email_topic_action_unsubscribe_text="missed messages in host requests",
63 push_title=message,
64 push_body="Check the app for more info.",
65 push_icon=v2avatar(other),
66 push_url=view_link,
67 )
68 elif notification.action == "create":
69 other = data.surfer
70 message = f"{other.name} sent you a host request"
71 return RenderedNotification(
72 email_subject=message,
73 email_preview=message,
74 email_template_name="host_request__new",
75 email_template_args={
76 "view_link": view_link,
77 "quick_decline_link": generate_quick_decline_link(data.host_request),
78 "host_request": data.host_request,
79 "message": message,
80 "other": other,
81 "text": data.text,
82 },
83 email_topic_action_unsubscribe_text="new host requests",
84 push_title=f"{message}",
85 push_body=f"Dates: {v2date(data.host_request.from_date, user)} to {v2date(data.host_request.to_date, user)}.\n\n{data.text}",
86 push_icon=v2avatar(other),
87 push_url=view_link,
88 )
89 elif notification.action == "message":
90 other = data.user
91 if data.am_host:
92 message = f"{other.name} sent you a message in their host request"
93 else:
94 message = f"{other.name} sent you a message in your host request"
95 topic_action_unsub_text = "messages in host request"
96 return RenderedNotification(
97 email_subject=message,
98 email_preview=message,
99 email_template_name="host_request__message",
100 email_template_args={
101 "view_link": view_link,
102 "host_request": data.host_request,
103 "message": message,
104 "other": other,
105 "text": data.text,
106 },
107 email_topic_action_unsubscribe_text=topic_action_unsub_text,
108 push_title=f"{message}",
109 push_body=f"Dates: {v2date(data.host_request.from_date, user)} to {v2date(data.host_request.to_date, user)}.\n\n{data.text}",
110 push_icon=v2avatar(other),
111 push_url=view_link,
112 )
113 elif notification.action in ["accept", "reject", "confirm", "cancel"]:
114 if notification.action in ["accept", "reject"]:
115 other = data.host
116 their_your = "your"
117 else:
118 other = data.surfer
119 their_your = "their"
120 actioned = {
121 "accept": "accepted",
122 "reject": "declined",
123 "confirm": "confirmed",
124 "cancel": "cancelled",
125 }[notification.action]
126 # "declined your host request", or similar
127 message = f"{other.name} {actioned} {their_your} host request"
128 return RenderedNotification(
129 email_subject=message,
130 email_preview=message,
131 email_template_name="host_request__plain",
132 email_template_args={
133 "view_link": view_link,
134 "host_request": data.host_request,
135 "message": message,
136 "other": other,
137 },
138 email_topic_action_unsubscribe_text=f"{actioned} host requests",
139 push_title=message,
140 push_body="Check the app for more info.",
141 push_icon=v2avatar(other),
142 push_url=view_link,
143 )
144 elif notification.action == "reminder":
145 message = f"You have a pending host request from {data.surfer.name}!"
146 description = "Please respond to the request!"
147 return RenderedNotification(
148 email_subject=message,
149 email_preview=description,
150 email_template_name="host_request__plain",
151 email_template_args={
152 "view_link": view_link,
153 "host_request": data.host_request,
154 "message": description,
155 "other": data.surfer,
156 },
157 email_topic_action_unsubscribe_text="Pending host request reminders",
158 push_title=message,
159 push_body=description,
160 push_icon=v2avatar(data.surfer),
161 push_url=view_link,
162 )
163 elif notification.topic_action.display == "password:change":
164 title = "Your password was changed"
165 message = "Your login password for Couchers.org was changed."
166 return RenderedNotification(
167 is_critical=True,
168 email_subject=title,
169 email_preview=message,
170 email_template_name="security",
171 email_template_args={
172 "title": title,
173 "message": message,
174 },
175 push_title=title,
176 push_body=message,
177 push_icon=urls.icon_url(),
178 push_url=urls.account_settings_link(),
179 )
180 elif notification.topic_action.display == "password_reset:start":
181 message = "Someone initiated a password change on your account."
182 return RenderedNotification(
183 is_critical=True,
184 email_subject="Reset your Couchers.org password",
185 email_preview=message,
186 email_template_name="password_reset",
187 email_template_args={
188 "password_reset_link": urls.password_reset_link(password_reset_token=data.password_reset_token)
189 },
190 push_title="A password reset was initiated on your account",
191 push_body=message,
192 push_icon=urls.icon_url(),
193 push_url=urls.account_settings_link(),
194 )
195 elif notification.topic_action.display == "password_reset:complete":
196 title = "Your password was successfully reset"
197 message = "Your password on Couchers.org was changed. If that was you, then no further action is needed."
198 return RenderedNotification(
199 is_critical=True,
200 email_subject=title,
201 email_preview=title,
202 email_template_name="security",
203 email_template_args={
204 "title": title,
205 "message": message,
206 },
207 push_title=title,
208 push_body=message,
209 push_icon=urls.icon_url(),
210 push_url=urls.account_settings_link(),
211 )
212 elif notification.topic_action.display == "email_address:change":
213 title = "An email change was initiated on your account"
214 message = f"An email change to the email <b>{data.new_email}</b> was initiated on your account."
215 message_plain = f"An email change to the email {data.new_email} was initiated on your account."
216 return RenderedNotification(
217 is_critical=True,
218 email_subject=title,
219 email_preview=title,
220 email_template_name="security",
221 email_template_args={
222 "title": title,
223 "message": message,
224 },
225 push_title=title,
226 push_body=message_plain,
227 push_icon=urls.icon_url(),
228 push_url=urls.account_settings_link(),
229 )
230 elif notification.topic_action.display == "email_address:verify":
231 title = "Email change completed"
232 message = "Your new email address has been verified."
233 return RenderedNotification(
234 is_critical=True,
235 email_subject=title,
236 email_preview=message,
237 email_template_name="security",
238 email_template_args={
239 "title": title,
240 "message": message,
241 },
242 push_title=title,
243 push_body=message,
244 push_icon=urls.icon_url(),
245 push_url=urls.account_settings_link(),
246 )
247 elif notification.topic_action.display == "phone_number:change":
248 title = "Phone verification started"
249 message = f"You started phone number verification with the number <b>{v2phone(data.phone)}</b>."
250 message_plain = f"You started phone number verification with the number {v2phone(data.phone)}."
251 return RenderedNotification(
252 is_critical=True,
253 email_subject=title,
254 email_preview=message,
255 email_template_name="security",
256 email_template_args={
257 "title": title,
258 "message": message,
259 },
260 push_title=title,
261 push_body=message_plain,
262 push_icon=urls.icon_url(),
263 push_url=urls.feature_preview_link(),
264 )
265 elif notification.topic_action.display == "phone_number:verify":
266 title = "Phone successfully verified"
267 message = f"Your phone was successfully verified as <b>{v2phone(data.phone)}</b> on Couchers.org."
268 message_plain = f"Your phone was successfully verified as {v2phone(data.phone)} on Couchers.org."
269 return RenderedNotification(
270 is_critical=True,
271 email_subject=title,
272 email_preview=message_plain,
273 email_template_name="security",
274 email_template_args={
275 "title": title,
276 "message": message,
277 },
278 push_title=title,
279 push_body=message_plain,
280 push_icon=urls.icon_url(),
281 push_url=urls.feature_preview_link(),
282 )
283 elif notification.topic_action.display == "gender:change":
284 title = "Your gender was changed"
285 message = f"Your gender on Couchers.org was changed to <b>{data.gender}</b> by an admin."
286 message_plain = f"Your gender on Couchers.org was changed to {data.gender} by an admin."
287 return RenderedNotification(
288 is_critical=True,
289 email_subject=title,
290 email_preview=message_plain,
291 email_template_name="security",
292 email_template_args={
293 "title": title,
294 "message": message,
295 },
296 push_title=title,
297 push_body=message_plain,
298 push_icon=urls.icon_url(),
299 push_url=urls.account_settings_link(),
300 )
301 elif notification.topic_action.display == "birthdate:change":
302 title = "Your date of birth was changed"
303 message = (
304 f"Your date of birth on Couchers.org was changed to <b>{v2date(data.birthdate, user)}</b> by an admin."
305 )
306 message_plain = f"Your date of birth on Couchers.org was changed to {v2date(data.birthdate, user)} by an admin."
307 return RenderedNotification(
308 is_critical=True,
309 email_subject=title,
310 email_preview=message_plain,
311 email_template_name="security",
312 email_template_args={
313 "title": title,
314 "message": message,
315 },
316 push_title=title,
317 push_body=message_plain,
318 push_icon=urls.icon_url(),
319 push_url=urls.account_settings_link(),
320 )
321 elif notification.topic_action.display == "api_key:create":
322 return RenderedNotification(
323 is_critical=True,
324 email_subject="Your API key for Couchers.org",
325 email_preview="We have issued you an API key as per your request.",
326 email_template_name="api_key",
327 email_template_args={
328 "api_key": data.api_key,
329 "expiry": data.expiry,
330 },
331 push_title="An API key was created for your account",
332 push_body="Details were sent to you via email.",
333 push_icon=urls.icon_url(),
334 push_url=urls.app_link(),
335 )
336 elif notification.topic_action.display in ["badge:add", "badge:remove"]:
337 actioned = "added to" if notification.action == "add" else "removed from"
338 title = f"The {data.badge_name} badge was {actioned} your profile"
339 return RenderedNotification(
340 email_subject=title,
341 email_preview=title,
342 email_template_name="badge",
343 email_template_args={
344 "badge_name": data.badge_name,
345 "actioned": actioned,
346 "unsub_type": "badge additions" if notification.action == "add" else "badge removals",
347 },
348 email_topic_action_unsubscribe_text="badge additions" if notification.action == "add" else "badge removals",
349 push_title=title,
350 push_body=(
351 "Check out your profile to see the new badge!"
352 if notification.action == "add"
353 else "You can see all your badges on your profile."
354 ),
355 push_icon=urls.icon_url(),
356 push_url=urls.profile_link(),
357 email_list_unsubscribe_url=generate_unsub_topic_action(notification),
358 )
359 elif notification.topic_action.display == "donation:received":
360 title = "Thank you for your donation to Couchers.org!"
361 message = f"Thank you so much for your donation of ${data.amount} to Couchers.org."
362 return RenderedNotification(
363 is_critical=True,
364 email_subject=title,
365 email_preview=message,
366 email_template_name="donation_received",
367 email_template_args={
368 "amount": data.amount,
369 "receipt_url": data.receipt_url,
370 },
371 push_title=title,
372 push_body=message,
373 push_icon=urls.icon_url(),
374 push_url=data.receipt_url,
375 )
376 elif notification.topic_action.display == "friend_request:create":
377 other = data.other_user
378 preview = f"You've received a friend request from {other.name}"
379 return RenderedNotification(
380 email_subject=f"{other.name} wants to be your friend on Couchers.org!",
381 email_preview=preview,
382 email_template_name="friend_request",
383 email_template_args={
384 "friend_requests_link": urls.friend_requests_link(),
385 "other": other,
386 },
387 email_topic_action_unsubscribe_text="new friend requests",
388 push_title=f"{other.name} wants to be your friend",
389 push_body=preview,
390 push_icon=v2avatar(other),
391 push_url=urls.friend_requests_link(),
392 )
393 elif notification.topic_action.display == "friend_request:accept":
394 other = data.other_user
395 title = f"{other.name} accepted your friend request!"
396 preview = f"{v2esc(other.name)} has accepted your friend request"
397 return RenderedNotification(
398 email_subject=title,
399 email_preview=preview,
400 email_template_name="friend_request_accepted",
401 email_template_args={
402 "other_user_link": urls.user_link(username=other.username),
403 "other": other,
404 },
405 email_topic_action_unsubscribe_text="accepted friend requests",
406 push_title=title,
407 push_body=preview,
408 push_icon=v2avatar(other),
409 push_url=urls.user_link(username=other.username),
410 )
411 elif notification.topic_action.display == "account_deletion:start":
412 return RenderedNotification(
413 is_critical=True,
414 allow_deleted=True,
415 email_subject="Confirm your Couchers.org account deletion",
416 email_preview="Please confirm that you want to delete your Couchers.org account.",
417 email_template_name="account_deletion_start",
418 email_template_args={
419 "deletion_link": urls.delete_account_link(account_deletion_token=data.deletion_token),
420 },
421 push_title="Account deletion initiated",
422 push_body="Someone initiated the deletion of your Couchers.org account. To delete your account, please follow the link in the email we sent you.",
423 push_icon=urls.icon_url(),
424 push_url=urls.app_link(),
425 )
426 elif notification.topic_action.display == "account_deletion:complete":
427 title = "Your Couchers.org account has been deleted"
428 return RenderedNotification(
429 is_critical=True,
430 allow_deleted=True,
431 email_subject=title,
432 email_preview="We have deleted your Couchers.org account, to undo, follow the link in this email.",
433 email_template_name="account_deletion_complete",
434 email_template_args={
435 "undelete_link": urls.recover_account_link(account_undelete_token=data.undelete_token),
436 "days": data.undelete_days,
437 },
438 push_title=title,
439 push_body=f"You can still undo this by following the link we emailed to you within {data.undelete_days} days.",
440 push_icon=urls.icon_url(),
441 push_url=urls.app_link(),
442 )
443 elif notification.topic_action.display == "account_deletion:recovered":
444 title = "Your Couchers.org account has been recovered!"
445 subtitle = "We have recovered your Couchers.org account as per your request! Welcome back!"
446 return RenderedNotification(
447 is_critical=True,
448 allow_deleted=True,
449 email_subject=title,
450 email_preview=subtitle,
451 email_template_name="account_deletion_recovered",
452 email_template_args={
453 "app_link": urls.app_link(),
454 },
455 push_title=title,
456 push_body=subtitle,
457 push_icon=urls.icon_url(),
458 push_url=urls.app_link(),
459 )
460 elif notification.topic_action.display == "chat:message":
461 return RenderedNotification(
462 email_subject=data.message,
463 email_preview="You received a message on Couchers.org!",
464 email_template_name="chat_message",
465 email_template_args={
466 "author": data.author,
467 "message": data.message,
468 "text": data.text,
469 "view_link": urls.chat_link(chat_id=data.group_chat_id),
470 },
471 email_topic_action_unsubscribe_text="new chat messages",
472 email_topic_key_unsubscribe_text="this chat (mute)",
473 push_title=data.message,
474 push_body=data.text,
475 push_icon=v2avatar(data.author),
476 push_url=urls.chat_link(chat_id=data.group_chat_id),
477 )
478 elif notification.topic_action.display == "chat:missed_messages":
479 return RenderedNotification(
480 email_subject="You have unseen messages on Couchers.org!",
481 email_preview="You missed some messages on the platform.",
482 email_template_name="chat_unseen_messages",
483 email_template_args={
484 "items": [
485 {
486 "author": item.author,
487 "message": item.message,
488 "text": item.text,
489 "view_link": urls.chat_link(chat_id=item.group_chat_id),
490 }
491 for item in data.messages
492 ]
493 },
494 email_topic_action_unsubscribe_text="unseen chat messages",
495 push_title="You have unseen messages on Couchers.org",
496 push_body="Please check out any messages you missed.",
497 push_icon=urls.icon_url(),
498 push_url=urls.messages_link(),
499 )
500 elif notification.topic == "event":
501 event = data.event
502 time_display = f"{v2timestamp(event.start_time, user)} - {v2timestamp(event.end_time, user)}"
503 event_link = urls.event_link(occurrence_id=event.event_id, slug=event.slug)
504 if notification.action in ["create_approved", "create_any"]:
505 # create_approved = invitation, approved by mods
506 # create_any = new event created by anyone (no need for approval) -- off by default
507 body = f"{time_display}\n"
508 if notification.action == "create_approved":
509 subject = f'{data.inviting_user.name} invited you to "{event.title}"'
510 start_text = "You've been invited to a new event"
511 body += f"Invited by {data.inviting_user.name}\n\n"
512 elif notification.action == "create_any":
513 subject = f'{data.inviting_user.name} created an event called "{event.title}"'
514 start_text = "A new event was created"
515 body += f"Created by {data.inviting_user.name}\n\n"
516 body += event.content
517 community_link = (
518 urls.community_link(node_id=data.in_community.community_id, slug=data.in_community.slug)
519 if data.in_community
520 else None
521 )
522 return RenderedNotification(
523 email_subject=subject,
524 email_preview=f"{start_text} on Couchers.org!",
525 email_template_name="event_create",
526 email_template_args={
527 "inviting_user": data.inviting_user,
528 "time_display": time_display,
529 "start_text": start_text,
530 "nearby": "nearby" if data.nearby else None,
531 "community": data.in_community if data.in_community else None,
532 "community_link": community_link,
533 "event": event,
534 "view_link": event_link,
535 },
536 email_topic_action_unsubscribe_text=(
537 "new events by community members"
538 if notification.action == "create_any"
539 else "invitations to events (approved by moderators)"
540 ),
541 push_title=subject,
542 push_body=body,
543 push_icon=v2avatar(data.inviting_user),
544 push_url=event_link,
545 )
546 elif notification.action == "update":
547 updated_text = ", ".join(data.updated_items)
548 body = f"{time_display}\n"
549 body += f"{data.updating_user.name} updated: {updated_text}\n\n"
550 body += event.content
551 return RenderedNotification(
552 email_subject=f'{data.updating_user.name} updated "{event.title}"',
553 email_preview="An event you are subscribed to was updated.",
554 email_template_name="event_update",
555 email_template_args={
556 "updating_user": data.updating_user,
557 "time_display": time_display,
558 "event": event,
559 "updated_text": updated_text,
560 "view_link": event_link,
561 },
562 email_topic_action_unsubscribe_text="event updates",
563 push_title=f'{data.updating_user.name} updated "{event.title}"',
564 push_body=body,
565 push_icon=v2avatar(data.updating_user),
566 push_url=event_link,
567 )
568 elif notification.action == "cancel":
569 body = f"{time_display}\n"
570 body += f"The event has been cancelled by {data.cancelling_user.name}.\n\n"
571 body += event.content
572 return RenderedNotification(
573 email_subject=f'{data.cancelling_user.name} cancelled "{event.title}"',
574 email_preview="An event you are subscribed to has been cancelled.",
575 email_template_name="event_cancel",
576 email_template_args={
577 "cancelling_user": data.cancelling_user,
578 "time_display": time_display,
579 "event": event,
580 "view_link": event_link,
581 },
582 email_topic_action_unsubscribe_text="event cancellations",
583 push_title=f'{data.cancelling_user.name} cancelled "{event.title}"',
584 push_body=body,
585 push_icon=v2avatar(data.cancelling_user),
586 push_url=event_link,
587 )
588 elif notification.action == "delete":
589 return RenderedNotification(
590 email_subject=f'A moderator deleted "{event.title}"',
591 email_preview="An event you are subscribed to has been deleted.",
592 email_template_name="event_delete",
593 email_template_args={
594 "time_display": time_display,
595 "event": event,
596 },
597 email_topic_action_unsubscribe_text="event deletions",
598 push_title=f'A moderator deleted "{event.title}"',
599 push_body=f"{time_display}\nThe event has been deleted by the moderators.",
600 push_icon=urls.icon_url(),
601 push_url=urls.app_link(),
602 )
603 elif notification.action == "invite_organizer":
604 body = f"{time_display}\n"
605 body += f"Invited to co-organize by {data.inviting_user.name}\n\n"
606 body += event.content
607 return RenderedNotification(
608 email_subject=f'{data.inviting_user.name} invited you to co-organize "{event.title}"',
609 email_preview="You were invited to co-organize an event on Couchers.org.",
610 email_template_name="event_invite_organizer",
611 email_template_args={
612 "inviting_user": data.inviting_user,
613 "time_display": time_display,
614 "event": event,
615 "view_link": event_link,
616 },
617 email_topic_action_unsubscribe_text="invitations to co-organize events",
618 push_title=f'{data.inviting_user.name} invited you to co-organize "{event.title}"',
619 push_body=body,
620 push_icon=v2avatar(data.inviting_user),
621 push_url=event_link,
622 )
623 elif notification.action == "comment":
624 body = f"{time_display}\n"
625 body += f"{data.author.name} commented:\n\n"
626 body += data.reply.content
627 return RenderedNotification(
628 email_subject=f'{data.author.name} commented on "{event.title}"',
629 email_preview="Someone commented on an event you are attending.",
630 email_template_name="event_comment",
631 email_template_args={
632 "author": data.author,
633 "time_display": time_display,
634 "event": event,
635 "content": data.reply.content,
636 "view_link": event_link,
637 },
638 email_topic_action_unsubscribe_text="event comments",
639 push_title=f'{data.author.name} commented on "{event.title}"',
640 push_body=body,
641 push_icon=v2avatar(data.author),
642 push_url=event_link,
643 )
644 elif notification.action == "reminder":
645 body = "Don't forget your upcoming event on Couchers.org\n"
646 body += f"{time_display}\n"
647 body += data.event.content
648 return RenderedNotification(
649 email_subject=f'Reminder: "{data.event.title}" starts soon',
650 email_preview="Don't forget your upcoming event on Couchers.org",
651 email_template_name="event_reminder",
652 email_template_args={
653 "time_display": time_display,
654 "event": event,
655 "view_link": event_link,
656 },
657 email_topic_action_unsubscribe_text="event reminders",
658 push_title=f'"{data.event.title}" starts soon',
659 push_body=body,
660 push_icon=urls.icon_url(),
661 push_url=event_link,
662 )
663 elif notification.topic == "discussion":
664 discussion = data.discussion
665 discussion_link = urls.discussion_link(discussion_id=discussion.discussion_id, slug=discussion.slug)
666 if notification.action == "create":
667 body = f"{data.author.name} created a discussion in {discussion.owner_title}: {discussion.title}\n\n"
668 body += discussion.content
669 return RenderedNotification(
670 email_subject=f'{data.author.name} created a discussion: "{discussion.title}"',
671 email_preview="Someone created a discussion in a community or group you are subscribed to.",
672 email_template_name="discussion_create",
673 email_template_args={
674 "author": data.author,
675 "discussion": discussion,
676 "view_link": discussion_link,
677 },
678 email_topic_action_unsubscribe_text="new discussions",
679 push_title=discussion.title,
680 push_body=body,
681 push_icon=v2avatar(data.author),
682 push_url=discussion_link,
683 )
684 elif notification.action == "comment":
685 body = f"{data.author.name} commented:\n\n"
686 body += data.reply.content
687 return RenderedNotification(
688 email_subject=f'{data.author.name} commented on "{discussion.title}"',
689 email_preview="Someone commented on your discussion.",
690 email_template_name="discussion_comment",
691 email_template_args={
692 "author": data.author,
693 "discussion": discussion,
694 "reply": data.reply,
695 "view_link": discussion_link,
696 },
697 email_topic_action_unsubscribe_text="discussion comments",
698 push_title=discussion.title,
699 push_body=body,
700 push_icon=v2avatar(data.author),
701 push_url=discussion_link,
702 )
703 elif notification.topic_action.display == "thread:reply":
704 parent = data.WhichOneof("reply_parent")
705 if parent == "event":
706 title = data.event.title
707 view_link = urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug)
708 elif parent == "discussion":
709 title = data.discussion.title
710 view_link = urls.discussion_link(discussion_id=data.discussion.discussion_id, slug=data.discussion.slug)
711 else:
712 raise Exception("Can only do replies to events and discussions")
714 body = f"{data.author.name} replied:\n\n"
715 body += data.reply.content
716 return RenderedNotification(
717 email_subject=f'{data.author.name} replied in "{title}"',
718 email_preview="Someone replied in a comment thread you have participated in.",
719 email_template_name="comment_reply",
720 email_template_args={
721 "author": data.author,
722 "title": title,
723 "reply": data.reply,
724 "view_link": view_link,
725 },
726 email_topic_action_unsubscribe_text="comment replies",
727 push_title=title,
728 push_body=body,
729 push_icon=v2avatar(data.author),
730 push_url=view_link,
731 )
732 elif notification.topic == "reference":
733 if notification.action == "receive_friend":
734 title = f"You've received a friend reference from {data.from_user.name}!"
735 return RenderedNotification(
736 email_subject=title,
737 email_preview=v2esc(data.text),
738 email_template_name="friend_reference",
739 email_template_args={
740 "from_user": data.from_user,
741 "profile_references_link": urls.profile_references_link(),
742 "text": data.text,
743 },
744 email_topic_action_unsubscribe_text="new references from friends",
745 push_title=title,
746 push_body=data.text,
747 push_icon=v2avatar(data.from_user),
748 push_url=urls.profile_references_link(),
749 )
750 elif notification.action in ["receive_hosted", "receive_surfed"]:
751 title = f"You've received a reference from {data.from_user.name}!"
752 # what was my type? i surfed with them if i received a "hosted" request
753 surfed = notification.action == "receive_hosted"
754 leave_reference_link = urls.leave_reference_link(
755 reference_type="surfed" if surfed else "hosted",
756 to_user_id=data.from_user.user_id,
757 host_request_id=data.host_request_id,
758 )
759 profile_references_link = urls.profile_references_link()
760 if data.text:
761 body = v2esc(data.text)
762 push_url = profile_references_link
763 else:
764 body = "Please go and write a reference for them too. It's a nice gesture and helps us build a community together!"
765 push_url = leave_reference_link
766 return RenderedNotification(
767 email_subject=title,
768 email_preview=body,
769 email_template_name="host_reference",
770 email_template_args={
771 "from_user": data.from_user,
772 "leave_reference_link": leave_reference_link,
773 "profile_references_link": profile_references_link,
774 "text": data.text,
775 "both_written": True if data.text else False,
776 "surfed": surfed,
777 },
778 email_topic_action_unsubscribe_text="new references from " + ("hosts" if surfed else "surfers"),
779 push_title=title,
780 push_body=body,
781 push_icon=v2avatar(data.from_user),
782 push_url=push_url,
783 )
784 elif notification.action in ["reminder_hosted", "reminder_surfed"]:
785 # what was my type? i surfed with them if i get a surfed reminder
786 surfed = notification.action == "reminder_surfed"
787 leave_reference_link = urls.leave_reference_link(
788 reference_type="surfed" if surfed else "hosted",
789 to_user_id=data.other_user.user_id,
790 host_request_id=data.host_request_id,
791 )
792 title = f"You have {data.days_left} days to write a reference for {data.other_user.name}!"
793 preview = "It's a nice gesture to write references and helps us build a community together! References will become visible 2 weeks after the stay, or when you've both written a reference for each other, whichever happens first."
794 return RenderedNotification(
795 email_subject=title,
796 email_preview=preview,
797 email_template_name="reference_reminder",
798 email_template_args={
799 "other_user": data.other_user,
800 "leave_reference_link": leave_reference_link,
801 "days_left": str(data.days_left),
802 "surfed": surfed,
803 },
804 email_topic_action_unsubscribe_text=("surfed" if surfed else "hosted") + " reference reminders",
805 push_title=title,
806 push_body=preview,
807 push_icon=v2avatar(data.other_user),
808 push_url=leave_reference_link,
809 )
810 elif notification.topic_action.display == "onboarding:reminder":
811 if notification.key == "1":
812 return RenderedNotification(
813 email_subject="Welcome to Couchers.org and the future of couch surfing",
814 email_preview="We are so excited to have you join our community!",
815 email_template_name="onboarding1",
816 email_template_args={
817 "app_link": urls.app_link(),
818 "edit_profile_link": urls.edit_profile_link(),
819 },
820 email_topic_action_unsubscribe_text="onboarding emails",
821 push_title="Welcome to Couchers.org and the future of couch surfing",
822 push_body=f"Hi {v2esc(user.name)}! We are excited that you have joined us! Please take a moment to complete your profile with a picture and a bit of text about yourself!",
823 push_icon=urls.icon_url(),
824 push_url=urls.edit_profile_link(),
825 )
826 elif notification.key == "2":
827 return RenderedNotification(
828 email_subject="Complete your profile on Couchers.org",
829 email_preview="We would ask one big favour of you: please fill out your profile by adding a photo and some text.",
830 email_template_name="onboarding2",
831 email_template_args={
832 "edit_profile_link": urls.edit_profile_link(),
833 },
834 email_topic_action_unsubscribe_text="onboarding emails",
835 push_title="Please complete your profile on Couchers.org!",
836 push_body=f"Hi {v2esc(user.name)}! We would ask one big favour of you: please fill out your profile by adding a photo and some text.",
837 push_icon=urls.icon_url(),
838 push_url=urls.edit_profile_link(),
839 )
840 elif notification.topic_action.display == "modnote:create":
841 title = "You have received a mod note"
842 message = "You have received an important note from the moderators. You must read and acknowledge it before continuing to use the platform."
843 return RenderedNotification(
844 is_critical=True,
845 email_subject=title,
846 email_preview=message,
847 email_template_name="mod_note",
848 email_template_args={"title": title},
849 push_title="You received a mod note",
850 push_body="You need to read and acknowledge the note before continuing to use the platform.",
851 push_icon=urls.icon_url(),
852 push_url=urls.app_link(),
853 )
854 elif notification.topic_action.display == "verification:sv_success":
855 title = "Strong Verification succeeded"
856 message = "You have been verified with Strong Verification! You will now see a tick next to your name on the platform."
857 return RenderedNotification(
858 is_critical=True,
859 email_subject=title,
860 email_preview=message,
861 email_template_name="strong_verification_success",
862 email_template_args={
863 "message": message,
864 },
865 push_title=title,
866 push_body=message,
867 push_icon=urls.icon_url(),
868 push_url=urls.account_settings_link(),
869 )
870 elif notification.topic_action.display == "verification:sv_fail":
871 title = "Strong Verification failed"
872 message: str
873 if data.reason == notification_data_pb2.SV_FAIL_REASON_WRONG_BIRTHDATE_OR_GENDER:
874 message = "The date of birth or gender on your profile does not match the date of birth or sex on your passport. Please contact the support team to update your date of birth or gender, or if your passport sex does not match your gender identity."
875 elif data.reason == notification_data_pb2.SV_FAIL_REASON_NOT_A_PASSPORT:
876 message = "You tried to verify with a document that is not a passport. You can only use a passport for Strong Verification."
877 elif data.reason == notification_data_pb2.SV_FAIL_REASON_DUPLICATE:
878 message = "You tried to verify with a passport that has already been used for verification. Please use another passport."
879 else:
880 raise Exception("Shouldn't get here")
881 return RenderedNotification(
882 is_critical=True,
883 email_subject=title,
884 email_preview=title,
885 email_template_name="security",
886 email_template_args={
887 "title": title,
888 "message": message,
889 },
890 push_title=title,
891 push_body=message,
892 push_icon=urls.icon_url(),
893 push_url=urls.account_settings_link(),
894 )
895 elif notification.topic_action.display == "activeness:probe":
896 title = "Are you still open to hosting on Couchers.org?"
897 return RenderedNotification(
898 email_subject=title,
899 email_preview=title,
900 email_template_name="activeness_probe",
901 email_template_args={
902 "app_link": urls.app_link(),
903 "days_left": (to_aware_datetime(data.deadline) - now()).days,
904 },
905 push_title=title,
906 push_body="Please log in to confirm your hosting status.",
907 push_icon=urls.icon_url(),
908 push_url=urls.app_link(),
909 )
910 elif notification.topic_action.display == "general:new_blog_post":
911 title = f"New blog post: {data.title}"
912 return RenderedNotification(
913 email_subject=title,
914 email_preview=data.blurb,
915 email_template_name="new_blog_post",
916 email_template_args={
917 "title": data.title,
918 "blurb": data.blurb,
919 "url": data.url,
920 },
921 email_topic_action_unsubscribe_text="new blog post alerts",
922 push_title=title,
923 push_body=data.blurb,
924 push_icon=urls.icon_url(),
925 push_url=data.url,
926 )
927 else:
928 raise NotImplementedError(f"Unknown topic-action: {notification.topic}:{notification.action}")