Coverage for src/couchers/notifications/render.py: 84%

223 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-03-11 15:27 +0000

1import logging 

2from dataclasses import dataclass 

3 

4from couchers import urls 

5from couchers.notifications.unsubscribe import generate_unsub_topic_action 

6from couchers.templates.v2 import v2avatar, v2date, v2esc, v2phone, v2timestamp 

7from proto import notification_data_pb2 

8 

9logger = logging.getLogger(__name__) 

10 

11 

12@dataclass(kw_only=True) 

13class RenderedNotification: 

14 # whether the notification is critical and cannot be turned off 

15 is_critical: bool = False 

16 # email subject 

17 email_subject: str 

18 # shows up when listing emails in many clients 

19 email_preview: str 

20 # corresponds to .mjml + .txt file in templates/v2 

21 email_template_name: str 

22 # other template args 

23 email_template_args: dict 

24 # the link label on the topic_action unsubscribe link 

25 email_topic_action_unsubscribe_text: str = None 

26 # the link label on the topic_key unsubscribe link 

27 email_topic_key_unsubscribe_text: str = None 

28 # url to unsubscribe with one click 

29 email_list_unsubscribe_url: str = None 

30 # push notification title 

31 push_title: str 

32 # push notification content 

33 push_body: str 

34 # url to an icon for push notifications 

35 push_icon: str 

36 # url to where clicking on the notification should take you 

37 push_url: str 

38 

39 

40def render_notification(user, notification) -> RenderedNotification: 

41 data = notification.topic_action.data_type.FromString(notification.data) 

42 if notification.topic == "host_request": 

43 view_link = urls.host_request(host_request_id=data.host_request.host_request_id) 

44 if notification.action == "missed_messages": 

45 their_your = "their" if data.am_host else "your" 

46 other = data.user 

47 # "declined your host request", or similar 

48 message = f"{other.name} sent you message(s) in {their_your} host request" 

49 return RenderedNotification( 

50 email_subject=message, 

51 email_preview=message, 

52 email_template_name="host_request__plain", 

53 email_template_args={ 

54 "view_link": view_link, 

55 "host_request": data.host_request, 

56 "message": message, 

57 "other": other, 

58 }, 

59 email_topic_action_unsubscribe_text="missed messages in host requests", 

60 push_title=message, 

61 push_body="Check the app for more info.", 

62 push_icon=v2avatar(other), 

63 push_url=view_link, 

64 ) 

65 elif notification.action in ["create", "message"]: 

66 if notification.action == "create": 

67 other = data.surfer 

68 message = f"{other.name} sent you a host request" 

69 topic_action_unsub_text = "new host requests" 

70 elif notification.action == "message": 

71 other = data.user 

72 if data.am_host: 

73 message = f"{other.name} sent you a message in their host request" 

74 else: 

75 message = f"{other.name} sent you a message in your host request" 

76 topic_action_unsub_text = "messages in host request" 

77 return RenderedNotification( 

78 email_subject=message, 

79 email_preview=message, 

80 email_template_name="host_request__message", 

81 email_template_args={ 

82 "view_link": view_link, 

83 "host_request": data.host_request, 

84 "message": message, 

85 "other": other, 

86 "text": data.text, 

87 }, 

88 email_topic_action_unsubscribe_text=topic_action_unsub_text, 

89 push_title=f"{message}", 

90 push_body=f"Dates: {v2date(data.host_request.from_date, user)} to {v2date(data.host_request.to_date, user)}.\n\n{data.text}", 

91 push_icon=v2avatar(other), 

92 push_url=view_link, 

93 ) 

94 elif notification.action in ["accept", "reject", "confirm", "cancel"]: 

95 if notification.action in ["accept", "reject"]: 

96 other = data.host 

97 their_your = "your" 

98 else: 

99 other = data.surfer 

100 their_your = "their" 

101 actioned = { 

102 "accept": "accepted", 

103 "reject": "declined", 

104 "confirm": "confirmed", 

105 "cancel": "cancelled", 

106 }[notification.action] 

107 # "declined your host request", or similar 

108 message = f"{other.name} {actioned} {their_your} host request" 

109 return RenderedNotification( 

110 email_subject=message, 

111 email_preview=message, 

112 email_template_name="host_request__plain", 

113 email_template_args={ 

114 "view_link": view_link, 

115 "host_request": data.host_request, 

116 "message": message, 

117 "other": other, 

118 }, 

119 email_topic_action_unsubscribe_text=f"{actioned} host requests", 

120 push_title=message, 

121 push_body="Check the app for more info.", 

122 push_icon=v2avatar(other), 

123 push_url=view_link, 

124 ) 

125 elif notification.topic_action.display == "password:change": 

126 title = "Your password was changed" 

127 message = "Your login password for Couchers.org was changed." 

128 return RenderedNotification( 

129 is_critical=True, 

130 email_subject=title, 

131 email_preview=message, 

132 email_template_name="security", 

133 email_template_args={ 

134 "title": title, 

135 "message": message, 

136 }, 

137 push_title=title, 

138 push_body=message, 

139 push_icon=urls.icon_url(), 

140 push_url=urls.account_settings_link(), 

141 ) 

142 elif notification.topic_action.display == "password_reset:start": 

143 message = "Someone initiated a password change on your account." 

144 return RenderedNotification( 

145 is_critical=True, 

146 email_subject="Reset your Couchers.org password", 

147 email_preview=message, 

148 email_template_name="password_reset", 

149 email_template_args={ 

150 "password_reset_link": urls.password_reset_link(password_reset_token=data.password_reset_token) 

151 }, 

152 push_title="A password reset was initiated on your account", 

153 push_body=message, 

154 push_icon=urls.icon_url(), 

155 push_url=urls.account_settings_link(), 

156 ) 

157 elif notification.topic_action.display == "password_reset:complete": 

158 title = "Your password was successfully reset" 

159 message = "Your password on Couchers.org was changed. If that was you, then no further action is needed." 

160 return RenderedNotification( 

161 is_critical=True, 

162 email_subject=title, 

163 email_preview=title, 

164 email_template_name="security", 

165 email_template_args={ 

166 "title": title, 

167 "message": message, 

168 }, 

169 push_title=title, 

170 push_body=message, 

171 push_icon=urls.icon_url(), 

172 push_url=urls.account_settings_link(), 

173 ) 

174 elif notification.topic_action.display == "email_address:change": 

175 title = "An email change was initiated on your account" 

176 message = f"An email change to the email <b>{data.new_email}</b> was initiated on your account." 

177 message_plain = f"An email change to the email {data.new_email} was initiated on your account." 

178 return RenderedNotification( 

179 is_critical=True, 

180 email_subject=title, 

181 email_preview=title, 

182 email_template_name="security", 

183 email_template_args={ 

184 "title": title, 

185 "message": message, 

186 }, 

187 push_title=title, 

188 push_body=message_plain, 

189 push_icon=urls.icon_url(), 

190 push_url=urls.account_settings_link(), 

191 ) 

192 elif notification.topic_action.display == "email_address:verify": 

193 title = "Email change completed" 

194 message = "Your new email address has been verified." 

195 return RenderedNotification( 

196 is_critical=True, 

197 email_subject=title, 

198 email_preview=message, 

199 email_template_name="security", 

200 email_template_args={ 

201 "title": title, 

202 "message": message, 

203 }, 

204 push_title=title, 

205 push_body=message, 

206 push_icon=urls.icon_url(), 

207 push_url=urls.account_settings_link(), 

208 ) 

209 elif notification.topic_action.display == "phone_number:change": 

210 title = "Phone verification started" 

211 message = f"You started phone number verification with the number <b>{v2phone(data.phone)}</b>." 

212 message_plain = f"You started phone number verification with the number {v2phone(data.phone)}." 

213 return RenderedNotification( 

214 is_critical=True, 

215 email_subject=title, 

216 email_preview=message, 

217 email_template_name="security", 

218 email_template_args={ 

219 "title": title, 

220 "message": message, 

221 }, 

222 push_title=title, 

223 push_body=message_plain, 

224 push_icon=urls.icon_url(), 

225 push_url=urls.feature_preview_link(), 

226 ) 

227 elif notification.topic_action.display == "phone_number:verify": 

228 title = "Phone successfully verified" 

229 message = f"Your phone was successfully verified as <b>{v2phone(data.phone)}</b> on Couchers.org." 

230 message_plain = f"Your phone was successfully verified as {v2phone(data.phone)} on Couchers.org." 

231 return RenderedNotification( 

232 is_critical=True, 

233 email_subject=title, 

234 email_preview=message_plain, 

235 email_template_name="security", 

236 email_template_args={ 

237 "title": title, 

238 "message": message, 

239 }, 

240 push_title=title, 

241 push_body=message_plain, 

242 push_icon=urls.icon_url(), 

243 push_url=urls.feature_preview_link(), 

244 ) 

245 elif notification.topic_action.display == "gender:change": 

246 title = "Your gender was changed" 

247 message = f"Your gender on Couchers.org was changed to <b>{data.gender}</b> by an admin." 

248 message_plain = f"Your gender on Couchers.org was changed to {data.gender} by an admin." 

249 return RenderedNotification( 

250 is_critical=True, 

251 email_subject=title, 

252 email_preview=message_plain, 

253 email_template_name="security", 

254 email_template_args={ 

255 "title": title, 

256 "message": message, 

257 }, 

258 push_title=title, 

259 push_body=message_plain, 

260 push_icon=urls.icon_url(), 

261 push_url=urls.account_settings_link(), 

262 ) 

263 elif notification.topic_action.display == "birthdate:change": 

264 title = "Your date of birth was changed" 

265 message = ( 

266 f"Your date of birth on Couchers.org was changed to <b>{v2date(data.birthdate, user)}</b> by an admin." 

267 ) 

268 message_plain = f"Your date of birth on Couchers.org was changed to {v2date(data.birthdate, user)} by an admin." 

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.account_settings_link(), 

282 ) 

283 elif notification.topic_action.display == "api_key:create": 

284 return RenderedNotification( 

285 is_critical=True, 

286 email_subject="Your API key for Couchers.org", 

287 email_preview="We have issued you an API key as per your request.", 

288 email_template_name="api_key", 

289 email_template_args={ 

290 "api_key": data.api_key, 

291 "expiry": data.expiry, 

292 }, 

293 push_title="An API key was created for your account", 

294 push_body="Details were sent to you via email.", 

295 push_icon=urls.icon_url(), 

296 push_url=urls.app_link(), 

297 ) 

298 elif notification.topic_action.display in ["badge:add", "badge:remove"]: 

299 actioned = "added to" if notification.action == "add" else "removed from" 

300 title = f"The {data.badge_name} badge was {actioned} your profile" 

301 return RenderedNotification( 

302 email_subject=title, 

303 email_preview=title, 

304 email_template_name="badge", 

305 email_template_args={ 

306 "badge_name": data.badge_name, 

307 "actioned": actioned, 

308 "unsub_type": "badge additions" if notification.action == "add" else "badge removals", 

309 }, 

310 email_topic_action_unsubscribe_text="badge additions" if notification.action == "add" else "badge removals", 

311 push_title=title, 

312 push_body=( 

313 "Check out your profile to see the new badge!" 

314 if notification.action == "add" 

315 else "You can see all your badges on your profile." 

316 ), 

317 push_icon=urls.icon_url(), 

318 push_url=urls.profile_link(), 

319 email_list_unsubscribe_url=generate_unsub_topic_action(notification), 

320 ) 

321 elif notification.topic_action.display == "donation:received": 

322 title = "Thank you for your donation to Couchers.org!" 

323 message = f"Thank you so much for your donation of ${data.amount} to Couchers.org." 

324 return RenderedNotification( 

325 is_critical=True, 

326 email_subject=title, 

327 email_preview=message, 

328 email_template_name="donation_received", 

329 email_template_args={ 

330 "amount": data.amount, 

331 "receipt_url": data.receipt_url, 

332 }, 

333 push_title=title, 

334 push_body=message, 

335 push_icon=urls.icon_url(), 

336 push_url=data.receipt_url, 

337 ) 

338 elif notification.topic_action.display == "friend_request:create": 

339 other = data.other_user 

340 preview = f"You've received a friend request from {other.name}" 

341 return RenderedNotification( 

342 email_subject=f"{other.name} wants to be your friend on Couchers.org!", 

343 email_preview=preview, 

344 email_template_name="friend_request", 

345 email_template_args={ 

346 "friend_requests_link": urls.friend_requests_link(), 

347 "other": other, 

348 }, 

349 email_topic_action_unsubscribe_text="new friend requests", 

350 push_title=f"{other.name} wants to be your friend", 

351 push_body=preview, 

352 push_icon=v2avatar(other), 

353 push_url=urls.friend_requests_link(), 

354 ) 

355 elif notification.topic_action.display == "friend_request:accept": 

356 other = data.other_user 

357 title = f"{other.name} accepted your friend request!" 

358 preview = f"{v2esc(other.name)} has accepted your friend request" 

359 return RenderedNotification( 

360 email_subject=title, 

361 email_preview=preview, 

362 email_template_name="friend_request_accepted", 

363 email_template_args={ 

364 "other_user_link": urls.user_link(username=other.username), 

365 "other": other, 

366 }, 

367 email_topic_action_unsubscribe_text="accepted friend requests", 

368 push_title=title, 

369 push_body=preview, 

370 push_icon=v2avatar(other), 

371 push_url=urls.user_link(username=other.username), 

372 ) 

373 elif notification.topic_action.display == "account_deletion:start": 

374 return RenderedNotification( 

375 is_critical=True, 

376 email_subject="Confirm your Couchers.org account deletion", 

377 email_preview="Please confirm that you want to delete your Couchers.org account.", 

378 email_template_name="account_deletion_start", 

379 email_template_args={ 

380 "deletion_link": urls.delete_account_link(account_deletion_token=data.deletion_token), 

381 }, 

382 push_title="Account deletion initiated", 

383 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.", 

384 push_icon=urls.icon_url(), 

385 push_url=urls.app_link(), 

386 ) 

387 elif notification.topic_action.display == "account_deletion:complete": 

388 title = "Your Couchers.org account has been deleted" 

389 return RenderedNotification( 

390 is_critical=True, 

391 email_subject=title, 

392 email_preview="We have deleted your Couchers.org account, to undo, follow the link in this email.", 

393 email_template_name="account_deletion_complete", 

394 email_template_args={ 

395 "undelete_link": urls.recover_account_link(account_undelete_token=data.undelete_token), 

396 "days": data.undelete_days, 

397 }, 

398 push_title=title, 

399 push_body=f"You can still undo this by following the link we emailed to you within {data.undelete_days} days.", 

400 push_icon=urls.icon_url(), 

401 push_url=urls.app_link(), 

402 ) 

403 elif notification.topic_action.display == "account_deletion:recovered": 

404 title = "Your Couchers.org account has been recovered!" 

405 subtitle = "We have recovered your Couchers.org account as per your request! Welcome back!" 

406 return RenderedNotification( 

407 is_critical=True, 

408 email_subject=title, 

409 email_preview=subtitle, 

410 email_template_name="account_deletion_recovered", 

411 email_template_args={ 

412 "app_link": urls.app_link(), 

413 }, 

414 push_title=title, 

415 push_body=subtitle, 

416 push_icon=urls.icon_url(), 

417 push_url=urls.app_link(), 

418 ) 

419 elif notification.topic_action.display == "chat:message": 

420 return RenderedNotification( 

421 email_subject=data.message, 

422 email_preview="You received a message on Couchers.org!", 

423 email_template_name="chat_message", 

424 email_template_args={ 

425 "author": data.author, 

426 "message": data.message, 

427 "text": data.text, 

428 "view_link": urls.chat_link(chat_id=data.group_chat_id), 

429 }, 

430 email_topic_action_unsubscribe_text="new chat messages", 

431 email_topic_key_unsubscribe_text="this chat (mute)", 

432 push_title=data.message, 

433 push_body=data.text, 

434 push_icon=v2avatar(data.author), 

435 push_url=urls.chat_link(chat_id=data.group_chat_id), 

436 ) 

437 elif notification.topic_action.display == "chat:missed_messages": 

438 return RenderedNotification( 

439 email_subject="You have unseen messages on Couchers.org!", 

440 email_preview="You missed some messages on the platform.", 

441 email_template_name="chat_unseen_messages", 

442 email_template_args={ 

443 "items": [ 

444 { 

445 "author": item.author, 

446 "message": item.message, 

447 "text": item.text, 

448 "view_link": urls.chat_link(chat_id=item.group_chat_id), 

449 } 

450 for item in data.messages 

451 ] 

452 }, 

453 email_topic_action_unsubscribe_text="unseen chat messages", 

454 push_title="You have unseen messages on Couchers.org", 

455 push_body="Please check out any messages you missed.", 

456 push_icon=urls.icon_url(), 

457 push_url=urls.messages_link(), 

458 ) 

459 elif notification.topic == "event": 

460 event = data.event 

461 time_display = f"{v2timestamp(event.start_time, user)} - {v2timestamp(event.end_time, user)}" 

462 event_link = urls.event_link(occurrence_id=event.event_id, slug=event.slug) 

463 if notification.action in ["create_approved", "create_any"]: 

464 body = f"{time_display}\n" 

465 body += f"Invited by {data.inviting_user.name}\n\n" 

466 body += event.content 

467 community_link = ( 

468 urls.community_link(node_id=data.in_community.community_id, slug=data.in_community.slug) 

469 if data.in_community 

470 else None 

471 ) 

472 return RenderedNotification( 

473 email_subject=f'{data.inviting_user.name} invited you to "{event.title}"', 

474 email_preview="You've been invited to a new event on Couchers.org!", 

475 email_template_name="event_create", 

476 email_template_args={ 

477 "inviting_user": data.inviting_user, 

478 "time_display": time_display, 

479 "nearby": "nearby" if data.nearby else None, 

480 "community": data.in_community if data.in_community else None, 

481 "community_link": community_link, 

482 "nearby_or_community_text_plain": ( 

483 "nearby" if data.nearby else f"in the {data.in_community.name} community" 

484 ), 

485 "event": event, 

486 "view_link": event_link, 

487 }, 

488 email_topic_action_unsubscribe_text=( 

489 "new events by community members" 

490 if notification.action == "create_any" 

491 else "new events approved by moderators" 

492 ), 

493 push_title=f'{data.inviting_user.name} invited you to "{event.title}"', 

494 push_body=body, 

495 push_icon=v2avatar(data.inviting_user), 

496 push_url=event_link, 

497 ) 

498 elif notification.action == "update": 

499 updated_text = ", ".join(data.updated_items) 

500 body = f"{time_display}\n" 

501 body += f"{data.updating_user.name} updated: {updated_text}\n\n" 

502 body += event.content 

503 return RenderedNotification( 

504 email_subject=f'{data.updating_user.name} updated "{event.title}"', 

505 email_preview="An event you are subscribed to was updated.", 

506 email_template_name="event_update", 

507 email_template_args={ 

508 "updating_user": data.updating_user, 

509 "time_display": time_display, 

510 "event": event, 

511 "updated_text": updated_text, 

512 "view_link": event_link, 

513 }, 

514 email_topic_action_unsubscribe_text="event updates", 

515 push_title=f'{data.updating_user.name} updated "{event.title}"', 

516 push_body=body, 

517 push_icon=v2avatar(data.updating_user), 

518 push_url=event_link, 

519 ) 

520 elif notification.action == "cancel": 

521 body = f"{time_display}\n" 

522 body += f"The event has been cancelled by {data.cancelling_user.name}.\n\n" 

523 body += event.content 

524 return RenderedNotification( 

525 email_subject=f'{data.cancelling_user.name} cancelled "{event.title}"', 

526 email_preview="An event you are subscribed to has been cancelled.", 

527 email_template_name="event_cancel", 

528 email_template_args={ 

529 "cancelling_user": data.cancelling_user, 

530 "time_display": time_display, 

531 "event": event, 

532 "view_link": event_link, 

533 }, 

534 email_topic_action_unsubscribe_text="event cancellations", 

535 push_title=f'{data.cancelling_user.name} cancelled "{event.title}"', 

536 push_body=body, 

537 push_icon=v2avatar(data.cancelling_user), 

538 push_url=event_link, 

539 ) 

540 elif notification.action == "delete": 

541 return RenderedNotification( 

542 email_subject=f'A moderator deleted "{event.title}"', 

543 email_preview="An event you are subscribed to has been deleted.", 

544 email_template_name="event_delete", 

545 email_template_args={ 

546 "time_display": time_display, 

547 "event": event, 

548 }, 

549 email_topic_action_unsubscribe_text="event deletions", 

550 push_title=f'A moderator deleted "{event.title}"', 

551 push_body=f"{time_display}\nThe event has been deleted by the moderators.", 

552 push_icon=urls.icon_url(), 

553 push_url=urls.app_link(), 

554 ) 

555 elif notification.action == "invite_organizer": 

556 body = f"{time_display}\n" 

557 body += f"Invited to co-organize by {data.inviting_user.name}\n\n" 

558 body += event.content 

559 return RenderedNotification( 

560 email_subject=f'{data.inviting_user.name} invited you to co-organize "{event.title}"', 

561 email_preview="You were invited to co-organize an event on Couchers.org.", 

562 email_template_name="event_invite_organizer", 

563 email_template_args={ 

564 "inviting_user": data.inviting_user, 

565 "time_display": time_display, 

566 "event": event, 

567 "view_link": event_link, 

568 }, 

569 email_topic_action_unsubscribe_text="invitations to co-organize events", 

570 push_title=f'{data.inviting_user.name} invited you to co-organize "{event.title}"', 

571 push_body=body, 

572 push_icon=v2avatar(data.inviting_user), 

573 push_url=event_link, 

574 ) 

575 elif notification.action == "comment": 

576 body = f"{time_display}\n" 

577 body += f"{data.author.name} commented:\n\n" 

578 body += data.reply.content 

579 return RenderedNotification( 

580 email_subject=f'{data.author.name} commented on "{event.title}"', 

581 email_preview="Someone commented on an event you are attending.", 

582 email_template_name="event_comment", 

583 email_template_args={ 

584 "author": data.author, 

585 "time_display": time_display, 

586 "event": event, 

587 "content": data.reply.content, 

588 "view_link": event_link, 

589 }, 

590 email_topic_action_unsubscribe_text="event comments", 

591 push_title=f'{data.author.name} commented on "{event.title}"', 

592 push_body=body, 

593 push_icon=v2avatar(data.author), 

594 push_url=event_link, 

595 ) 

596 elif notification.topic == "discussion": 

597 discussion = data.discussion 

598 discussion_link = urls.discussion_link(discussion_id=discussion.discussion_id, slug=discussion.slug) 

599 if notification.action == "create": 

600 body = f"{data.author.name} created a discussion in {discussion.owner_title}: {discussion.title}\n\n" 

601 body += discussion.content 

602 return RenderedNotification( 

603 email_subject=f'{data.author.name} created a discussion: "{discussion.title}"', 

604 email_preview="Someone created a discussion in a community or group you are subscribed to.", 

605 email_template_name="discussion_create", 

606 email_template_args={ 

607 "author": data.author, 

608 "discussion": discussion, 

609 "view_link": discussion_link, 

610 }, 

611 email_topic_action_unsubscribe_text="new discussions", 

612 push_title=discussion.title, 

613 push_body=body, 

614 push_icon=v2avatar(data.author), 

615 push_url=discussion_link, 

616 ) 

617 elif notification.action == "comment": 

618 body = f"{data.author.name} commented:\n\n" 

619 body += data.reply.content 

620 return RenderedNotification( 

621 email_subject=f'{data.author.name} commented on "{discussion.title}"', 

622 email_preview="Someone commented on your discussion.", 

623 email_template_name="discussion_comment", 

624 email_template_args={ 

625 "author": data.author, 

626 "discussion": discussion, 

627 "reply": data.reply, 

628 "view_link": discussion_link, 

629 }, 

630 email_topic_action_unsubscribe_text="discussion comments", 

631 push_title=discussion.title, 

632 push_body=body, 

633 push_icon=v2avatar(data.author), 

634 push_url=discussion_link, 

635 ) 

636 elif notification.topic_action.display == "thread:reply": 

637 if data.event: 

638 title = data.event.title 

639 view_link = urls.event_link(occurrence_id=data.event.event_id, slug=data.event.slug) 

640 elif data.discussion: 

641 title = data.discussion.title 

642 view_link = urls.discussion_link(discussion_id=data.discussion.discussion_id, slug=data.discussion.slug) 

643 else: 

644 raise Exception("Can only do replies to events and discussions") 

645 

646 body = f"{data.author.name} replied:\n\n" 

647 body += data.reply.content 

648 return RenderedNotification( 

649 email_subject=f'{data.author.name} replied in "{title}"', 

650 email_preview="Someone replied on your comment.", 

651 email_template_name="comment_reply", 

652 email_template_args={ 

653 "author": data.author, 

654 "title": title, 

655 "reply": data.reply, 

656 "view_link": view_link, 

657 }, 

658 email_topic_action_unsubscribe_text="comment replies", 

659 push_title=title, 

660 push_body=body, 

661 push_icon=v2avatar(data.author), 

662 push_url=view_link, 

663 ) 

664 elif notification.topic == "reference": 

665 if notification.action == "receive_friend": 

666 title = f"You've received a friend reference from {data.from_user.name}!" 

667 return RenderedNotification( 

668 email_subject=title, 

669 email_preview=v2esc(data.text), 

670 email_template_name="friend_reference", 

671 email_template_args={ 

672 "from_user": data.from_user, 

673 "profile_references_link": urls.profile_references_link(), 

674 "text": data.text, 

675 }, 

676 email_topic_action_unsubscribe_text="new references from friends", 

677 push_title=title, 

678 push_body=data.text, 

679 push_icon=v2avatar(data.from_user), 

680 push_url=urls.profile_references_link(), 

681 ) 

682 elif notification.action in ["receive_hosted", "receive_surfed"]: 

683 title = f"You've received a reference from {data.from_user.name}!" 

684 # what was my type? i surfed with them if i received a "hosted" request 

685 surfed = notification.action == "receive_hosted" 

686 leave_reference_link = urls.leave_reference_link( 

687 reference_type="surfed" if surfed else "hosted", 

688 to_user_id=data.from_user.user_id, 

689 host_request_id=data.host_request_id, 

690 ) 

691 profile_references_link = urls.profile_references_link() 

692 if data.text: 

693 body = v2esc(data.text) 

694 push_url = profile_references_link 

695 else: 

696 body = "Please go and write a reference for them too. It's a nice gesture and helps us build a community together!" 

697 push_url = leave_reference_link 

698 return RenderedNotification( 

699 email_subject=title, 

700 email_preview=body, 

701 email_template_name="host_reference", 

702 email_template_args={ 

703 "from_user": data.from_user, 

704 "leave_reference_link": leave_reference_link, 

705 "profile_references_link": profile_references_link, 

706 "text": data.text, 

707 "both_written": True if data.text else False, 

708 "surfed": surfed, 

709 }, 

710 email_topic_action_unsubscribe_text="new references from " + ("hosts" if surfed else "surfers"), 

711 push_title=title, 

712 push_body=body, 

713 push_icon=v2avatar(data.from_user), 

714 push_url=push_url, 

715 ) 

716 elif notification.action in ["reminder_hosted", "reminder_surfed"]: 

717 # what was my type? i surfed with them if i get a surfed reminder 

718 surfed = notification.action == "reminder_surfed" 

719 leave_reference_link = urls.leave_reference_link( 

720 reference_type="surfed" if surfed else "hosted", 

721 to_user_id=data.other_user.user_id, 

722 host_request_id=data.host_request_id, 

723 ) 

724 title = f"You have {data.days_left} days to write a reference for {data.other_user.name}!" 

725 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." 

726 return RenderedNotification( 

727 email_subject=title, 

728 email_preview=preview, 

729 email_template_name="reference_reminder", 

730 email_template_args={ 

731 "other_user": data.other_user, 

732 "leave_reference_link": leave_reference_link, 

733 "days_left": str(data.days_left), 

734 "surfed": surfed, 

735 }, 

736 email_topic_action_unsubscribe_text=("surfed" if surfed else "hosted") + " reference reminders", 

737 push_title=title, 

738 push_body=preview, 

739 push_icon=v2avatar(data.other_user), 

740 push_url=leave_reference_link, 

741 ) 

742 elif notification.topic_action.display == "onboarding:reminder": 

743 if notification.key == "1": 

744 return RenderedNotification( 

745 email_subject="Welcome to Couchers.org and the future of couch surfing", 

746 email_preview="We are so excited to have you join our community!", 

747 email_template_name="onboarding1", 

748 email_template_args={ 

749 "app_link": urls.app_link(), 

750 "edit_profile_link": urls.edit_profile_link(), 

751 }, 

752 email_topic_action_unsubscribe_text="onboarding emails", 

753 push_title="Welcome to Couchers.org and the future of couch surfing", 

754 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!", 

755 push_icon=urls.icon_url(), 

756 push_url=urls.edit_profile_link(), 

757 ) 

758 elif notification.key == "2": 

759 return RenderedNotification( 

760 email_subject="Complete your profile on Couchers.org", 

761 email_preview="We would ask one big favour of you: please fill out your profile by adding a photo and some text.", 

762 email_template_name="onboarding2", 

763 email_template_args={ 

764 "edit_profile_link": urls.edit_profile_link(), 

765 }, 

766 email_topic_action_unsubscribe_text="onboarding emails", 

767 push_title="Please complete your profile on Couchers.org!", 

768 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.", 

769 push_icon=urls.icon_url(), 

770 push_url=urls.edit_profile_link(), 

771 ) 

772 elif notification.topic_action.display == "modnote:create": 

773 title = "You have received a mod note" 

774 message = "You have received an important note from the moderators. You must read and acknowledge it before continuing to use the platform." 

775 return RenderedNotification( 

776 is_critical=True, 

777 email_subject=title, 

778 email_preview=message, 

779 email_template_name="mod_note", 

780 email_template_args={"title": title}, 

781 push_title="You received a mod note", 

782 push_body="You need to read and acknowledge the note before continuing to use the platform.", 

783 push_icon=urls.icon_url(), 

784 push_url=urls.app_link(), 

785 ) 

786 elif notification.topic_action.display == "verification:sv_success": 

787 title = "Strong Verification succeeded" 

788 message = "You have been verified with Strong Verification! You will now see a tick next to your name on the platform." 

789 return RenderedNotification( 

790 is_critical=True, 

791 email_subject=title, 

792 email_preview=title, 

793 email_template_name="security", 

794 email_template_args={ 

795 "title": title, 

796 "message": message, 

797 }, 

798 push_title=title, 

799 push_body=message, 

800 push_icon=urls.icon_url(), 

801 push_url=urls.account_settings_link(), 

802 ) 

803 elif notification.topic_action.display == "verification:sv_fail": 

804 title = "Strong Verification failed" 

805 message: str 

806 if data.reason == notification_data_pb2.SV_FAIL_REASON_WRONG_BIRTHDATE_OR_GENDER: 

807 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." 

808 elif data.reason == notification_data_pb2.SV_FAIL_REASON_NOT_A_PASSPORT: 

809 message = "You tried to verify with a document that is not a passport. You can only use a passport for Strong Verification." 

810 elif data.reason == notification_data_pb2.SV_FAIL_REASON_DUPLICATE: 

811 message = "You tried to verify with a passport that has already been used for verification. Please use another passport." 

812 else: 

813 raise Exception("Shouldn't get here") 

814 return RenderedNotification( 

815 is_critical=True, 

816 email_subject=title, 

817 email_preview=title, 

818 email_template_name="security", 

819 email_template_args={ 

820 "title": title, 

821 "message": message, 

822 }, 

823 push_title=title, 

824 push_body=message, 

825 push_icon=urls.icon_url(), 

826 push_url=urls.account_settings_link(), 

827 ) 

828 else: 

829 raise NotImplementedError(f"Unknown topic-action: {notification.topic}:{notification.action}")