• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cofacts / rumors-line-bot / 13479813940

23 Feb 2025 04:48AM UTC coverage: 90.456% (-0.5%) from 90.989%
13479813940

Pull #403

github

MrOrz
fix: Remove duplicate line and simplify processingCount variable
Pull Request #403: Handle network errors

541 of 641 branches covered (84.4%)

Branch coverage included in aggregate %.

9 of 21 new or added lines in 5 files covered. (42.86%)

1 existing line in 1 file now uncovered.

1127 of 1203 relevant lines covered (93.68%)

12.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.51
/src/webhook/handlers/askingCooccurrence.ts
1
import { z } from 'zod';
2
import { msgid, ngettext, t } from 'ttag';
3

4
import ga from 'src/lib/ga';
5
import { ChatbotPostbackHandler } from 'src/types/chatbotState';
6

7
import {
8
  POSTBACK_YES,
9
  POSTBACK_NO,
10
  ManipulationError,
11
  createTextMessage,
12
  searchText,
13
  searchMedia,
14
  getLineContentProxyURL,
15
  createPostbackAction,
16
  createCooccurredSearchResultsCarouselContents,
17
  setExactMatchesAsCooccurrence,
18
  addReplyRequestForUnrepliedCooccurredArticles,
19
  setReplyTokenCollectorMsg,
20
  displayLoadingAnimation,
21
} from './utils';
22

23
const inputSchema = z.enum([POSTBACK_NO, POSTBACK_YES]);
3✔
24

25
/** Minimum similarity for a CooccurredMessage to be considered as existing in DB */
26
const IN_DB_THRESHOLD = 0.8;
3✔
27

28
/** Postback input type for ASKING_ARTICLE_SOURCE state handler */
29
export type Input = z.infer<typeof inputSchema>;
30

31
const askingCooccurence: ChatbotPostbackHandler = async ({
3✔
32
  context,
33
  postbackData: { state, input: postbackInput },
34
  userId,
35
}) => {
36
  let input: Input;
37
  try {
3✔
38
    input = inputSchema.parse(postbackInput);
3✔
39
  } catch (e) {
40
    console.error('[askingCooccurence]', e);
1✔
41
    throw new ManipulationError(t`Please choose from provided options.`);
1✔
42
  }
43

44
  const visitor = ga(userId, state, `Batch: ${context.msgs.length} messages`);
2✔
45

46
  switch (input) {
2!
47
    case POSTBACK_NO: {
48
      visitor
1✔
49
        .event({
50
          ec: 'UserInput',
51
          ea: 'IsCooccurrence',
52
          el: 'No',
53
        })
54
        .send();
55
      return {
1✔
56
        context,
57
        replies: [
58
          createTextMessage({
59
            text: t`Please send me the messages separately.`,
60
          }),
61
        ],
62
      };
63
    }
64

65
    case POSTBACK_YES: {
66
      visitor
1✔
67
        .event({
68
          ec: 'UserInput',
69
          ea: 'IsCooccurrence',
70
          el: 'Yes',
71
        })
72
        .send();
73

74
      let processingCount = context.msgs.length;
1✔
75
      await setReplyTokenCollectorMsg(
1✔
76
        userId,
77
        t`Out of the ${context.msgs.length} message(s) you have submitted, I am still analyzing ${processingCount} of them.`
78
      );
79
      await displayLoadingAnimation(userId);
1✔
80

81
      let searchResults;
82
      try {
1✔
83
        searchResults = await Promise.all(
1✔
84
          context.msgs.map(async (msg) => {
85
            const result = await (msg.type === 'text'
2✔
86
              ? searchText(msg.text)
87
              : searchMedia(getLineContentProxyURL(msg.id), userId));
88

89
            processingCount -= 1;
2✔
90
            // Update reply token collector message with latest number of messages that is still being analyzed
91
            await setReplyTokenCollectorMsg(
2✔
92
              userId,
93
              t`Out of the ${context.msgs.length} message(s) you have submitted, I am still analyzing ${processingCount} of them.`
94
            );
95

96
            return result;
2✔
97
          })
98
        );
99
      } /* istanbul ignore next */ catch (error) {
NEW
100
        console.error('[askingCooccurrence] Error searching media:', error);
×
NEW
101
        return {
×
102
          context,
103
          replies: [
104
            createTextMessage({
105
              text: t`Sorry, I encountered an error while analyzing the messages. Please try sending them to me again later.`,
106
            }),
107
          ],
108
        };
109
      }
110

111
      const notInDbMsgIndexes = searchResults.reduce((indexes, result, idx) => {
1✔
112
        const firstResult = result.edges[0];
2✔
113
        if (!firstResult) return [...indexes, idx];
2!
114

115
        return ('mediaSimilarity' in firstResult
2!
116
          ? firstResult.mediaSimilarity
117
          : firstResult.similarity) >= IN_DB_THRESHOLD
118
          ? indexes
119
          : [...indexes, idx];
120
      }, [] as number[]);
121

122
      if (notInDbMsgIndexes.length > 0) {
1!
123
        // Ask if the user want to submit those are not in DB into the database
124
        const totalCount = context.msgs.length;
×
125

126
        const btnText = `🆕 ${t`Report to database`}`;
×
127
        return {
×
128
          context,
129
          replies: [
130
            {
131
              type: 'flex',
132
              altText: t`Be the first to report these messages`,
133
              contents: {
134
                type: 'bubble',
135
                body: {
136
                  type: 'box',
137
                  layout: 'vertical',
138
                  spacing: 'md',
139
                  paddingAll: 'lg',
140
                  contents: [
141
                    {
142
                      type: 'text',
143
                      wrap: true,
144
                      text:
145
                        notInDbMsgIndexes.length === totalCount
×
146
                          ? t`The ${notInDbMsgIndexes.length} messages that you have sent are not within the Cofacts database.`
147
                          : ngettext(
148
                              msgid`Out of the ${totalCount} messages you sent, ${notInDbMsgIndexes.length} is not within the Cofacts database.`,
149
                              `Out of the ${totalCount} messages you sent, ${notInDbMsgIndexes.length} are not within the Cofacts database.`,
150
                              notInDbMsgIndexes.length
151
                            ),
152
                    },
153
                    {
154
                      type: 'text',
155
                      wrap: true,
156
                      text: t`If you believe:`,
157
                    },
158
                    {
159
                      type: 'box',
160
                      layout: 'horizontal',
161
                      contents: [
162
                        {
163
                          type: 'text',
164
                          text: '🤔',
165
                          flex: 0,
166
                          margin: 'none',
167
                        },
168
                        {
169
                          type: 'text',
170
                          wrap: true,
171
                          flex: 1,
172
                          margin: 'md',
173
                          contents: [
174
                            {
175
                              type: 'span',
176
                              text: /* t: If you believe ~ a rumor */ t`That they are most likely `,
177
                            },
178
                            {
179
                              type: 'span',
180
                              text: /* t: If you believe that it is most likely ~ */ t`rumors,`,
181
                              decoration: 'none',
182
                              color: '#ffb600',
183
                              weight: 'bold',
184
                            },
185
                          ],
186
                        },
187
                      ],
188
                      margin: 'md',
189
                    },
190
                    {
191
                      type: 'box',
192
                      layout: 'horizontal',
193
                      contents: [
194
                        {
195
                          type: 'text',
196
                          text: '🌐',
197
                          flex: 0,
198
                          margin: 'none',
199
                        },
200
                        {
201
                          type: 'text',
202
                          wrap: true,
203
                          flex: 1,
204
                          margin: 'md',
205
                          contents: [
206
                            {
207
                              type: 'span',
208
                              text: /* t: ~ make this messasge public */ t`And you are willing to `,
209
                            },
210
                            {
211
                              type: 'span',
212
                              text: /* t: and you are willing to ~ */ t`make these messages public`,
213
                              decoration: 'none',
214
                              color: '#ffb600',
215
                              weight: 'bold',
216
                            },
217
                          ],
218
                        },
219
                      ],
220
                      margin: 'md',
221
                    },
222
                    {
223
                      type: 'text',
224
                      wrap: true,
225
                      contents: [
226
                        {
227
                          type: 'span',
228
                          text: t`Press “${btnText}” to make these messages public on Cofacts website `,
229
                          color: '#ffb600',
230
                          weight: 'bold',
231
                        },
232
                        {
233
                          type: 'span',
234
                          text: t`and have volunteers fact-check it. This way you can help the people who receive the same message in the future.`,
235
                        },
236
                      ],
237
                    },
238
                  ],
239
                },
240
              },
241
              quickReply: {
242
                items: [
243
                  {
244
                    type: 'action',
245
                    action: createPostbackAction(
246
                      btnText,
247
                      notInDbMsgIndexes,
248
                      btnText,
249
                      context.sessionId,
250
                      'ASKING_ARTICLE_SUBMISSION_CONSENT'
251
                    ),
252
                  },
253
                  {
254
                    type: 'action',
255
                    action: createPostbackAction(
256
                      t`Don’t report`,
257
                      [],
258
                      t`Don’t report`,
259
                      context.sessionId,
260
                      'ASKING_ARTICLE_SUBMISSION_CONSENT'
261
                    ),
262
                  },
263
                ],
264
              },
265
            },
266
          ],
267
        };
268
      }
269

270
      await Promise.all([
1✔
271
        // All messages in DB and can be extracted from search results, can be set as cooccurrence.
272
        setExactMatchesAsCooccurrence(searchResults, userId),
273
        addReplyRequestForUnrepliedCooccurredArticles(searchResults, userId),
274
      ]);
275

276
      // Get first few search results for each message, and make at most 10 options
277
      //
278

279
      return {
1✔
280
        context,
281
        replies: [
282
          createTextMessage({
283
            text: `🔍 ${t`There are some messages that looks similar to the ones you have sent to me.`}`,
284
          }),
285
          createTextMessage({
286
            text:
287
              t`Internet rumors are often mutated and shared.
288
                Please choose the version that looks the most similar` + '👇',
289
          }),
290
          {
291
            type: 'flex',
292
            altText: t`Please choose the most similar message from the list.`,
293
            contents: {
294
              type: 'carousel',
295
              contents: createCooccurredSearchResultsCarouselContents(
296
                searchResults,
297
                context.sessionId
298
              ),
299
            },
300
          },
301
        ],
302
      };
303
    }
304

305
    default:
306
      // exhaustive check
307
      return input satisfies never;
×
308
  }
309
};
310

311
export default askingCooccurence;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc