interactions.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. use crate::github::{GithubClient, Issue};
  2. use std::fmt::Write;
  3. pub struct ErrorComment<'a> {
  4. issue: &'a Issue,
  5. message: String,
  6. }
  7. impl<'a> ErrorComment<'a> {
  8. pub fn new<T>(issue: &'a Issue, message: T) -> ErrorComment<'a>
  9. where
  10. T: Into<String>,
  11. {
  12. ErrorComment {
  13. issue,
  14. message: message.into(),
  15. }
  16. }
  17. pub async fn post(&self, client: &GithubClient) -> anyhow::Result<()> {
  18. let mut body = String::new();
  19. writeln!(body, "**Error**: {}", self.message)?;
  20. writeln!(body)?;
  21. writeln!(
  22. body,
  23. "Please let **`@rust-lang/release`** know if you're having trouble with this bot."
  24. )?;
  25. self.issue.post_comment(client, &body).await
  26. }
  27. }
  28. pub struct PingComment<'a> {
  29. issue: &'a Issue,
  30. users: &'a [&'a str],
  31. }
  32. impl<'a> PingComment<'a> {
  33. pub fn new(issue: &'a Issue, users: &'a [&str]) -> PingComment<'a> {
  34. PingComment { issue, users }
  35. }
  36. pub async fn post(&self, client: &GithubClient) -> anyhow::Result<()> {
  37. let mut body = String::new();
  38. for user in self.users {
  39. write!(body, "@{} ", user)?;
  40. }
  41. self.issue.post_comment(client, &body).await
  42. }
  43. }
  44. pub struct EditIssueBody<'a> {
  45. issue: &'a Issue,
  46. id: &'static str,
  47. }
  48. static START_BOT: &str = "<!-- TRIAGEBOT_START -->\n\n";
  49. static END_BOT: &str = "<!-- TRIAGEBOT_END -->";
  50. impl<'a> EditIssueBody<'a> {
  51. pub fn new(issue: &'a Issue, id: &'static str) -> EditIssueBody<'a> {
  52. EditIssueBody { issue, id }
  53. }
  54. fn get_current(&self) -> Option<&str> {
  55. let start_section = self.start_section();
  56. let end_section = self.end_section();
  57. if self.issue.body.contains(START_BOT) {
  58. if self.issue.body.contains(&start_section) {
  59. let start_idx = self.issue.body.find(&start_section).unwrap();
  60. let end_idx = self.issue.body.find(&end_section).unwrap();
  61. Some(&self.issue.body[start_idx..(end_idx + end_section.len())])
  62. } else {
  63. None
  64. }
  65. } else {
  66. None
  67. }
  68. }
  69. pub fn current_data<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
  70. let all = self.get_current()?;
  71. let start = self.data_section_start();
  72. let end = self.data_section_end();
  73. let start_idx = all.find(&start).unwrap();
  74. let end_idx = all.find(&end).unwrap();
  75. let text = &all[(start_idx + start.len())..end_idx];
  76. Some(serde_json::from_str(text).unwrap_or_else(|e| {
  77. panic!("deserializing data {:?} failed: {:?}", text, e);
  78. }))
  79. }
  80. fn start_section(&self) -> String {
  81. format!("<!-- TRIAGEBOT_{}_START -->\n", self.id)
  82. }
  83. fn end_section(&self) -> String {
  84. format!("\n<!-- TRIAGEBOT_{}_END -->\n", self.id)
  85. }
  86. fn data_section_start(&self) -> String {
  87. format!("\n<!-- TRIAGEBOT_{}_DATA_START$$", self.id)
  88. }
  89. fn data_section_end(&self) -> String {
  90. format!("$$TRIAGEBOT_{}_DATA_END -->\n", self.id)
  91. }
  92. fn data_section<T>(&self, data: T) -> String
  93. where
  94. T: serde::Serialize,
  95. {
  96. format!(
  97. "{}{}{}",
  98. self.data_section_start(),
  99. serde_json::to_string(&data).unwrap(),
  100. self.data_section_end()
  101. )
  102. }
  103. pub async fn apply<T>(&self, client: &GithubClient, text: String, data: T) -> anyhow::Result<()>
  104. where
  105. T: serde::Serialize,
  106. {
  107. let mut current_body = self.issue.body.clone();
  108. let start_section = self.start_section();
  109. let end_section = self.end_section();
  110. let bot_section = format!(
  111. "{}{}{}{}",
  112. start_section,
  113. text,
  114. self.data_section(data),
  115. end_section
  116. );
  117. let empty_bot_section = format!("{}{}", start_section, end_section);
  118. let all_new = format!("\n\n{}{}{}", START_BOT, bot_section, END_BOT);
  119. if current_body.contains(START_BOT) {
  120. if current_body.contains(&start_section) {
  121. let start_idx = current_body.find(&start_section).unwrap();
  122. let end_idx = current_body.find(&end_section).unwrap();
  123. current_body.replace_range(start_idx..(end_idx + end_section.len()), &bot_section);
  124. if current_body.contains(&all_new) && bot_section == empty_bot_section {
  125. let start_idx = current_body.find(&all_new).unwrap();
  126. let end_idx = start_idx + all_new.len();
  127. current_body.replace_range(start_idx..end_idx, "");
  128. }
  129. self.issue.edit_body(&client, &current_body).await?;
  130. } else {
  131. let end_idx = current_body.find(&END_BOT).unwrap();
  132. current_body.insert_str(end_idx, &bot_section);
  133. self.issue.edit_body(&client, &current_body).await?;
  134. }
  135. } else {
  136. let new_body = format!("{}{}", current_body, all_new);
  137. self.issue.edit_body(&client, &new_body).await?;
  138. }
  139. Ok(())
  140. }
  141. }