| 1 | import type { WebhookBody, WebhookPing } from '../webhooks.js'; |
| 2 | |
| 3 | /** What a chat message says about an event, before a format escapes and shapes it. `summary` and `excerpt` hold user text, unescaped. */ |
| 4 | export type WebhookMessage |
| 5 | /** The repository's or organization's name. */ |
| 6 | context: string |
| 7 | summary: string |
| 8 | url: string |
| 9 | excerpt: string |
| 10 | }; |
| 11 | |
| 12 | // Enough to see the point of a comment in a chat, short enough to keep a channel readable. |
| 13 | const EXCERPT_LENGTH = 500 |
| 14 | |
| 15 | // A push lists this many commits; the rest are counted. |
| 16 | const LISTED_COMMITS = 5 |
| 17 | |
| 18 | const REVIEW_VERBS = { |
| 19 | approved: 'approved', |
| 20 | changes_requested: 'requested changes on', |
| 21 | commented: 'reviewed', |
| 22 | } as const; |
| 23 | |
| 24 | export function messageOf(body |
| 25 | if (body.event |
| 26 | return { |
| 27 | context: body.repository |
| 28 | summary: 'Ghost webhook connected', |
| 29 | url: null, |
| 30 | excerpt: `Events: ${body |
| 31 | }; |
| 32 | } |
| 33 | |
| 34 | const { repository |
| 35 | const actor = |
| 36 | const say = |
| 37 | summary: string, |
| 38 | url: string |
| 39 | excerpt: string |
| 40 | ): WebhookMessage => |
| 41 | context: repository.fullName |
| 42 | summary: `${actor} |
| 43 | url, |
| 44 | excerpt: excerpt?.trim |
| 45 | }); |
| 46 | |
| 47 | const thread = |
| 48 | const about = |
| 49 | ? `${body.pullRequest |
| 50 | : ''; |
| 51 | const onThread = |
| 52 | say(summary, thread |
| 53 | |
| 54 | switch (body.event |
| 55 | case 'push': { |
| 56 | const [kind, |
| 57 | if (body.deleted |
| 58 | const url = |
| 59 | if (body.created |
| 60 | const commits = |
| 61 | const listed = |
| 62 | .slice(0, LISTED_COMMITS |
| 63 | .map((commit) |
| 64 | if (commits.length |
| 65 | listed.push(`and |
| 66 | } |
| 67 | return say( |
| 68 | `pushed ${commits.length} |
| 69 | url, |
| 70 | listed.join('\n |
| 71 | ); |
| 72 | } |
| 73 | case 'issue.opened': |
| 74 | return onThread(`opened |
| 75 | case 'issue.edited': |
| 76 | return onThread(`edited |
| 77 | case 'issue.closed': |
| 78 | return onThread(`closed |
| 79 | case 'issue.reopened': |
| 80 | return onThread(`reopened |
| 81 | case 'issue.assigned': |
| 82 | return onThread(`assigned |
| 83 | case 'issue.unassigned': |
| 84 | return onThread(`unassigned |
| 85 | case 'issue.labeled': |
| 86 | return onThread(`added the label |
| 87 | case 'issue.unlabeled': |
| 88 | return onThread(`removed the label |
| 89 | case 'issue.commented': |
| 90 | return onThread(`commented on |
| 91 | case 'issue.comment_edited': |
| 92 | return onThread( |
| 93 | `edited a comment on ${about}`, |
| 94 | body.comment?.body |
| 95 | ); |
| 96 | case 'issue.comment_deleted': |
| 97 | return onThread(`deleted a comment on |
| 98 | case 'pull_request.ready_for_review': |
| 99 | return onThread(`marked |
| 100 | case 'pull_request.converted_to_draft': |
| 101 | return onThread(`converted |
| 102 | case 'pull_request.merged': |
| 103 | return onThread(`merged |
| 104 | case 'pull_request.reviewed': |
| 105 | return onThread( |
| 106 | `${REVIEW_VERBS[body. |
| 107 | body.review?.body |
| 108 | ); |
| 109 | case 'pull_request.review_dismissed': |
| 110 | return onThread( |
| 111 | `dismissed a review on ${about}`, |
| 112 | body.review?.dismissalMessage |
| 113 | ); |
| 114 | case 'pull_request.review_commented': |
| 115 | return onThread( |
| 116 | `commented on ${body.comment |
| 117 | body.comment?.body |
| 118 | ); |
| 119 | case 'label.created': |
| 120 | case 'label.edited': |
| 121 | case 'label.deleted': |
| 122 | return say( |
| 123 | `${body.event. |
| 124 | `${repository.htmlUrl} |
| 125 | ); |
| 126 | case 'release.created': |
| 127 | case 'release.published': |
| 128 | case 'release.edited': |
| 129 | case 'release.deleted': |
| 130 | return say( |
| 131 | `${body.event. |
| 132 | body.release?.htmlUrl |
| 133 | body.event === |
| 134 | ); |
| 135 | case 'star.created': |
| 136 | return say(`starred |
| 137 | case 'star.deleted': |
| 138 | return say(`unstarred |
| 139 | case 'watch.started': |
| 140 | return say(`started watching |
| 141 | case 'fork.created': |
| 142 | return say( |
| 143 | `forked ${repository.fullName |
| 144 | body.fork?.htmlUrl |
| 145 | ); |
| 146 | case 'repository.edited': |
| 147 | return say(`edited the settings of |
| 148 | case 'repository.transferred': |
| 149 | return say(`transferred |
| 150 | case 'member.added': |
| 151 | return say( |
| 152 | `accepted the invitation to collaborate on ${repository.fullName |
| 153 | ); |
| 154 | case 'member.removed': |
| 155 | return say(`removed |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /** `refs/heads/main` is the branch main, `refs/tags/v1` the tag v1. */ |
| 160 | function refName(ref: |
| 161 | if (ref.startsWith |
| 162 | return ['tag', |
| 163 | return ['branch', |
| 164 | } |
| 165 | |
| 166 | function truncate(text: |
| 167 | return text.length |
| 168 | ? `${text.slice |
| 169 | : text; |
| 170 | } |