I had the opportunity to develop a function for exchanging messages between a system written in Ruby and LINE, a general user, so I will share the knowledge I gained at that time.
I also made a demo of a working application using Rails, so see here for detailed implementation details. I think that the content of the article will come in smoothly if you ask.
A system administrator (hereinafter referred to as "administrator") and a general user (hereinafter referred to as "user") create a function for exchanging text messages via LINE. Since the system needs to know the user's LINE information in order to actually communicate, we will also implement LINE login on the browser.
After linking the user's LINE information, when the user sends a message to the LINE official account linked to the administrator, the message will be sent to the system by Webhook and saved in the database from there. The image is roughly as shown in the figure below.
Administrators can also send LINE messages to users through the system. When you send a text from the system, you can send a message to the target user from the administrator's LINE official account through the program. Once sent, the message will be saved in the DB. The image is as follows.
If the administrator does not have a LINE API channel, it will not be possible to communicate with users on LINE, so create it. Let's make it from LINE Developer console. The channel you need to create this time
There are two.
The Messaging API acts as an official LINE account. As a caveat, let's set the ** response mode to "Bot" **. In chat mode, I can communicate from the LINE official account manager, but I can't receive webhooks. Therefore, it is necessary to use it in Bot mode because there is a problem that the system cannot keep a log of the interaction. For details on how to create it, click here [https://developers.line.biz/ja/docs/messaging-api/getting-started/#%E3%83%81%E3%83%A3%E3%83%8D% E3% 83% AB% E3% 81% AE% E4% BD% 9C% E6% 88% 90) Please. LINE Login is literally a channel for using LINE Login.
After creating, create the data in the admins table of DB. Originally, I think that it is good to register from the screen, but it is made to wear it sideways and poke it directly from the console (when putting it in the DB with the production code, it is recommended to encrypt it in consideration of security To do)
create_table "admins", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.integer "line_messaging_id", null: false #Messaging API "Channel ID"
t.string "line_messaging_secret", null: false #Messaging API "Channel Secret"
t.string "line_messaging_token", null: false #Messaging API "Channel Access Token"
t.integer "line_login_id", null: false #"Channel ID" of LINE Login
t.string "line_login_secret", null: false #LINE Login "Channel Secret"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
Next time, I need to set some URLs on the console side of LINE, so I will register. Specifically, it is as follows.
--Messaging API Webhook URL (URL received by the system when the user sends a message) --LINE login callback URL (redirect URL after login)
There are some things to be aware of when registering this URL. It has a URL
--https only --localhost not available
That is the point. At this rate, local development seems difficult. However, you can temporarily publish the local URL to the outside by using ngrok, so you can comfortably develop locally by registering the URL created by it. You can do it. It's very convenient.
After creating the POST request path for the Webhook URL and setting it on the console, verify the request through the "Verify" button. Make sure you get 200 responses. For more information [here](https://developers.line.biz/ja/docs/messaging-api/building-bot/#%E3%82%B3%E3%83%B3%E3%82%BD%E3% 83% BC% E3% 83% AB% E3% 81% A7% E3% 83% 9C% E3% 83% 83% E3% 83% 88% E3% 82% 92% E8% A8% AD% E5% AE% 9A%E3%81%99%E3%82%8B) Please.
Now that the preparations for development are complete, I would like to continue developing.
First, I would like to create a LINE login function to put the user's LINE information necessary for sending and receiving messages into the DB. This LINE information is the "user ID". Please note that it is not the LINE ID used for ID search.
The login flow is [here](https://developers.line.biz/ja/docs/line-login/integrate-line-login/#%E3%83%AD%E3%82%B0%E3%82% A4% E3% 83% B3% E3% 81% AE% E3% 83% 95% E3% 83% AD% E3% 83% BC), but if I write it again
redirect_uri
. At this time, the query string including the authorization code and state
is also sent to the system.state
, the system uses the authorization code to request an access token at the endpoint URL https://api.line.me/oauth2/v2.1/token.Let's look at the code that creates the authorization URL for step 1.
app/controllers/auth_controller.rb
class AuthController < ApplicationController
def index
admin = Admin.find(params[:admin_id])
state = SecureRandom.hex(32)
session[:state] = state
redirect_to Line::Api::Oauth.new(admin).auth_uri(state)
end
end
Use / admin /: admin_id / auth to redirect to the authorization URL based on the account information of the target administrator's LINE login.
Since state is necessary for CSRF measures, a random number is generated on the application side. The value is stored in session (used in post-authorization processing) and passed to Line :: Api :: Oauth # auth_uri
. Let's take a look at the code in it.
app/models/line/api/oauth.rb
AUTH_URI = 'https://access.line.me/oauth2/v2.1/authorize'
def auth_uri(state)
params = {
response_type: 'code',
client_id: @admin.line_login_id,
redirect_uri: callback_uri,
state: state,
scope: 'openid',
prompt: 'consent', #Option to make sure to allow LINE authentication
bot_prompt: 'aggressive' #After logging in, a screen will appear asking if you want to make friends with the official account you linked with.
}
# NOTE: https://developers.line.biz/ja/docs/line-login/integrate-line-login/#making-an-authorization-request
"#{AUTH_URI}?#{params.to_query}"
end
See here for the detailed meaning of the parameters. Personally, the bot_prompt
parameter is convenient, and after logging in to LINE, the screen for adding friends to the corresponding official account is also displayed, so I thought it would be convenient to take messages with me.
When you hit the URL, you will be redirected to the login screen as shown in the image below.
When login is complete and authorization is complete, you will be redirected to the LINE login callback URL you set earlier. Now that we're heading to / admin /: admin_id / callback
, let's look at the corresponding code.
app/controllers/callback_controller.rb
class CallbackController < ApplicationController
def index
admin = Admin.find(params[:admin_id])
#Throw an exception if the states are different
raise Line::InvalidState unless params[:state] == session[:state]
line_user_id = Line::Api::Oauth.new(admin).line_user_id(params[:code])
User.create!(line_user_id: line_user_id)
render plain: 'LINE cooperation completed!', status: :ok
end
end
CSRF measures are taken by comparing the state value that comes after authorization with the state value of the session that was previously entered. Then get your LINE user ID. Once obtained, store it in the DB. Let's take a look at the user ID acquisition code.
app/models/line/api/oauth.rb
def line_user_id(code)
verify_id_token(access_token_data(code))['sub']
end
def access_token_data(code)
req_body = {
grant_type: 'authorization_code',
code: code,
redirect_uri: callback_uri, # NOTE:It is compared with the "callback URL" set in the console of the LINE login channel.
client_id: @admin.line_login_id,
client_secret: @admin.line_login_secret
}
# NOTE: https://developers.line.biz/ja/docs/line-login/integrate-line-login/#get-access-token
res = conn.post do |req|
req.url '/oauth2/v2.1/token'
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
req.body = req_body
end
JSON.parse(handle_error(res).body)
end
def verify_id_token(access_token_data)
req_body = {
id_token: access_token_data['id_token'],
client_id: @admin.line_login_id
}
# NOTE: https://developers.line.biz/ja/reference/social-api/#verify-id-token
res = conn.post do |req|
req.url '/oauth2/v2.1/verify'
req.body = req_body
end
JSON.parse(handle_error(res).body)
end
ʻAccess_token_data` hits Access Token Acquisition API to return information, and it To the Social API ID Token Verification API and succeed, you will get your user ID.
One thing to keep in mind is that the access token acquisition API passes redirect_uri
, which should be exactly the same as the redirect_uri
set in the authorization URI. You may not do much, but for various reasons, if you want to pass something as a query parameter to redirect_uri
when creating an authorization URL and get it with a callback, the access token acquisition API including that query parameter If you do not pass it to redirect_uri
, you will get an error.
Now that the user has been able to link with the administrator's LINE official account, I would like to receive the LINE message sent by the user in the system next.
Since the URL of the path of / admin /: admin_id / webhook
is specified in the Webhook URL of the Messaging API, the request will be sent every time some event (add friend, block message, etc.) occurs in the official account. Become.
So, if the event where the message was sent from that request && the person who sent it is the LINE user ID ** saved in the DB, if you save the message in the DB, the LINE messages from the user will be collected. You can.
The code for the webhook controller is below.
app/controllers/webhook_controller.rb
class WebhookController < ApplicationController
protect_from_forgery with: :null_session
def create
admin = Admin.find(params[:admin_id])
bot = Line::Api::Bot.new(admin)
body = request.body.read
raise Line::InvalidSignatureError unless bot.validate_signature?(body, request.env['HTTP_X_LINE_SIGNATURE'])
events = bot.parse_events_from(body)
events.each do |event|
case event
when Line::Bot::Event::Message
Line::SaveReceivedMessage.new(admin).call(event)
end
end
render plain: 'success!', status: :ok
end
end
Since the request comes from the outside, invalidate the CSRF token. You will also need to do a Signature Validation to determine if the request is from the LINE platform. It's a little complicated, but let's use it because it will be done with a gem called line-bot-sdk-ruby.
Once verified, determine what the event looks like. I will use it because the gem will also parse the event information from the request body.
I think it's simple to branch the event with a case statement. Since Line :: Bot :: Event :: Message
is the event when the message arrives, the process of saving the message is inserted at that time. The code is below.
app/models/line/save_received_message.rb
module Line
class SaveReceivedMessage
def initialize(admin)
@admin = admin
end
def call(event)
user = User.find_by(line_user_id: event['source']['userId'])
resource = MessageText.new(content: event.message['text'])
Message.create!(sendable: user, receivable: @admin, resource: resource) if user.present?
end
end
end
See here for the contents of the event object.
Finally, check the operation. When you send a message
You can see that the request is skipped and the record is saved. The system can now receive the user's LINE message and put it in the DB.
Next, we will look at System-> User's LINE message transmission, which is the opposite of the previous one. You don't need to use Webhook, just hit API to send push message and you're done.
app/models/line/save_sent_message.rb
module Line
class SaveSentMessage
def initialize(admin)
@admin = admin
end
def call_with_text(user:, text:)
user = User.find_by(line_user_id: user.line_user_id)
if user.present?
Line::Api::Push.new(@admin).call_with_text(user: user, text: text)
resource = MessageText.new(content: text)
Message.create!(sendable: @admin, receivable: user, resource: resource)
end
end
end
end
app/models/line/api/push.rb
module Line::Api
class Push < Base
def call_with_text(user:, text:)
call(user: user, resource: Message::Text.new([text]))
end
private
def call(user:, resource:)
req_body = {to: user.line_user_id}.merge(resource.request_body)
# NOTE: https://developers.line.biz/ja/reference/messaging-api/#send-push-message
res = conn.post do |req|
req.url '/v2/bot/message/push'
req.headers['Content-Type'] = 'application/json'
req.headers['Authorization'] = "Bearer #{@admin.line_messaging_token}"
req.body = req_body.to_json
end
handle_error(res)
end
end
end
It is an operation check. Wear it sideways and hit the chord from the console.
A record has been added.
A message has been sent to LINE!
With the above, it is possible to communicate with each other between the user and the system and leave the contents. I think it will be more complicated in a real system, but I hope this will help you implement something: pray:
Recommended Posts