LINE Official Account に友達登録している人の user ID リストを作成したかったのですが、Messaging API referenceを見る限り、verified or premium アカウントでしかこの機能を使えないようなので、メッセージが送信された際に取得できる情報を利用することにしました。
※ 認証済みアカウントで上記のAPIを用いる時のコードは認証済みアカウントでuserIdを収集するに記載しました。(ページ下部)
LINE BOTの作成
LINE developers Consoleで必要な登録と設定を済ませ、LINE BOT(Channel)を作成します。それに関しては、LINEのBot開発 超入門(前編) ゼロから応答ができるまでが参考になるかと思います。
Channelが作成できたら、[LINE developer] -> [Messaging API] から、Channel access token を取得してください。また、以下で作成するGAS(web application)のWebhook URLの設定箇所も同じ場所です。
※ LINE アカウントを作成したい場合は LINE Official Account Manager から作成してください。
GASの設定
以下のように doPost
を定義したスクリプトを用意し、web applicationとして公開、そのURLを先ほどのwebhook URLに設定すれば、(例えば以下では "message"
のみの) event に反応して特殊な反応(func_xxx_event
)をさせることができます。
const prop = PropertiesService.getScriptProperties();
const ss = SpreadsheetApp.getActiveSpreadsheet();
function doPost(e) {
var contents = e.postData.contents;
var obj = JSON.parse(contents)
var events = obj["events"];
for(var i = 0; i < events.length; i++){
if(events[i].type == "message"){
func_message_event(events[i]);
}else if (events[i].type == "follow"){
func_follow_event(events[i])
}
:
}
}
get_profile
userId
だけを収集してしまうとわからなくなってしまうので、userId
からプロフィール情報を入手する関数を用意する。なお、この関数を使いながらデータをためても良いし、ある程度のデータがたまった後にまとめてこの関数でプロフィールに紐づけても良い。
/** Get the profile information of users with ``userId``.
* @ref <https://developers.line.biz/en/reference/messaging-api/#get-profile>
* @param {string} userId User ID that is returned in a webhook event object. Do not use the LINE ID found on LINE.
* @param {string} acces_token Channel's Access Token.
* @return {Object} User profile Information.
*/
function get_profile(userId, access_token){
try{
var options = {
"method" : "GET",
"headers" : {
"Content-Type" : "application/json",
"Authorization" : `Bearer ${access_token}`,
},
};
var response = UrlFetchApp.fetch(`https://api.line.me/v2/bot/profile/${userId}`, options);
return JSON.parse(response.getContentText());
} catch(ex) {
return {
"displayName": "",
"userId": "",
"language": "",
"pictureUrl": "",
"statusMessage": ""
}
}
}
store_message_info
例えば func_message_event
に以下の関数を用いれば、user ID などを記録することができ、user ID の収集に非常に役立ちます。
※ Property の CHANNEL_ACCESS_TOKEN
に上記で取得した Channel access token の値を設定してください。
var CHAT_LOG_SHEET = ss.getSheetByName("LINE_chat_logs");
var CHAT_LOG_COUNTER_CELL = CHAT_LOG_SHEET.getRange("B1");
var CHAT_LOG_TABLE_A1 = CHAT_LOG_SHEET.getRange("A3");
var CHAT_LOG_TABLE_OFFSET_ROW = CHAT_LOG_TABLE_A1.getRow();
var CHAT_LOG_TABLE_OFFSET_COL = CHAT_LOG_TABLE_A1.getColumn();
/** Store chat logs.
* @ref <https://developers.line.biz/en/reference/messaging-api/#message-event>
* @param {Object} e Message event.
*/
function store_message_info(e){
var userId = e.source.userId;
var profile = get_profile(userId, prop.getProperty("CHANNEL_ACCESS_TOKEN"))
var data = [[e.timestamp, profile.displayName, userId, e.source.groupId, e.source.roomId, e.message.text]]
var num_stored_messages = parseInt(CHAT_LOG_COUNTER_CELL.getValue());
CHAT_LOG_SHEET.getRange(
CHAT_LOG_TABLE_OFFSET_ROW+num_stored_messages,
CHAT_LOG_TABLE_OFFSET_COL,
data.length,
data[0].length
).setValues(data);
CHAT_LOG_COUNTER_CELL.setValue(num_stored_messages+1);
}
reply_message_echolalia
同様にして、 func_message_event
に以下の関数を用いれば、送信されたメッセージをそのまま返すLINE BOTを作成することもできます。
※ Property の CHANNEL_ACCESS_TOKEN
に上記で取得した Channel access token の値を設定してください。
function reply_message_echolalia(e) {
var postData = {
"replyToken" : e.replyToken,
"messages" : [
{
"type" : "text",
"text" : e.message.text,
}
]
};
var options = {
"method" : "post",
"headers" : {
"Content-Type" : "application/json",
"Authorization" : "Bearer " + prop.getProperty("CHANNEL_ACCESS_TOKEN")
},
"payload" : JSON.stringify(postData)
};
UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
}
store_new_followers_info
func_follow_event
に以下の関数を用いれば、新規followerの情報を逐次収集できる。
var USER_ID_SHEET = ss.getSheetByName("LINE_user_Ids");
var USER_ID_COUNTER_CELL = USER_ID_SHEET.getRange("B2");
var USER_ID_TABLE_A1 = USER_ID_SHEET.getRange("A4");
var USER_ID_TABLE_OFFSET_ROW = USER_ID_TABLE_A1.getRow();
var USER_ID_TABLE_OFFSET_COL = USER_ID_TABLE_A1.getColumn();
/** Store new followers info
* @ref <https://developers.line.biz/en/reference/messaging-api/#follow-event>
* @param {Object} e Follow event.
*/
function store_new_followers_info(e){
var userId = e.source.userId;
var profile = get_profile(userId, prop.getProperty("CHANNEL_ACCESS_TOKEN"));
var data = [[profile.displayName, userId, ""]];
var num_stored_userIds = parseInt(USER_ID_COUNTER_CELL.getValue());
USER_ID_SHEET.getRange(
USER_ID_TABLE_OFFSET_ROW+num_stored_userIds,
USER_ID_TABLE_OFFSET_COL,
data.length,
data[0].length,
).setValues(data);
USER_ID_COUNTER_CELL.setValue(num_stored_userIds+1);
}
Python で LINE BOT を操作する
line-bot-sdk-pythonという便利なSDKがあったので、これを用いて操作してみます。
$ pip install line-bot-sdk
以下のコードでメッセージの送信が可能です。なお、サンプルコードには、flaskと連携し、LINE BOTをカスタマイズする方法が記載されています。
シンプルなテキストを送信する
from linebot import LineBotApi
from linebot.models import TextSendMessage
line_bot_api = LineBotApi(channel_access_token=channel_access_token)
line_bot_api.push_message(to=user_ID, messages=TextSendMessage(text='Hello World!'))
テンプレートテキストを送信する
from linebot import LineBotApi
from linebot.models import ButtonsTemplate, PostbackAction, MessageAction, URIAction, TemplateSendMessage
line_bot_api = LineBotApi(channel_access_token=channel_access_token)
line_bot_api.push_message(to=user_ID, messages=TemplateSendMessage(
alt_text='Buttons template',
template=ButtonsTemplate(
thumbnail_image_url='https://iwasakishuto.github.io/images/profile/twitter.png',
title='Menu',
text='Please select',
actions=[
PostbackAction(
label='postback',
display_text='postback text',
data='action=buy&itemid=1'
),
MessageAction(
label='message',
text='message text'
),
URIAction(
label='portfolio',
uri='https://iwasakishuto.github.io/'
)
]
)
))
これらによって、以下のようなメッセージを送信できます。
認証済みアカウントでuserIdを収集する
認証済みアカウントであれば https://api.line.me/v2/bot/followers/ids
のAPIが使えるので、以下のコードで一度に全てのuserIdリストを作成することができます。
get_follower_ids
(認証済)アカウントの ACCESS_TOKEN
を用いて get_follower-ids
というAPIを叩きます。このとき、一度に300人分しか取れないため、それ以降のユーザーの情報を取得するためには continuationToken
という引数に、この関数の返り値の next
というパラメータを入れる必要があります。
/** Get followers userId lists
* @param {string} access_token Channels Access token. (Must be verfied.)
* @param {string} continuationToken
* @ref <https://developers.line.biz/en/reference/messaging-api/#get-follower-ids>
*/
function get_follower_ids(access_token, continuationToken=""){
start_param = (continuationToken.length == 0) ? "" : `?start=${continuationToken}`;
var options = {
"method" : "GET",
"headers" : {
"Content-Type" : "application/json",
"Authorization" : `Bearer ${access_token}`,
},
};
var response = UrlFetchApp.fetch(`https://api.line.me/v2/bot/followers/ids${start_param}`, options);
return JSON.parse(response.getContentText());
}
get_all_follower_ids
上述の通り、一度に取得できるユーザーIDの数に限りがあるので、while
で全てのユーザーIdを取得するまで get_follower_ids
を実行します。
/** Get all followers' userId list.
* @param {string} access_token Channels Access token.
* @ref https://developers.line.biz/en/reference/messaging-api/#get-follower-ids>
*/
function get_all_follower_ids(){
var userIds = [];
var continuationToken="";
while (true){
response = get_follower_ids(continuationToken)
userIds = userIds.concat(response.userIds)
continuationToken = response.next;
if (continuationToken == undefined) break
}
return userIds;
}
update_followers_profile
もし、これらの関数を定期的に実行してスプレッドシートにまとめる、といったオペレーションがある場合、以下の関数を使うと便利だと思います。以下の関数では、
- 取得していないuserIdがあった場合、それを表に追加する
- 取得していたが、
displayName
に変更があった場合、更新する
ことができます。
/** Get user profile and organize it in a table while updating user's displayName.
* @param {string} access_token Channels Access token.
* @param {string} counter_cell The name of the cell which holds the number of acquired data.
* @param {string} table_A1 Upper left part (worth like a A1 cell) of the table to save data.
* @param {string} sheet_name Target Sheet Name.
*/
function update_followers_profile(access_token, counter_cell, table_A1, sheet_name){
var FOLLOWERS_SHEET = ss.getSheetByName(sheet_name);
var FOLLOWERS_COUNTER_CELL = FOLLOWERS_SHEET.getRange(counter_cell);
var FOLLOWERS_TABLE_A1 = FOLLOWERS_SHEET.getRange(table_A1);
var FOLLOWERS_TABLE_OFFSET_ROW = FOLLOWERS_TABLE_A1.getRow();
var FOLLOWERS_TABLE_OFFSET_COL = FOLLOWERS_TABLE_A1.getColumn();
var num_stored_users = parseInt(FOLLOWERS_COUNTER_CELL.getValue());
var follower_userIds = get_all_followers_profile(access_token);
var stored_userIds = [];
var stored_displayNames = [];
var stored_Names = [];
FOLLOWERS_SHEET.getRange(
FOLLOWERS_TABLE_OFFSET_ROW,
FOLLOWERS_TABLE_OFFSET_COL,
num_stored_users,
3
).getValues().forEach(function(e){
stored_userIds.push(e[0]);
stored_displayNames.push(e[1]);
stored_Names.push(e[2]);
})
for (var i=0; i<follower_userIds.length; i++){
var userId = follower_userIds[i];
var idx = stored_userIds.indexOf(userId);
var profile = get_profile(userId, access_token);
Logger.log([i, idx, profile.displayName]);
if (idx==-1){
// Register a new user.
var data = [[userId, profile.displayName, ""]];
FOLLOWERS_SHEET.getRange(
FOLLOWERS_TABLE_OFFSET_ROW+num_stored_users,
FOLLOWERS_TABLE_OFFSET_COL,
data.length,
data[0].length,
).setValues(data);
num_stored_users++;
}else if (stored_displayNames[idx] != profile.displayName){
// Update a stored user info.
Logger.log(`${stored_displayNames[idx]} != ${profile.displayName}`);
var data = [[userId, profile.displayName, stored_Names[idx]]];
FOLLOWERS_SHEET.getRange(
FOLLOWERS_TABLE_OFFSET_ROW+idx,
FOLLOWERS_TABLE_OFFSET_COL,
data.length,
data[0].length,
).setValues(data);
}
}
FOLLOWERS_COUNTER_CELL.setValue(num_stored_users);
}