notify_zulip.rs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. use crate::{
  2. config::NotifyZulipConfig,
  3. github::{IssuesAction, IssuesEvent},
  4. handlers::Context,
  5. };
  6. pub(super) struct NotifyZulipInput {
  7. notification_type: NotificationType,
  8. }
  9. pub(super) enum NotificationType {
  10. Labeled,
  11. Unlabeled,
  12. }
  13. pub(super) fn parse_input(
  14. _ctx: &Context,
  15. event: &IssuesEvent,
  16. config: Option<&NotifyZulipConfig>,
  17. ) -> Result<Option<NotifyZulipInput>, String> {
  18. if let IssuesAction::Labeled | IssuesAction::Unlabeled = event.action {
  19. let applied_label = &event.label.as_ref().expect("label").name;
  20. if let Some(config) = config.and_then(|c| c.labels.get(applied_label)) {
  21. for label in &config.required_labels {
  22. let pattern = match glob::Pattern::new(label) {
  23. Ok(pattern) => pattern,
  24. Err(err) => {
  25. log::error!("Invalid glob pattern: {}", err);
  26. continue;
  27. }
  28. };
  29. if !event
  30. .issue
  31. .labels()
  32. .iter()
  33. .any(|l| pattern.matches(&l.name))
  34. {
  35. // Issue misses a required label, ignore this event
  36. return Ok(None);
  37. }
  38. }
  39. if event.action == IssuesAction::Labeled && config.message_on_add.is_some() {
  40. return Ok(Some(NotifyZulipInput {
  41. notification_type: NotificationType::Labeled,
  42. }));
  43. } else if config.message_on_remove.is_some() {
  44. return Ok(Some(NotifyZulipInput {
  45. notification_type: NotificationType::Unlabeled,
  46. }));
  47. }
  48. }
  49. }
  50. Ok(None)
  51. }
  52. pub(super) async fn handle_input<'a>(
  53. ctx: &Context,
  54. config: &NotifyZulipConfig,
  55. event: &IssuesEvent,
  56. input: NotifyZulipInput,
  57. ) -> anyhow::Result<()> {
  58. let config = config
  59. .labels
  60. .get(&event.label.as_ref().unwrap().name)
  61. .unwrap();
  62. let mut topic = config.topic.clone();
  63. topic = topic.replace("{number}", &event.issue.number.to_string());
  64. topic = topic.replace("{title}", &event.issue.title);
  65. // Truncate to 60 chars (a Zulip limitation)
  66. let mut chars = topic.char_indices().skip(59);
  67. if let (Some((len, _)), Some(_)) = (chars.next(), chars.next()) {
  68. topic.truncate(len);
  69. topic.push('…');
  70. }
  71. let mut msg = match input.notification_type {
  72. NotificationType::Labeled => config.message_on_add.as_ref().unwrap().clone(),
  73. NotificationType::Unlabeled => config.message_on_remove.as_ref().unwrap().clone(),
  74. };
  75. msg = msg.replace("{number}", &event.issue.number.to_string());
  76. msg = msg.replace("{title}", &event.issue.title);
  77. let zulip_req = crate::zulip::MessageApiRequest {
  78. recipient: crate::zulip::Recipient::Stream {
  79. id: config.zulip_stream,
  80. topic: &topic,
  81. },
  82. content: &msg,
  83. };
  84. zulip_req.send(&ctx.github.raw()).await?;
  85. Ok(())
  86. }