File size: 5,460 Bytes
bf48b89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import remarkParse from 'remark-parse';
import { unified } from 'unified';

// @TODO maybe we could use label or some other better ways to distinguish bug/feature issues
const matchTitle = ['路由地址', 'Routes'];
const maintainerURL = 'https://raw.githubusercontent.com/DIYgod/RSSHub/gh-pages/build/maintainers.json';
const successTag = 'bug ping: pinged';
const parseFailTag = 'bug ping: parsing failed';
const failTag = 'bug ping: not found';
const deprecatedRoute = 'route: deprecated';
const route = 'route';

// DnD (do-not-disturb) usernames, add yours here to avoid being notified
// eslint-disable-next-line unicorn/no-useless-collection-argument
const dndUsernames = new Set([]);

async function parseBodyRoutes(body, core) {
    const ast = await unified().use(remarkParse).parse(body);

    // Is this a bug report?
    const title = ast.children[0].children[0].value.trim();
    core.debug(`title: ${title}`);
    if (!matchTitle.some((ele) => ele.localeCompare(title) === 0)) {
        return null;
    }

    let routes = ast.children[1].value.trim();
    core.debug(`routes: ${JSON.stringify(routes)}`);
    if (routes.localeCompare('NOROUTE') === 0) {
        return null;
    }

    if (routes) {
        routes = routes.split(/\r?\n/).filter(Boolean);
        const dedup = [...new Set(routes)];
        if (dedup.length !== routes.length) {
            core.warning('Duplication detected.');
        }
        core.debug(dedup);
        return dedup;
    }

    throw new Error('unable to parse the issue body: route does not exist');
}

async function getMaintainersByRoutes(routes, core) {
    const response = await fetch(maintainerURL);
    const maintainers = await response.json();

    return routes.map((e) => {
        const m = maintainers[e];
        if (m === undefined) {
            core.warning(`Route ${e} not found`);
        }

        return m;
    });
}

export default async function callMaintainer({ github, context, core }) {
    const body = context.payload.issue.body;
    const issueFacts = {
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
    };

    const addLabels = (labels) =>
        github.rest.issues
            .addLabels({
                ...issueFacts,
                labels,
            })
            .catch((error) => {
                core.warning(error);
            });
    const updateIssueState = (state) =>
        github.rest.issues
            .update({
                ...issueFacts,
                state,
            })
            .catch((error) => {
                core.warning(error);
            });

    if (context.payload.issue.state === 'closed') {
        await updateIssueState('open');
    }

    const routes = await parseBodyRoutes(body, core).catch((error) => {
        core.warning(error);
    });

    if (routes === null) {
        return; // Not a bug report, or NOROUTE
    }

    if (routes === undefined) {
        await addLabels([parseFailTag]);
        return;
    }

    const maintainers = await getMaintainersByRoutes(routes, core);

    let successCount = 0;
    let emptyCount = 0;
    let failedCount = 0;
    let comments = '##### Searching for maintainers: \n\n';

    for (const [i, route] of routes.entries()) {
        const main = maintainers[i];
        if (main === undefined) {
            comments += `- \`${route}\`: **Route not found**\n`;
            failedCount += 1;
            continue;
        }

        if (main.length === 0) {
            comments += `- \`${route}\`: No maintainer listed, possibly a v1 or misconfigured route\n`;
            emptyCount += 1;
            continue;
        }

        if (main.length > 0) {
            const pingStr = main
                .map((e) => {
                    if (dndUsernames.has(e)) {
                        return `\`@${e}\``; // Wrap in an inline code block to make sure no mention will be sent
                    }
                    return `@${e}`;
                })
                .join(' ');
            comments += `- \`${route}\`: ${pingStr}\n`;
            successCount += 1;
        }
    }

    const labels = [''];

    if (failedCount > 0) {
        labels.push(failTag);
    } else {
        labels.push(successTag);
    }

    if (emptyCount > 0) {
        labels.push(deprecatedRoute);
    }

    if (successCount > 0) {
        labels.push(route);
    }

    // Write labels (status, affected route count)
    await addLabels(labels);

    // Reply to the issue and notify the maintainers (if any)
    await github.rest.issues
        .createComment({
            ...issueFacts,
            body: `${comments}


> To maintainers: if you are not willing to be disturbed, list your username in \`scripts/workflow/test-issue/call-maintainer.js\`. In this way, your username will be wrapped in an inline code block when tagged so you will not be notified.

If all routes can not be found, the issue will be closed automatically. Please use \`NOROUTE\` for a route-irrelevant issue or leave a comment if it is a mistake.
如果所有路由都无法匹配,issue 将会被自动关闭。如果 issue 和路由无关,请使用 \`NOROUTE\` 关键词,或者留下评论。我们会重新审核。
`,
        })
        .catch((error) => {
            core.warning(error);
        });

    if (failedCount && emptyCount === 0 && successCount === 0) {
        await updateIssueState('closed');
    }
}