notify_zulip.rs 3.6 KB

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