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