notify_zulip.rs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. return Ok(Some(NotifyZulipInput {
  43. notification_type: NotificationType::Labeled,
  44. }));
  45. } else if config.message_on_remove.is_some() {
  46. return Ok(Some(NotifyZulipInput {
  47. notification_type: NotificationType::Unlabeled,
  48. }));
  49. }
  50. }
  51. }
  52. }
  53. Ok(None)
  54. }
  55. fn handle_input<'b>(
  56. &self,
  57. ctx: &'b Context,
  58. config: &'b Self::Config,
  59. event: &'b Event,
  60. input: Self::Input,
  61. ) -> BoxFuture<'b, anyhow::Result<()>> {
  62. handle_input(ctx, config, event, input).boxed()
  63. }
  64. }
  65. async fn handle_input<'a>(
  66. ctx: &Context,
  67. config: &NotifyZulipConfig,
  68. event: &Event,
  69. input: NotifyZulipInput,
  70. ) -> anyhow::Result<()> {
  71. let event = match event {
  72. Event::Issue(e) => e,
  73. _ => unreachable!()
  74. };
  75. let config = config.labels.get(&event.label.as_ref().unwrap().name).unwrap();
  76. let mut topic = config.topic.clone();
  77. topic = topic.replace("{number}", &event.issue.number.to_string());
  78. topic = topic.replace("{title}", &event.issue.title);
  79. topic.truncate(60); // Zulip limitation
  80. let mut msg = match input.notification_type {
  81. NotificationType::Labeled => {
  82. config.message_on_add.as_ref().unwrap().clone()
  83. }
  84. NotificationType::Unlabeled => {
  85. config.message_on_add.as_ref().unwrap().clone()
  86. }
  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. type_: "stream",
  92. to: &config.zulip_stream.to_string(),
  93. topic: Some(&topic),
  94. content: &msg,
  95. };
  96. zulip_req.send(&ctx.github.raw()).await?;
  97. Ok(())
  98. }