Coverage for src / couchers / notifications / render_push.py: 83%
273 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-07 16:21 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-07 16:21 +0000
1"""
2Renders a Notification model into a localized push notification.
3"""
5import logging
6from typing import Any, assert_never
8from couchers import urls
9from couchers.models import Notification, NotificationTopicAction, User
10from couchers.notifications.push import PushNotificationContent
11from couchers.proto import events_pb2, notification_data_pb2
12from couchers.templates.v2 import v2avatar, v2date, v2esc, v2phone, v2timestamp
14logger = logging.getLogger(__name__)
16# Best practices for push notification strings (Android/iOS lowest common denominator):
17# Title:
18# - Describe the event, e.g. "Payment Successful"
19# - <= 30 chars (Android), most important info in first 20 chars
20# - Title-style capitalization, no ending punctuation
21# Body:
22# - <= 80 chars (Android), first 40 visible when collapsed
23# - Sentence-style capitalization with punctuation
26def render_push_notification(user: User, notification: Notification) -> PushNotificationContent:
27 # Any-typed so it implicitly converts when calling the methods below
28 data: Any = notification.topic_action.data_type.FromString(notification.data) # type: ignore[attr-defined]
29 match notification.topic_action:
30 # Using a match statement enable mypy's exhaustiveness checking.
31 # Every case is has its own function so that they can declare different types for "data",
32 # as mypy wouldn't allow that in a single function.
33 # Keep topics sorted (actions can follow logical ordering)
34 case NotificationTopicAction.account_deletion__start:
35 return _account_deletion__start(data)
36 case NotificationTopicAction.account_deletion__complete:
37 return _account_deletion__complete(data)
38 case NotificationTopicAction.account_deletion__recovered:
39 return _account_deletion__recovered()
40 case NotificationTopicAction.activeness__probe:
41 return _activeness__probe(data)
42 case NotificationTopicAction.api_key__create:
43 return _api_key__create(data)
44 case NotificationTopicAction.badge__add:
45 return _badge__add(data)
46 case NotificationTopicAction.badge__remove:
47 return _badge__remove(data)
48 case NotificationTopicAction.birthdate__change:
49 return _birthdate__change(data, user)
50 case NotificationTopicAction.chat__message:
51 return _chat__message(data)
52 case NotificationTopicAction.chat__missed_messages: 52 ↛ 53line 52 didn't jump to line 53 because the pattern on line 52 never matched
53 return _chat__missed_messages()
54 case NotificationTopicAction.donation__received:
55 return _donation__received(data)
56 case NotificationTopicAction.discussion__create:
57 return _discussion__create(data)
58 case NotificationTopicAction.discussion__comment:
59 return _discussion__comment(data)
60 case NotificationTopicAction.email_address__change:
61 return _email_address__change(data)
62 case NotificationTopicAction.email_address__verify:
63 return _email_address__verify()
64 case NotificationTopicAction.event__create_any: 64 ↛ 65line 64 didn't jump to line 65 because the pattern on line 64 never matched
65 return _event__create_any(data, user)
66 case NotificationTopicAction.event__create_approved:
67 return _event__create_approved(data, user)
68 case NotificationTopicAction.event__update: 68 ↛ 69line 68 didn't jump to line 69 because the pattern on line 68 never matched
69 return _event__update(data, user)
70 case NotificationTopicAction.event__invite_organizer: 70 ↛ 71line 70 didn't jump to line 71 because the pattern on line 70 never matched
71 return _event__invite_organizer(data, user)
72 case NotificationTopicAction.event__comment:
73 return _event__comment(data, user)
74 case NotificationTopicAction.event__reminder:
75 return _event__reminder(data, user)
76 case NotificationTopicAction.event__cancel: 76 ↛ 77line 76 didn't jump to line 77 because the pattern on line 76 never matched
77 return _event__cancel(data, user)
78 case NotificationTopicAction.event__delete: 78 ↛ 79line 78 didn't jump to line 79 because the pattern on line 78 never matched
79 return _event__delete(data, user)
80 case NotificationTopicAction.friend_request__create:
81 return _friend_request__create(data)
82 case NotificationTopicAction.friend_request__accept:
83 return _friend_request__accept(data)
84 case NotificationTopicAction.gender__change:
85 return _gender__change(data)
86 case NotificationTopicAction.general__new_blog_post:
87 return _general__new_blog_post(data)
88 case NotificationTopicAction.host_request__create:
89 return _host_request__create(data, user)
90 case NotificationTopicAction.host_request__message:
91 return _host_request__message(data, user)
92 case NotificationTopicAction.host_request__missed_messages: 92 ↛ 93line 92 didn't jump to line 93 because the pattern on line 92 never matched
93 return _host_request__missed_messages(data)
94 case NotificationTopicAction.host_request__reminder:
95 return _host_request__reminder(data)
96 case NotificationTopicAction.host_request__accept:
97 return _host_request__accept(data)
98 case NotificationTopicAction.host_request__reject: 98 ↛ 99line 98 didn't jump to line 99 because the pattern on line 98 never matched
99 return _host_request__reject(data)
100 case NotificationTopicAction.host_request__cancel: 100 ↛ 101line 100 didn't jump to line 101 because the pattern on line 100 never matched
101 return _host_request__cancel(data)
102 case NotificationTopicAction.host_request__confirm:
103 return _host_request__confirm(data)
104 case NotificationTopicAction.modnote__create:
105 return _modnote__create()
106 case NotificationTopicAction.onboarding__reminder:
107 return _onboarding__reminder(notification.key, user)
108 case NotificationTopicAction.password__change:
109 return _password__change()
110 case NotificationTopicAction.password_reset__start:
111 return _password_reset__start(data)
112 case NotificationTopicAction.password_reset__complete:
113 return _password_reset__complete()
114 case NotificationTopicAction.phone_number__change:
115 return _phone_number__change(data)
116 case NotificationTopicAction.phone_number__verify:
117 return _phone_number__verify(data)
118 case NotificationTopicAction.postal_verification__postcard_sent:
119 return _postal_verification__postcard_sent(data)
120 case NotificationTopicAction.postal_verification__success: 120 ↛ 121line 120 didn't jump to line 121 because the pattern on line 120 never matched
121 return _postal_verification__success()
122 case NotificationTopicAction.postal_verification__failed: 122 ↛ 123line 122 didn't jump to line 123 because the pattern on line 122 never matched
123 return _postal_verification__failed(data)
124 case NotificationTopicAction.reference__receive_friend:
125 return _reference__receive_friend(data)
126 case NotificationTopicAction.reference__receive_hosted: 126 ↛ 127line 126 didn't jump to line 127 because the pattern on line 126 never matched
127 return _reference__receive_hosted(data)
128 case NotificationTopicAction.reference__receive_surfed:
129 return _reference__receive_surfed(data)
130 case NotificationTopicAction.reference__reminder_hosted:
131 return _reference__reminder_hosted(data)
132 case NotificationTopicAction.reference__reminder_surfed:
133 return _reference__reminder_surfed(data)
134 case NotificationTopicAction.thread__reply:
135 return _thread__reply(data)
136 case NotificationTopicAction.verification__sv_success:
137 return _verification__sv_success()
138 case NotificationTopicAction.verification__sv_fail: 138 ↛ 140line 138 didn't jump to line 140 because the pattern on line 138 always matched
139 return _verification__sv_fail(data)
140 case _:
141 # Enables mypy's exhaustiveness checking for the cases above.
142 assert_never(notification.topic_action)
145def _account_deletion__start(data: notification_data_pb2.AccountDeletionStart) -> PushNotificationContent:
146 return PushNotificationContent(
147 title="Account deletion initiated",
148 body="Someone initiated the deletion of your Couchers.org account. To delete your account, please follow the link in the email we sent you.",
149 )
152def _account_deletion__complete(data: notification_data_pb2.AccountDeletionComplete) -> PushNotificationContent:
153 return PushNotificationContent(
154 title="Your Couchers.org account has been deleted",
155 body=f"You can still undo this by following the link we emailed to you within {data.undelete_days} days.",
156 )
159def _account_deletion__recovered() -> PushNotificationContent:
160 return PushNotificationContent(
161 title="Your Couchers.org account has been recovered!",
162 body="We have recovered your Couchers.org account as per your request! Welcome back!",
163 )
166def _activeness__probe(data: notification_data_pb2.ActivenessProbe) -> PushNotificationContent:
167 return PushNotificationContent(
168 title="Are you still open to hosting on Couchers.org?",
169 body="Please log in to confirm your hosting status.",
170 )
173def _api_key__create(data: notification_data_pb2.ApiKeyCreate) -> PushNotificationContent:
174 return PushNotificationContent(
175 title="An API key was created for your account",
176 body="Details were sent to you via email.",
177 action_url=urls.app_link(),
178 )
181def _badge__add(data: notification_data_pb2.BadgeAdd) -> PushNotificationContent:
182 return PushNotificationContent(
183 title=f"The {data.badge_name} badge was added to your profile",
184 body="Check out your profile to see the new badge!",
185 action_url=urls.profile_link(),
186 )
189def _badge__remove(data: notification_data_pb2.BadgeRemove) -> PushNotificationContent:
190 return PushNotificationContent(
191 title=f"The {data.badge_name} badge was removed from your profile",
192 body="You can see all your badges on your profile.",
193 action_url=urls.profile_link(),
194 )
197def _birthdate__change(data: notification_data_pb2.BirthdateChange, user: User) -> PushNotificationContent:
198 return PushNotificationContent(
199 title="Your date of birth was changed",
200 body=f"Your date of birth on Couchers.org was changed to {v2date(data.birthdate, user)} by an admin.",
201 action_url=urls.account_settings_link(),
202 )
205def _chat__message(data: notification_data_pb2.ChatMessage) -> PushNotificationContent:
206 return PushNotificationContent(
207 title=data.message,
208 body=data.text,
209 icon_url=v2avatar(data.author),
210 action_url=urls.chat_link(chat_id=data.group_chat_id),
211 )
214def _chat__missed_messages() -> PushNotificationContent:
215 return PushNotificationContent(
216 title="You have unseen messages on Couchers.org",
217 body="Please check out any messages you missed.",
218 action_url=urls.messages_link(),
219 )
222def _donation__received(data: notification_data_pb2.DonationReceived) -> PushNotificationContent:
223 return PushNotificationContent(
224 title="Thank you for your donation to Couchers.org!",
225 body=f"Thank you so much for your donation of ${data.amount} to Couchers.org.",
226 action_url=data.receipt_url,
227 )
230def _discussion__create(data: notification_data_pb2.DiscussionCreate) -> PushNotificationContent:
231 return PushNotificationContent(
232 title=data.discussion.title,
233 body=f"{data.author.name} created a discussion in {data.discussion.owner_title}: {data.discussion.title}\n\n{data.discussion.content}",
234 icon_url=v2avatar(data.author),
235 action_url=urls.discussion_link(discussion_id=data.discussion.discussion_id, slug=data.discussion.slug),
236 )
239def _discussion__comment(data: notification_data_pb2.DiscussionComment) -> PushNotificationContent:
240 return PushNotificationContent(
241 title=data.discussion.title,
242 body=f"{data.author.name} commented:\n\n{data.reply.content}",
243 icon_url=v2avatar(data.author),
244 action_url=urls.discussion_link(discussion_id=data.discussion.discussion_id, slug=data.discussion.slug),
245 )
248def _email_address__change(data: notification_data_pb2.EmailAddressChange) -> PushNotificationContent:
249 return PushNotificationContent(
250 title="An email change was initiated on your account",
251 body=f"An email change to the email {data.new_email} was initiated on your account.",
252 action_url=urls.account_settings_link(),
253 )
256def _email_address__verify() -> PushNotificationContent:
257 return PushNotificationContent(
258 title="Email change completed",
259 body="Your new email address has been verified.",
260 action_url=urls.account_settings_link(),
261 )
264def _get_event_time_display(event: events_pb2.Event, user: User) -> str:
265 return f"{v2timestamp(event.start_time, user)} - {v2timestamp(event.end_time, user)}"
268def _event__create_any(data: notification_data_pb2.EventCreate, user: User) -> PushNotificationContent:
269 time_display = _get_event_time_display(data.event, user)
270 return PushNotificationContent(
271 title=f'{data.inviting_user.name} created an event called "{data.event.title}"',
272 body=f"{time_display}\nCreated by {data.inviting_user.name}\n\n{data.event.content}",
273 icon_url=v2avatar(data.inviting_user),
274 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
275 )
278def _event__create_approved(data: notification_data_pb2.EventCreate, user: User) -> PushNotificationContent:
279 time_display = _get_event_time_display(data.event, user)
280 return PushNotificationContent(
281 title=f'{data.inviting_user.name} invited you to "{data.event.title}"',
282 body=f"{time_display}\nInvited by {data.inviting_user.name}\n\n{data.event.content}",
283 icon_url=v2avatar(data.inviting_user),
284 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
285 )
288def _event__update(data: notification_data_pb2.EventUpdate, user: User) -> PushNotificationContent:
289 time_display = _get_event_time_display(data.event, user)
290 updated_text = ", ".join(data.updated_items)
291 return PushNotificationContent(
292 title=f'{data.updating_user.name} updated "{data.event.title}"',
293 body=f"{time_display}\n{data.updating_user.name} updated: {updated_text}\n\n{data.event.content}",
294 icon_url=v2avatar(data.updating_user),
295 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
296 )
299def _event__invite_organizer(data: notification_data_pb2.EventInviteOrganizer, user: User) -> PushNotificationContent:
300 time_display = _get_event_time_display(data.event, user)
301 return PushNotificationContent(
302 title=f'{data.inviting_user.name} invited you to co-organize "{data.event.title}"',
303 body=f"{time_display}\nInvited to co-organize by {data.inviting_user.name}\n\n{data.event.content}",
304 icon_url=v2avatar(data.inviting_user),
305 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
306 )
309def _event__comment(data: notification_data_pb2.EventComment, user: User) -> PushNotificationContent:
310 time_display = _get_event_time_display(data.event, user)
311 return PushNotificationContent(
312 title=f'{data.author.name} commented on "{data.event.title}"',
313 body=f"{time_display}\n{data.author.name} commented:\n\n{data.reply.content}",
314 icon_url=v2avatar(data.author),
315 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
316 )
319def _event__reminder(data: notification_data_pb2.EventReminder, user: User) -> PushNotificationContent:
320 time_display = _get_event_time_display(data.event, user)
321 return PushNotificationContent(
322 title=f'"{data.event.title}" starts soon',
323 body=f"Don't forget your upcoming event on Couchers.org\n{time_display}\n{data.event.content}",
324 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
325 )
328def _event__cancel(data: notification_data_pb2.EventCancel, user: User) -> PushNotificationContent:
329 time_display = _get_event_time_display(data.event, user)
330 return PushNotificationContent(
331 title=f'{data.cancelling_user.name} cancelled "{data.event.title}"',
332 body=f"{time_display}\nThe event has been cancelled by {data.cancelling_user.name}.\n\n{data.event.content}",
333 icon_url=v2avatar(data.cancelling_user),
334 action_url=urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug),
335 )
338def _event__delete(data: notification_data_pb2.EventDelete, user: User) -> PushNotificationContent:
339 time_display = _get_event_time_display(data.event, user)
340 return PushNotificationContent(
341 title=f'A moderator deleted "{data.event.title}"',
342 body=f"{time_display}\nThe event has been deleted by the moderators.",
343 )
346def _friend_request__create(data: notification_data_pb2.FriendRequestCreate) -> PushNotificationContent:
347 return PushNotificationContent(
348 title=f"{data.other_user.name} wants to be your friend",
349 body=f"You've received a friend request from {data.other_user.name}",
350 icon_url=v2avatar(data.other_user),
351 action_url=urls.friend_requests_link(),
352 )
355def _friend_request__accept(data: notification_data_pb2.FriendRequestAccept) -> PushNotificationContent:
356 return PushNotificationContent(
357 title=f"{data.other_user.name} accepted your friend request!",
358 body=f"{v2esc(data.other_user.name)} has accepted your friend request",
359 icon_url=v2avatar(data.other_user),
360 action_url=urls.user_link(username=data.other_user.username),
361 )
364def _gender__change(data: notification_data_pb2.GenderChange) -> PushNotificationContent:
365 return PushNotificationContent(
366 title="Your gender was changed",
367 body=f"Your gender on Couchers.org was changed to {data.gender} by an admin.",
368 action_url=urls.account_settings_link(),
369 )
372def _general__new_blog_post(data: notification_data_pb2.GeneralNewBlogPost) -> PushNotificationContent:
373 return PushNotificationContent(
374 title=f"New blog post: {data.title}",
375 body=data.blurb,
376 action_url=data.url,
377 )
380def _host_request__create(data: notification_data_pb2.HostRequestCreate, user: User) -> PushNotificationContent:
381 return PushNotificationContent(
382 title=f"{data.surfer.name} sent you a host request",
383 body=f"Dates: {v2date(data.host_request.from_date, user)} to {v2date(data.host_request.to_date, user)}.\n\n{data.text}",
384 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
385 icon_url=v2avatar(data.surfer),
386 )
389def _host_request__message(data: notification_data_pb2.HostRequestMessage, user: User) -> PushNotificationContent:
390 if data.am_host: 390 ↛ 393line 390 didn't jump to line 393 because the condition on line 390 was always true
391 title = f"{data.user.name} sent you a message in their host request"
392 else:
393 title = f"{data.user.name} sent you a message in your host request"
394 return PushNotificationContent(
395 title=title,
396 body=f"Dates: {v2date(data.host_request.from_date, user)} to {v2date(data.host_request.to_date, user)}.\n\n{data.text}",
397 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
398 icon_url=v2avatar(data.user),
399 )
402def _host_request__missed_messages(data: notification_data_pb2.HostRequestMissedMessages) -> PushNotificationContent:
403 their_your = "their" if data.am_host else "your"
404 return PushNotificationContent(
405 title=f"{data.user.name} sent you message(s) in {their_your} host request",
406 body="Check the app for more info.",
407 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
408 icon_url=v2avatar(data.user),
409 )
412def _host_request__reminder(data: notification_data_pb2.HostRequestReminder) -> PushNotificationContent:
413 return PushNotificationContent(
414 title=f"You have a pending host request from {data.surfer.name}!",
415 body="Please respond to the request!",
416 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
417 icon_url=v2avatar(data.surfer),
418 )
421def _host_request__accept(data: notification_data_pb2.HostRequestAccept) -> PushNotificationContent:
422 return PushNotificationContent(
423 title=f"{data.host.name} accepted your host request",
424 body="Check the app for more info.",
425 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
426 icon_url=v2avatar(data.host),
427 )
430def _host_request__reject(data: notification_data_pb2.HostRequestReject) -> PushNotificationContent:
431 return PushNotificationContent(
432 title=f"{data.host.name} rejected your host request",
433 body="Check the app for more info.",
434 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
435 icon_url=v2avatar(data.host),
436 )
439def _host_request__cancel(data: notification_data_pb2.HostRequestCancel) -> PushNotificationContent:
440 return PushNotificationContent(
441 title=f"{data.surfer.name} cancelled their host request",
442 body="Check the app for more info.",
443 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
444 icon_url=v2avatar(data.surfer),
445 )
448def _host_request__confirm(data: notification_data_pb2.HostRequestConfirm) -> PushNotificationContent:
449 return PushNotificationContent(
450 title=f"{data.surfer.name} confirmed their host request",
451 body="Check the app for more info.",
452 action_url=urls.host_request(host_request_id=data.host_request.host_request_id),
453 icon_url=v2avatar(data.surfer),
454 )
457def _modnote__create() -> PushNotificationContent:
458 return PushNotificationContent(
459 title="You received a mod note",
460 body="You need to read and acknowledge the note before continuing to use the platform.",
461 )
464def _onboarding__reminder(key: str, user: User) -> PushNotificationContent:
465 if key == "1":
466 return PushNotificationContent(
467 title="Welcome to Couchers.org and the future of couch surfing",
468 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!",
469 action_url=urls.edit_profile_link(),
470 )
471 elif key == "2":
472 return PushNotificationContent(
473 title="Please complete your profile on Couchers.org!",
474 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.",
475 action_url=urls.edit_profile_link(),
476 )
477 else:
478 raise NotImplementedError(f"Unknown onboarding reminder key: {key}")
481def _password__change() -> PushNotificationContent:
482 return PushNotificationContent(
483 title="Your password was changed",
484 body="Your login password for Couchers.org was changed.",
485 action_url=urls.account_settings_link(),
486 )
489def _password_reset__start(data: notification_data_pb2.PasswordResetStart) -> PushNotificationContent:
490 return PushNotificationContent(
491 title="A password reset was initiated on your account",
492 body="Someone initiated a password change on your account.",
493 action_url=urls.account_settings_link(),
494 )
497def _password_reset__complete() -> PushNotificationContent:
498 return PushNotificationContent(
499 title="Your password was successfully reset",
500 body="Your password on Couchers.org was changed. If that was you, then no further action is needed.",
501 action_url=urls.account_settings_link(),
502 )
505def _phone_number__change(data: notification_data_pb2.PhoneNumberChange) -> PushNotificationContent:
506 return PushNotificationContent(
507 title="Phone verification started",
508 body=f"You started phone number verification with the number {v2phone(data.phone)}.",
509 action_url=urls.feature_preview_link(),
510 )
513def _phone_number__verify(data: notification_data_pb2.PhoneNumberVerify) -> PushNotificationContent:
514 return PushNotificationContent(
515 title="Phone successfully verified",
516 body=f"Your phone was successfully verified as {v2phone(data.phone)} on Couchers.org.",
517 action_url=urls.feature_preview_link(),
518 )
521def _postal_verification__postcard_sent(
522 data: notification_data_pb2.PostalVerificationPostcardSent,
523) -> PushNotificationContent:
524 return PushNotificationContent(
525 title="Your verification postcard is on its way",
526 body=f"Postcard sent to {data.city}, {data.country}. Expect it within 1-3 weeks.",
527 action_url=urls.account_settings_link(),
528 )
531def _postal_verification__success() -> PushNotificationContent:
532 return PushNotificationContent(
533 title="Postal Verification succeeded",
534 body="You have been verified with Postal Verification! Your address has been confirmed.",
535 action_url=urls.account_settings_link(),
536 )
539def _postal_verification__failed(data: notification_data_pb2.PostalVerificationFailed) -> PushNotificationContent:
540 if data.reason == notification_data_pb2.POSTAL_VERIFICATION_FAIL_REASON_CODE_EXPIRED:
541 reason_message = "Your verification code has expired. Codes are valid for 90 days after the postcard is sent. You can start a new verification attempt."
542 elif data.reason == notification_data_pb2.POSTAL_VERIFICATION_FAIL_REASON_TOO_MANY_ATTEMPTS:
543 reason_message = "Too many incorrect code attempts. You can start a new verification attempt."
544 else:
545 reason_message = "Your postal verification attempt has failed. You can start a new verification attempt."
546 return PushNotificationContent(
547 title="Postal Verification failed",
548 body=reason_message,
549 action_url=urls.account_settings_link(),
550 )
553def _reference__receive_friend(data: notification_data_pb2.ReferenceReceiveFriend) -> PushNotificationContent:
554 return PushNotificationContent(
555 title=f"You've received a friend reference from {data.from_user.name}!",
556 body=data.text,
557 icon_url=v2avatar(data.from_user),
558 action_url=urls.profile_references_link(),
559 )
562def _reference__receive(
563 data: notification_data_pb2.ReferenceReceiveHostRequest, reference_type: str
564) -> PushNotificationContent:
565 if data.text: 565 ↛ 566line 565 didn't jump to line 566 because the condition on line 565 was never true
566 body = v2esc(data.text)
567 action_url = urls.profile_references_link()
568 else:
569 body = (
570 "Please go and write a reference for them too. It's a nice gesture and helps us build a community together!"
571 )
572 action_url = urls.leave_reference_link(
573 reference_type=reference_type,
574 to_user_id=data.from_user.user_id,
575 host_request_id=str(data.host_request_id),
576 )
577 return PushNotificationContent(
578 title=f"You've received a reference from {data.from_user.name}!",
579 body=body,
580 icon_url=v2avatar(data.from_user),
581 action_url=action_url,
582 )
585def _reference__receive_hosted(data: notification_data_pb2.ReferenceReceiveHostRequest) -> PushNotificationContent:
586 # I surfed with them if I received a "hosted" request
587 return _reference__receive(data, reference_type="surfed")
590def _reference__receive_surfed(data: notification_data_pb2.ReferenceReceiveHostRequest) -> PushNotificationContent:
591 return _reference__receive(data, reference_type="hosted")
594def _reference__reminder(data: notification_data_pb2.ReferenceReminder, reference_type: str) -> PushNotificationContent:
595 leave_reference_link = urls.leave_reference_link(
596 reference_type=reference_type,
597 to_user_id=data.other_user.user_id,
598 host_request_id=str(data.host_request_id),
599 )
600 return PushNotificationContent(
601 title=f"You have {data.days_left} days to write a reference for {data.other_user.name}!",
602 body="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.",
603 icon_url=v2avatar(data.other_user),
604 action_url=leave_reference_link,
605 )
608def _reference__reminder_surfed(data: notification_data_pb2.ReferenceReminder) -> PushNotificationContent:
609 # I surfed with them if I get a surfed reminder
610 return _reference__reminder(data, reference_type="surfed")
613def _reference__reminder_hosted(data: notification_data_pb2.ReferenceReminder) -> PushNotificationContent:
614 return _reference__reminder(data, reference_type="hosted")
617def _thread__reply(data: notification_data_pb2.ThreadReply) -> PushNotificationContent:
618 parent = data.WhichOneof("reply_parent")
619 if parent == "event":
620 title = data.event.title
621 view_link = urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug)
622 elif parent == "discussion": 622 ↛ 626line 622 didn't jump to line 626 because the condition on line 622 was always true
623 title = data.discussion.title
624 view_link = urls.discussion_link(discussion_id=data.discussion.discussion_id, slug=data.discussion.slug)
625 else:
626 raise Exception("Can only do replies to events and discussions")
628 return PushNotificationContent(
629 title=title,
630 body=f"{data.author.name} replied:\n\n{data.reply.content}",
631 icon_url=v2avatar(data.author),
632 action_url=view_link,
633 )
636def _verification__sv_success() -> PushNotificationContent:
637 return PushNotificationContent(
638 title="Strong Verification succeeded",
639 body="You have been verified with Strong Verification! You will now see a tick next to your name on the platform.",
640 action_url=urls.account_settings_link(),
641 )
644def _verification__sv_fail(data: notification_data_pb2.VerificationSVFail) -> PushNotificationContent:
645 if data.reason == notification_data_pb2.SV_FAIL_REASON_WRONG_BIRTHDATE_OR_GENDER: 645 ↛ 646line 645 didn't jump to line 646 because the condition on line 645 was never true
646 reason_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."
647 elif data.reason == notification_data_pb2.SV_FAIL_REASON_NOT_A_PASSPORT:
648 reason_message = "You tried to verify with a document that is not a passport. You can only use a passport for Strong Verification."
649 elif data.reason == notification_data_pb2.SV_FAIL_REASON_DUPLICATE: 649 ↛ 652line 649 didn't jump to line 652 because the condition on line 649 was always true
650 reason_message = "You tried to verify with a passport that has already been used for verification. Please use another passport."
651 else:
652 raise Exception("Shouldn't get here")
653 return PushNotificationContent(
654 title="Strong Verification failed",
655 body=reason_message,
656 action_url=urls.account_settings_link(),
657 )