baipiao_bot_rust/
lib.rs

1use async_trait::async_trait;
2
3#[derive(Debug)]
4pub struct Repository {
5    pub owner: String,
6    pub name: String,
7}
8
9#[derive(Debug)]
10pub struct IssueCreatedEvent {
11    pub id: usize,
12    pub title: String,
13    pub body: String,
14    pub user: String,
15}
16
17#[derive(Debug)]
18pub enum UpdatedPart {
19    Title { from: String, to: String },
20    Body { from: String, to: String },
21}
22
23#[derive(Debug)]
24pub struct IssueUpdatedEvent {
25    pub id: usize,
26    pub updated_part: UpdatedPart,
27    pub user: String,
28}
29
30#[derive(Debug)]
31pub struct IssueReopenedEvent {
32    pub id: usize,
33    pub title: String,
34    pub body: String,
35    pub user: String,
36}
37
38#[derive(Debug)]
39pub struct PullRequestCreatedEvent {
40    pub id: usize,
41    pub title: String,
42    pub body: String,
43    pub user: String,
44    pub from_repo: Repository,
45    pub from_ref: String,
46    pub to_ref: String,
47}
48
49#[derive(Debug)]
50pub struct PullRequestUpdatedEvent {
51    pub id: usize,
52    pub updated_part: UpdatedPart,
53    pub user: String,
54}
55
56#[derive(Debug)]
57pub enum CommentTarget {
58    Issue(usize),
59    PullRequest(usize),
60}
61
62impl CommentTarget {
63    pub fn id(&self) -> usize {
64        match self {
65            CommentTarget::Issue(x) => *x,
66            CommentTarget::PullRequest(x) => *x,
67        }
68    }
69}
70
71#[derive(Debug)]
72pub struct CommentCreatedEvent {
73    pub id: usize,
74    pub user: String,
75    pub target: CommentTarget,
76    pub body: String,
77}
78
79#[derive(Debug)]
80pub struct CommentUpdatedEvent {
81    pub id: usize,
82    pub user: String,
83    pub target: CommentTarget,
84    pub from: String,
85    pub to: String,
86}
87
88#[derive(Debug)]
89pub struct RunningInfo {
90    pub run_id: usize,
91    pub run_number: usize,
92}
93
94#[async_trait]
95pub trait Bot: Send + Sync {
96    async fn on_issue_created(
97        &self,
98        _repo: Repository,
99        _running_info: RunningInfo,
100        _event: IssueCreatedEvent,
101    ) {
102    }
103
104    async fn on_issue_updated(
105        &self,
106        _repo: Repository,
107        _running_info: RunningInfo,
108        _event: IssueUpdatedEvent,
109    ) {
110    }
111
112    async fn on_issue_closed(
113        &self,
114        _repo: Repository,
115        _running_info: RunningInfo,
116        _issue_id: usize,
117    ) {
118    }
119
120    async fn on_issue_reopened(
121        &self,
122        _repo: Repository,
123        _running_info: RunningInfo,
124        _event: IssueReopenedEvent,
125    ) {
126    }
127
128    async fn on_pull_request_created(
129        &self,
130        _repo: Repository,
131        _running_info: RunningInfo,
132        _event: PullRequestCreatedEvent,
133    ) {
134    }
135
136    async fn on_pull_request_updated(
137        &self,
138        _repo: Repository,
139        _running_info: RunningInfo,
140        _event: PullRequestUpdatedEvent,
141    ) {
142    }
143
144    async fn on_pull_request_closed(
145        &self,
146        _repo: Repository,
147        _running_info: RunningInfo,
148        _pull_request_id: usize,
149    ) {
150    }
151
152    async fn on_comment_created(
153        &self,
154        _repo: Repository,
155        _running_info: RunningInfo,
156        _event: CommentCreatedEvent,
157    ) {
158    }
159
160    async fn on_comment_updated(
161        &self,
162        _repo: Repository,
163        _running_info: RunningInfo,
164        _event: CommentUpdatedEvent,
165    ) {
166    }
167
168    async fn on_comment_deleted(
169        &self,
170        _repo: Repository,
171        _running_info: RunningInfo,
172        _comment_id: usize,
173    ) {
174    }
175}
176
177pub struct Dispatcher<T: Bot> {
178    core: T,
179}
180
181impl<T: Bot> Dispatcher<T> {
182    pub fn new(core: T) -> Self {
183        Dispatcher { core }
184    }
185
186    pub async fn dispatch_event(&self, event: serde_json::Value) {
187        let event_name: &str = event["event_name"].as_str().unwrap();
188        match event_name {
189            "issues" if event["event"]["issue"].get("pull_request").is_some() => {
190                self.dispatch_pull_request_event(event).await
191            }
192            "pull_request" => self.dispatch_pull_request_event(event).await,
193            "issues" => self.dispatch_issues_event(event).await,
194            "issue_comment" => self.dispatch_issue_comment_event(event).await,
195            _ => unimplemented!(),
196        }
197    }
198
199    async fn dispatch_issues_event(&self, event: serde_json::Value) {
200        let event_action: &str = event["event"]["action"].as_str().unwrap();
201        let repo = Self::extract_repo_info(&event);
202        let running_info = Self::extract_running_info(&event);
203        match event_action {
204            "opened" => {
205                let inner_event = IssueCreatedEvent {
206                    id: event["event"]["issue"]["number"].as_u64().unwrap() as _,
207                    title: event["event"]["issue"]["title"]
208                        .as_str()
209                        .unwrap()
210                        .to_string(),
211                    body: event["event"]["issue"]["body"]
212                        .as_str()
213                        .unwrap()
214                        .to_string(),
215                    user: event["event"]["issue"]["user"]["login"]
216                        .as_str()
217                        .unwrap()
218                        .to_string(),
219                };
220                self.core
221                    .on_issue_created(repo, running_info, inner_event)
222                    .await;
223            }
224            "closed" => {
225                let id = event["event"]["issue"]["number"].as_u64().unwrap() as usize;
226                self.core.on_issue_closed(repo, running_info, id).await;
227            }
228            "updated" => {
229                let updated_part = if event["event"]["changes"].get("body").is_some() {
230                    UpdatedPart::Body {
231                        from: event["event"]["changes"]["body"]["from"]
232                            .as_str()
233                            .unwrap()
234                            .to_string(),
235                        to: event["event"]["issue"]["body"]
236                            .as_str()
237                            .unwrap()
238                            .to_string(),
239                    }
240                } else {
241                    UpdatedPart::Title {
242                        from: event["event"]["changed"]["body"]["from"]
243                            .as_str()
244                            .unwrap()
245                            .to_string(),
246                        to: event["event"]["issue"]["title"]
247                            .as_str()
248                            .unwrap()
249                            .to_string(),
250                    }
251                };
252                let inner_event = IssueUpdatedEvent {
253                    id: event["event"]["issue"]["number"].as_u64().unwrap() as usize,
254                    updated_part,
255                    user: event["event"]["issue"]["user"]["login"]
256                        .as_str()
257                        .unwrap()
258                        .to_string(),
259                };
260                self.core
261                    .on_issue_updated(repo, running_info, inner_event)
262                    .await;
263            }
264            "reopened" => {
265                let inner_event = IssueReopenedEvent {
266                    id: event["event"]["issue"]["number"].as_u64().unwrap() as _,
267                    title: event["event"]["issue"]["title"]
268                        .as_str()
269                        .unwrap()
270                        .to_string(),
271                    body: event["event"]["issue"]["body"]
272                        .as_str()
273                        .unwrap()
274                        .to_string(),
275                    user: event["event"]["issue"]["user"]["login"]
276                        .as_str()
277                        .unwrap()
278                        .to_string(),
279                };
280                self.core
281                    .on_issue_reopened(repo, running_info, inner_event)
282                    .await;
283            }
284            _ => unimplemented!(),
285        }
286    }
287
288    async fn dispatch_pull_request_event(&self, event: serde_json::Value) {
289        let repo = Self::extract_repo_info(&event);
290        let event_action: &str = event["event"]["action"].as_str().unwrap();
291        let running_info = Self::extract_running_info(&event);
292        match event_action {
293            "opened" => {
294                let inner_event = PullRequestCreatedEvent {
295                    id: event["event"]["pull_request"]["number"].as_u64().unwrap() as _,
296                    title: event["event"]["pull_request"]["title"]
297                        .as_str()
298                        .unwrap()
299                        .to_string(),
300                    body: event["event"]["pull_request"]["body"]
301                        .as_str()
302                        .unwrap()
303                        .to_string(),
304                    user: event["event"]["pull_request"]["user"]["login"]
305                        .as_str()
306                        .unwrap()
307                        .to_string(),
308                    from_repo: Repository {
309                        owner: event["event"]["pull_request"]["head"]["user"]["login"]
310                            .as_str()
311                            .unwrap()
312                            .to_string(),
313                        name: event["event"]["pull_request"]["head"]["repo"]["name"]
314                            .as_str()
315                            .unwrap()
316                            .to_string(),
317                    },
318                    from_ref: event["head_ref"].as_str().unwrap().to_string(),
319                    to_ref: event["base_ref"].as_str().unwrap().to_string(),
320                };
321                self.core
322                    .on_pull_request_created(repo, running_info, inner_event)
323                    .await;
324            }
325            "closed" => {
326                let id = event["event"]["issue"]["number"].as_u64().unwrap() as usize;
327                self.core
328                    .on_pull_request_closed(repo, running_info, id)
329                    .await;
330            }
331            "edited" => {
332                let updated_part = if event["event"]["changes"].get("body").is_some() {
333                    UpdatedPart::Body {
334                        from: event["event"]["changes"]["body"]["from"]
335                            .as_str()
336                            .unwrap()
337                            .to_string(),
338                        to: event["event"]["pull_request"]["body"]
339                            .as_str()
340                            .unwrap()
341                            .to_string(),
342                    }
343                } else {
344                    UpdatedPart::Title {
345                        from: event["event"]["changed"]["body"]["from"]
346                            .as_str()
347                            .unwrap()
348                            .to_string(),
349                        to: event["event"]["pull_request"]["title"]
350                            .as_str()
351                            .unwrap()
352                            .to_string(),
353                    }
354                };
355                let inner_event = PullRequestUpdatedEvent {
356                    id: event["event"]["issue"]["number"].as_u64().unwrap() as usize,
357                    updated_part,
358                    user: event["event"]["issue"]["user"]["login"]
359                        .as_str()
360                        .unwrap()
361                        .to_string(),
362                };
363                self.core
364                    .on_pull_request_updated(repo, running_info, inner_event)
365                    .await;
366            }
367            _ => unimplemented!(),
368        }
369    }
370
371    async fn dispatch_issue_comment_event(&self, event: serde_json::Value) {
372        let repo = Self::extract_repo_info(&event);
373        let event_action: &str = event["event"]["action"].as_str().unwrap();
374        let running_info = Self::extract_running_info(&event);
375        let target = if event["event"]["issue"].get("pull_request").is_some() {
376            CommentTarget::PullRequest(event["event"]["issue"]["number"].as_u64().unwrap() as _)
377        } else {
378            CommentTarget::Issue(event["event"]["issue"]["number"].as_u64().unwrap() as _)
379        };
380        match event_action {
381            "created" => {
382                let inner_event = CommentCreatedEvent {
383                    id: event["event"]["comment"]["id"].as_u64().unwrap() as usize,
384                    user: event["event"]["comment"]["user"]["login"]
385                        .as_str()
386                        .unwrap()
387                        .to_string(),
388                    target,
389                    body: event["event"]["comment"]["body"]
390                        .as_str()
391                        .unwrap()
392                        .to_string(),
393                };
394                self.core
395                    .on_comment_created(repo, running_info, inner_event)
396                    .await;
397            }
398            "deleted" => {
399                let id = event["event"]["comment"]["id"].as_u64().unwrap() as usize;
400                self.core.on_comment_deleted(repo, running_info, id).await;
401            }
402            "edited" => {
403                let inner_event = CommentUpdatedEvent {
404                    id: event["event"]["comment"]["id"].as_u64().unwrap() as usize,
405                    user: event["event"]["comment"]["user"]["login"]
406                        .as_str()
407                        .unwrap()
408                        .to_string(),
409                    target,
410                    from: event["event"]["changes"]["from"]
411                        .as_str()
412                        .unwrap()
413                        .to_string(),
414                    to: event["event"]["comment"]["body"]
415                        .as_str()
416                        .unwrap()
417                        .to_string(),
418                };
419                self.core
420                    .on_comment_updated(repo, running_info, inner_event)
421                    .await;
422            }
423            _ => unimplemented!(),
424        }
425    }
426
427    fn extract_repo_info(event: &serde_json::Value) -> Repository {
428        Repository {
429            owner: event["event"]["repository"]["owner"]["login"]
430                .as_str()
431                .unwrap()
432                .to_string(),
433            name: event["repository"]
434                .as_str()
435                .unwrap()
436                .split('/')
437                .nth(1)
438                .unwrap()
439                .to_string(),
440        }
441    }
442
443    fn extract_running_info(event: &serde_json::Value) -> RunningInfo {
444        RunningInfo {
445            run_id: usize::from_str_radix(event["run_id"].as_str().unwrap(), 10).unwrap(),
446            run_number: usize::from_str_radix(event["run_number"].as_str().unwrap(), 10).unwrap(),
447        }
448    }
449}