Line bot that checks if a message contains internet rumor.
This is a one of the sub-project of 真的假的。
This state diagram describes how the LINE bot talks to users:
Developing rumors-line-bot requires you to finish the following settings.
After cloning this repository & cd into project directory, then install the dependencies.
$ git clone --recursive [email protected]:cofacts/rumors-line-bot.git # --recursive for the submodules
$ cd rumors-line-bot
Please follow all the steps in LINE official tutorial.
Create .env
file from .env.sample
template, at least fill in:
API_URL=https://dev-api.cofacts.tw/graphql
LINE_CHANNEL_SECRET=<paste Messaging API's channel secret here>
LINE_CHANNEL_TOKEN=<paste Messaging API's channel access token here>
LINE_LOGIN_CHANNEL_ID=<paste LINE Login channel ID here>
LIFF_URL=<paste LIFF app's LiFF URL>
Other customizable env vars are:
REDIS_URL
: If not given,redis://127.0.0.1:6379
is used.PORT
: Which port the line bot server will listen at.GTM_ID
: Google Tag Manager ID. For the events and variables we push todataLayer
, see "Google Tag Manager" section below.DEBUG_LIFF
: Disables external browser check in LIFF. Useful when debugging LIFF in external browser. Don't enable this on production.RUMORS_LINE_BOT_URL
: Server public url which is used to generate tutorial image urls and auth callback url of LINE Notify.
You will need Node.JS
16+ to proceed.
$ npm i
Spin up peripherals like Redis and MongoDB using:
$ docker-compose up -d
Then spin up the application, including chatbot server and webpack-dev-server for LIFF, using:
$ npm run dev
The server will be started on localhost:5001
(or the PORT
you specified in your .env
file.)
If you wish to stop the peripherals, run docker-compose stop
.
Just run npm test
. It will automatically spin up the aforementioned docker and run unit tests.
We recommend using ngrok
to create a public address that directs the traffic from LINE server to your local machine. With ngrok
in your path, just
$ ngrok http 5001
ngrok
will give you a public URL. Use this to set the webhook URL of your Channel (See the section "Channel Console" in LINE official tutorial).
We recommend using ngrok configuration file to setup a tunnel with a fixed subdomain
. In this way the public URL can be fixed (means no repeatitive copy-pasting to LINE Channel settings!) as long as the subdomain
is not occupied by others.
Inside LINE Developers console in your Message API channel, under Messaging API > Webhook settings set the Webhook URL to ${ngrok_url}/callback
and turn on Use webhook. Click verify to confirm it is successfully connected to your local machine.
We are using LIFF to collect user's reason when submitting article & negative feedbacks.
If you don't need to develop LIFF, you can directly use LIFF_URL
provided in .env.sample
, which links to staging LIFF site.
If you want to modify LIFF, you may need to follow these steps:
To create LIFF apps, please follow instructions under official document, which involves
- Creating a LINE login channel
- Select
chat_message.write
in scope (for LIFF to send messages) After acquiring LIFF URL, place it in.env
asLIFF_URL
. - Set
Endpoint URL
to start with your chabbot endpoint, and add/liff/index.html
as postfix.
To develop LIFF, after npm run dev
, it is accessible under /liff/index.html
of dev server (http://localhost:5001) or production chatbot server.
In development mode, it spins a webpack-dev-server on localhost:<LIFF_DEV_PORT>
(default to 8080
),
and /liff
of chatbot server proxies all requests to the webpack-dev-server.
A tip to develop LIFF in browser is:
- Visit
https://<your-dev-chatbot.ngrok.io>/liff/index.html?p=<page>&...
in desktop browser. - If your browser has not logged in LINE, LIFF SDK will redirect your desktop browser window to login page.
- If your browser logged in LINE for a while, it is possible that your session has timed out. LINE LIFF does not log you out automatically; you will need to type
liff.logout()
manually in JS console to trigger a re-login.
liff.init()
would still work in desktop browser, so that the app renders, enabling us to debug web layouts on desktop.
liff.sendMessages()
would not work, though.
liff.closeWindow()
will not work either if your browser window has gone through login redirects.
The LINE bot server starts a GraphQL server that stiches Cofacts GraphQL API and API specific to the LINE chatbot.
Whenever Cofacts API updates, use npm run cofactsapi
to fetch the latest Cofacts API schema.
During development, use the following command to start a storybook on your local machine:
npm run storybook # Then visit http://localhost:6006
You can also visit https://cofacts.github.io/rumors-line-bot for pre-built storybook on master
branch.
On production, LIFF files are compiled to /liff
directory and served as static files by the chatbot server.
If you get 400 bad request
in LIFF, please search for liff.init
function call in compiled JS binary and see
if LIFF ID is consistent with your LIFF URL, which should be the path without leading https://liff.line.me/
.
The LIFF ID is set using Webpack Define plugin during build, thus swapping LIFF URL env variable without rebuilding the LIFF binaries will cause 400 bad request.
We use ttag to support build-time i18n for the chatbot.
Please refer to ttag documentation for annotating strings to translate.
To extract annotated strings to translation files, use:
$ npm run i18n:extract
The translation files are located under i18n/
, in Gettext PO format.
en_US.po
: Since the language used in code is already English, this empty translation file exists to simplify settings.zh_TW.po
: Traditional Chinese translation.ja.po
: Japanese translation.
You can replace this with any language you want to support, by leveraging Gettext msginit
command.
You will need to change i18n:extract
and i18n:validate
script in package.json
to reflect the locale change.
By default, the chatbot will be built under en_US
locale.
On Heroku, please set LOCALE
to one of en_US
, zh_TW
or any other language code that exists under i18n/
directory.
If you want to build using docker instead, you may need to modify Dockerfile to include the desired LOCALE
.
-
Prerequisites :
- LIFF setup
- Connect MongoDB
-
To use push message : in
.env
file, setsNOTIFY_METHOD=PUSH_MESSAGE
-
To use LINE Notify :
- You should first register a service.
- Then sets up
Callback Url
:RUMORS_LINE_BOT_URL
/authcallback/line_notify - in
.env
file, setsLINE_NOTIFY_CLIENT_ID=<paste LINE Notify Client ID here> LINE_NOTIFY_CLIENT_SECRET=<paste LINE Notify Client Secret here> NOTIFY_METHOD=LINE_NOTIFY RUMORS_LINE_BOT_URL=<line bot server url> LINE_FRIEND_URL=https://line.me/R/ti/p/<paste your chatbot ID here>
You can set up a setting page entry point(LIFF_URL
?p=setting) in account manager -> rich menu
- To run on local machine
$ npm run notify
- To run on heroku, you can use heroku scheduler
$ node build/scripts/scanRepliesAndNotify.js
rumors-line-bot uses Google cloud services that is authenticated and authorized using Google Cloud service accounts and Application Default Credentials.
Please create a service account under the project, download its key and use GOOGLE_APPLICATION_CREDENTIALS
env var to
provide the path to your downloaded service account key. See documentation for detail.
We use Dialogflow to detect if user is trying to chit-chat. If user input matches any of the Dialogflow intents, we can directly return predefined responses in that intent.
To use Dialogflow, please do the following setup:
- Please ensure your GCP project has enabled Dialogflow api.
- Build an agent connected to the GCP project.
- Please ensure the service account has
dialogflow.sessions.detectIntent
permission. - Set these env variables (optional):
Create a custom (user scope) dimemsion for Message Source
, and a custom (hit scope) metrix for Group Members Count
. Both of them default index is 1. If the indexes GA created are not 1, find cd1
and cm1
in the code and change them to cd$theIndexGACreated
and cm$theIndexGACreated
respectively.
Use npm run typecheck
to check types; use npm run typegen
to generate type from GraphQL schema.
Prepare .env
file (which should be identical to your deployment environment) and run docker build .
to generate docker image.
.env
will be copied over to the builder image to generate LIFF static file with the env.
When building image, you can just include the "Build-time variables" (denoted in .env.sample
) in .env
to ensure that no server credentials are leaked in the built client code.
Since built docker images will encode public URLs into statically built files, these build-time variables when we run the image as a container. Therefore, each separate deployment environment will require a separate build of the image.
You can test the built image locally using the docker-compose.yml
; just uncomment the line bot section and provide the built image name.
For production, please see rumors-deploy for sample docker-coompose.yml
that runs such image.
We push variables and events in Google Tag Manager's dataLayer
when the user interacts with LIFF.
You can prepare the following setup in .env
file:
GTM_ID
: Google Tag Manager Container ID (GTM-XXXXXXX
)
The application will fire the following custom events in GTM dataLayer
:
dataLoaded
- when data is loaded in article, comment or feedback LIFF.routeChangeComplete
- when LIFF is loaded or changes path.feedbackVote
- when the user submits a feedback.- Fires once when user opens Feedback LIFF, and can fire again when user updates vote or comments.
- Also fires when user submits feedback on Article LIFF.
chooseArticle
- when the user chooses an article in Articles LIFF.
Also, it will push the following custom variable to dataLayer
;
pagePath
- Set whenrouteChangeComplete
event fires. The page path from LIFF's router.userId
- Set after LIFF gets ID token and decodes LINE user ID inside.articleId
andreplyId
: set on Article, Comment and FeedbackonMount()
lifecycle is called. Or whenchooseArticle
event is fired.doc
- Set whendataLoaded
event fires. The loaded content itself in object (article in Article LIFF, comment in Comment LIFF and feedback in feedback LIFF).
Sent event format: Event category
/ Event action
/ Event label
We use dimension Message Source
(Custom Dimemsion1) to classify different event sources
user
for 1 on 1 messagesroom
|group
for group messages
- User sends a message to us
UserInput
/MessageType
/<text | image | video | ...>
- If we found a articles in database that matches the message:
UserInput
/ArticleSearch
/ArticleFound
Article
/Search
/<article id>
for each article found
- If nothing found in database:
UserInput
/ArticleSearch
/ArticleNotFound
- If articles found in database but is not what user want:
UserInput
/ArticleSearch
/ArticleFoundButNoHit
- When user provides source
UserInput
/IsForwarded
/Yes
|No
- When user specifies if the message comes from same person at same time (cooccurrence)
UserInput
/IsCooccurrence
/Yes
|No
- Matches one of Dialogflow intents
UserInput
/ChatWithBot
/<intent name>
- User chooses a found article
Article
/Selected
/<selected article id>
- If there are replies:
Reply
/Search
/<reply id>
for each replies
- If there are no replies:
Article
/NoReply
/<selected article id>
- User chooses a reply
Reply
/Selected
/<selected reply id>
Reply
/Type
/<selected reply's type>
- User votes a reply
UserInput
/Feedback-Vote
/<articleId>/<replyId>
- When the LIFF opens, page view for page
/feedback/yes
or/feedback/no
is also sent.
- User want to submit a new article
Article
/Create
/Yes
- User does not want to submit an article
Article
/Create
/No
- User updates their reason of reply request
Article
/ProvidingReason
/<articleId>
- When the LIFF opens, page view for page
/reason
is also sent.
- User opens article list
- Page view for page
/articles
is sent - If opened via rich menu:
utm_source=rumors-line-bot&utm_medium=richmenu
- If opened via push message:
utm_source=rumors-line-bot&utm_medium=push
- When user clicks viewed article item in article list
LIFF
/ChooseArticle
/<articleId>
- Note: this event is dispatched in LIFF, thus URL params like
utm_source
,utm_medium
also applies.
- User opens settings list
- Page view for page
/setting
is sent - If opened after sending reply requests:
utm_source=rumors-line-bot&utm_medium=reply-request
- If opened in tutorial:
&utm_source=rumors-line-bot&utm_medium=tutorial
- Tutorial
- If it's triggered by follow event (a.k.a add-friend event)
Tutorial
/Step
/ON_BOARDING
- If it's triggered by rich menu
Tutorial
/Step
/RICH_MENU
- Others
Tutorial
/Step
/<TUTORIAL_STEPS>
- When chatbot joined/leaved a group or a room
- Join
Group
/Join
/1
(Event category
/Event action
/Event value
)- And
Group Members Count
(Custom Metric1) to record group members count when chatbot joined.
- Leave
Group
/Leave
/-1
(Event category
/Event action
/Event value
)
Note:
- We set ga event value 1 as join, -1 as leave. To know total groups count chatbot currently joined, you can directly see the total event value (Details see Implicit Count).
- To know a group is currently joined or leaved, you should find the last
Join
orLeave
action of theClient Id
.- Also, you should find the last
Join
action of theClient Id
to get a more accurateGroup Members Count
.Group Members Count
is only recorded when chatbot joined group, to know the exact count, you should directly get it from line messaging-api.
- User sends a message to us
- If we found a articles in database that matches the message:
UserInput
/ArticleSearch
/ArticleFound
Article
/Search
/<article id>
for each article found
- If the article is identical
Article
/Selected
/<selected article id>
- If the article has a valid category and the reply is valid (Details see #238)
Reply
/Selected
/<selected reply id>
- User trigger chatbot to introduce itself:
UserInput
/Intro
/
- LINE content proxy URL is being accessed:
ContentProxy
/Forward
/<content type>
/<content length>
(value)
LICENSE
defines the license agreement for the source code in this repository.
LEGAL.md
is the user agreement for Cofacts website users.