0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SamuraiStats】2. サーバーを建ててLINEメッセージを送る

Last updated at Posted at 2023-06-02

前回記事

今回はMessaging API(LINEメッセージを送るAPI)とGlitch(無料サーバー)を使って、「『最新』というキーワードに対し最新の試合成績を返す」というコードを書きました。

<実際の動作>
image.png

コード

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を使用しました。見よう見まねで、解説できることがないのでコードだけ置いておきます。

server.js
// 必要なモジュールをインポートします
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通に激減)
image.png
image.png
そこで、「データベースを建てて、定期的にスクレイピングしたデータを保存し、replyはデータベースを参照して行う」という試みをしてみようと思います。データベースの構築と運用は初めてです。次回の記事で書こうと思います。

雑な記事ですみません。ただ、次回もこんな感じで書くかもしれません。ご容赦を。
ほな、また。

記録:初挑戦:API, Messaging API, require, サーバーの利用, replyMessage

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?