前回記事
今回はMessaging API(LINEメッセージを送るAPI)とGlitch(無料サーバー)を使って、「『最新』というキーワードに対し最新の試合成績を返す」というコードを書きました。
コード
Messaging APIで「メッセージを送る」
「前回の出力結果(下記)を、VSCodeを開かなくても見れるようにしたい」と思いました。
(1) ['吉田 正尚']
(11) ['日付:5月25日', '対戦チーム:エンゼルス', '打数:4', '安打:1', '本塁打:0', '打点:0', '得点:1', '三振:0', '四球:0', '死球:0', '打席結果:一併打、中2、二ゴロ、一ゴロ']
はじめはTwitter APIを使ってツイート&通知しようと思いましたが、全くうまくいかなかったのでMessaging API(LINE)を使うことにしました。
以下がMessaging APIでブロードキャストメッセージ(友達登録されている全員に送るメッセージ)を送信するコードです。
import line from "@line/bot-sdk";
import * as dotenv from "dotenv";
dotenv.config();
const sendMessageToLINE = (messageText) => {
const config = { channelAccessToken: process.env.ACCESS_TOKEN };
const client = new line.Client(config);
client
.broadcast({ type: "text", text: messageText }) //NOTE: 個別で送る場合はpushMessageメソッドを使う。引数は(userID, message)
.then(() => {
console.log("Message was sent successfully");
})
.catch((err) => {
console.error("An error occurred while sending message:", err);
});
};
部分ごとに分解します。
①必要なものをインポート
import line from "@line/bot-sdk";
import * as dotenv from "dotenv";
dotenv.config();
②チャンネルアクセストークン(.envファイルに保存)をもとにインスタンスを生成、
アクセストークンはLINE Developersから確認
const config = { channelAccessToken: process.env.ACCESS_TOKEN };
const client = new line.Client(config);
③broadcastメソッドを使い、登録されている全員にメッセージを送信。
client.broadcast({ type: "text", text: messageText })
//messageTextは送りたいメッセージの内容
//個別に送りたい場合は↓
client.pushMessage(userId, {type: "text", text: messageText})
//自分のuserIdは"LINE Developers > チャネル基本設定 > あなたのユーザーID"で確認
//メッセージ送信元のuserIdはevent
ただし、これらは一方的な「送信」であって、「メッセージの送信を検知し返信する」といったことにはサーバーが必要です。
サーバーをたてる(Glitch)
無料サーバーGlitchを使用しました。見よう見まねで、解説できることがないのでコードだけ置いておきます。
// 必要なモジュールをインポートします
const fastify = require("fastify")({ logger: false });
const line = require("@line/bot-sdk");
const samuraiList = require("./dbForScrape.js");
const scrapeToArray = require("./scrape.js")
const testLog = require("./test.js");
const sendToLineAsReply = require("./main.js")
// LINEの設定情報をロードします
const CONFIG = {
channelAccessToken: process.env.ACCESS_TOKEN,
channelSecret: process.env.SECRET_KEY,
};
// application/jsonのリクエストボディを文字列として扱うように設定します
fastify.addContentTypeParser(
"application/json",
{ parseAs: "string" },
function (req, body, done) {
done(null, body);
}
);
// /webhook へのPOSTリクエストを処理します
fastify.post("/webhook", async function (request, reply) {
// リクエストがLINEから来たものか確認します
const rawBody = request.body; // 文字列としてのリクエストボディ
const signature = request.headers["x-line-signature"]; // LINEからの署名
if (!line.validateSignature(rawBody, CONFIG.channelSecret, signature)) {
reply.code(403).send("Unauthorized"); // 署名が一致しない場合、403エラーを返します
return;
}
// リクエストボディをJavaScriptオブジェクトに変換します
const parsedBody = JSON.parse(rawBody);
// 各イベントを処理します
parsedBody.events.map((event) => {
//返信する関数にreplyTokenとmessage.text(送られてきたメッセージ文)を送る
replyToLine(event.replyToken, event.message.text)
});
// 200 OKを返します
reply.code(200).send("OK");
});
// サーバーを起動します
fastify.listen(
{ port: process.env.PORT, host: "0.0.0.0" },
function (err, address) {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`Your app is listening on ${address}`);
}
);
fastify.get("/", async (request, reply) => {
return { hello: "world" };
});
Messaging APIで「キーワードを検出し返信する」
汚いコードですがこれもとりあえず置いておきます(雑
sortArray / convertToDate 云々と書いているのは、最新の試合順に並び替えてリプライを送るためです。
const line = require("@line/bot-sdk");
const samuraiList = require("./dbForScrape.js");
const scrapeToArray = require("./scrape.js");
const formatToReply = require("./formatToReply.js");
const convertToDate = require("./convertToDate.js");
const CONFIG = {
channelAccessToken: process.env.ACCESS_TOKEN,
channelSecret: process.env.SECRET_KEY,
};
const client = new line.Client(CONFIG);
const scrapeAndFormat = async (url, cssSelector) => {
const scrapedData = await scrapeToArray(url, cssSelector);
const formatedData = formatToReply(scrapedData);
return formatedData;
};
const replyToLine = async (token, message) => {
let replyMessage = "";
if (message.includes("最新")) {
let sortArray = [];
for (let i in samuraiList) {
sortArray.push(
await scrapeToArray(samuraiList[i].url, samuraiList[i].selector)
);
}
sortArray.sort(
(a, b) =>
convertToDate(a.recentStats.日付) - convertToDate(b.recentStats.日付)
);
for (let i in sortArray) {
const formatedData = formatToReply(sortArray[i]);
console.log(i);
console.log(formatedData)
client.replyMessage(token, { type: "text", text: formatedData });
}
} else {
client.replyMessage(token, { type: "text", text: "don't match any case" });
}
};
課題:replyTokenの失効問題
replyTokenは30秒ほどで失効するらしく、スクレイピングが間に合わずreplyが途切れてしまうという問題が発生しました。
replyMessageメソッドではなくpushMessageメソッドを使えばreplyTokenを使わないので、良いのでは?と思ったのですが、以下の表にあるようにpushMessageを使うと一定数までのメッセージしか送信できません。
(しかも昨日改定されて、送信上限が1000通→200通に激減)
そこで、「データベースを建てて、定期的にスクレイピングしたデータを保存し、replyはデータベースを参照して行う」という試みをしてみようと思います。データベースの構築と運用は初めてです。次回の記事で書こうと思います。
雑な記事ですみません。ただ、次回もこんな感じで書くかもしれません。ご容赦を。
ほな、また。
記録:初挑戦:API, Messaging API, require, サーバーの利用, replyMessage