Open20

ポーカーチップ計算LINE Botを作りたい

滝 和弥滝 和弥

グループへの参加(Bot自身が参加)イベントはこんな感じで取れる

    switch (event.source.type) {
      case "group": {
        const groupId = event.source.groupId;
        const groupSummary = await lineClient.getGroupSummary(groupId);

滝 和弥滝 和弥

最初に「ディーラー」とメッセージを送った人がディーラー役になる、というフローでええか

(どことなくライアーゲーム感がある)

滝 和弥滝 和弥

いや、

  1. 最初は2人(ディーラー役とBot)でグループを作る
  2. ディーラー役のユーザーが「ディーラー」とメッセ
  3. Botがそれを検知して、メッセージからUserIDを取得
  4. DBにグループIDとディーラー役のIDを保存
  5. メンバーを追加していく
  6. メンバー追加イベントからはユーザーのIDを取得できるため、DBにメンバーを登録していく

これ良さげ

滝 和弥滝 和弥

realtime database を使おうとしたけどFirestoreの方が使いやすいからFirestore使う

滝 和弥滝 和弥

これでgroupIdのドキュメントをFirestoreに作成できる

    switch (event.source.type) {
      case "group": {
        const groupId = event.source.groupId;
        const groupsRef = databaseClient.collection("groups");
        await groupsRef.doc(groupId).create({dealer: null})
滝 和弥滝 和弥

Botがグループから退室したときはFirestoreから削除

    switch (event.source.type) {
      case "group": {
        const groupId = event.source.groupId;
        const groupRef = databaseClient.collection("groups").doc(groupId);
        await groupRef.delete();
滝 和弥滝 和弥

LINE グループの作成、削除をやりすぎたのか、グループが作れなくなった。
1ヶ月グループ作成できないらしい…

滝 和弥滝 和弥

しょうがないのでLIFFの方にとりかかる。

npx @line/create-liff-app

Create LIFF Appを使ってプロジェクトを作成する。

フレームワークはNext.jsを使ってみる。

滝 和弥滝 和弥

LINE関連の処理をここでまとめて

import liff from "@line/liff";
import {LiffMockPlugin} from "@line/liff-mock";
import {LIFF_ID} from "@/utils/secrets";
import {Profile} from "@liff/get-profile";

export const liffInit = async () => {
  if (process.env.NODE_ENV === 'development') {
    liff.use(new LiffMockPlugin())
    // @ts-ignore
    await liff.init({ liffId: LIFF_ID, mock: true })
  } else {
    await liff.init({ liffId: LIFF_ID })
  }
}

export const liffLogin = async () => {
  if (!liff.isInClient()) liff.login();
}

export const liffGetProfile = async (): Promise<Profile> => {
  return await liff.getProfile();
}

export const liffIsLoggedIn = (): boolean => {
  return liff.isLoggedIn();
}

こんな感じで使う

import {liffGetProfile, liffInit, liffIsLoggedIn, liffLogin} from "@/lib/line";
import {Profile} from "@liff/get-profile";
import {useEffect, useState} from "react";
import Head from "next/head";

export default function UserProfile() {
  const [profile, setProfile] = useState<Profile | null>(null)

  useEffect(() => {
    (async () => {
      await liffInit();
      if (!liffIsLoggedIn()) {
        await liffLogin();
      }
      const profile = await liffGetProfile();
      setProfile(profile);
    })()
  }, [])

滝 和弥滝 和弥

リロードしたらエラーになった。

error - ReferenceError: self is not defined
    at Object.<anonymous>
滝 和弥滝 和弥

これはうまくいく

    import("@line/liff")
      .then((liff) => liff.default)
      .then((liff) => {
        console.log("LIFF init...");
        liff
          .init({ liffId: process.env.NEXT_PUBLIC_LIFF_ID! })
          .then(() => {
            console.log("LIFF init succeeded.");
            setLiffObject(liff);
          })
          .catch((error: Error) => {
            console.log("LIFF init failed.");
            setLiffError(error.toString());
          });
      });
滝 和弥滝 和弥

結局よくわからないしNext.jsはオーバースペックな気がするので、大人しくReactを使うことにする

滝 和弥滝 和弥

Viteの場合の環境変数の取得方法

export const LIFF_ID = import.meta.env.VITE_LIFF_ID;
滝 和弥滝 和弥

グループとそこに所属するユーザーの情報を取得するコード

export default function Pot() {
  const [pot, setPot] = useState<0 | number>(0);
  useEffect(() => {
    (async () => {
      const group = await getGroupById("hoge");
      setPot(group?.pot!)
      console.log(group);
    })();
  }, []);

  return (
    <>
      <h1>{pot}</h1>
    </>
  )
}
export async function getGroupById(groupId: string): Promise<Group | null> {
  try {
    // 指定したIDのgroupドキュメントを取得
    const groupDocRef = doc(db, 'groups', groupId);
    const groupDocSnap = await getDoc(groupDocRef);

    if (groupDocSnap.exists()) {
      // groupドキュメントのデータを取得
      const groupData = groupDocSnap.data();

      // サブコレクションusersを取得
      const usersCollectionRef = collection(db, 'groups', groupId, 'users');
      const usersQuery = query(usersCollectionRef);
      const usersQuerySnapshot = await getDocs(usersQuery);
      console.log('usersQuerySnapshot: ', usersQuerySnapshot.docs)
      const usersData: User[] = usersQuerySnapshot.docs.map((userDoc) => {
        // usersドキュメントのデータを取得
        const userData = userDoc.data();
        return {
          id: userDoc.id,
          name: userData.name,
          isDealer: userData.isDealer,
          stack: userData.stack
        };
      });

      // Group型に変換して返す
      return {
        id: groupDocSnap.id,
        pot: groupData.pot,
        users: usersData
      };
    } else {
      console.log('Group does not exist.');
      return null;
    }
  } catch (error) {
    console.log('Error getting group and users by ID: ', error);
    return null;
  }
}

滝 和弥滝 和弥

LIFFアプリを開かせるためのクイックリプライメッセージの作り方

export const makeQuickReplyMessage = (): Message => {
  const liffUrl = `https://your-liff-url`;

  const quickReplyItem = {
    type: "action",
    imageUrl: "https://your-image-url",
    action: {
      type: "uri",
      label: "チップ計算を開く",
      uri: liffUrl
    }
  } as QuickReplyItem;

  const quickReply: QuickReply = {
    items: [
      quickReplyItem
    ]
  }

  return {
    type: "text",
    text: "LIFFアプリを開いてください。",
    quickReply: quickReply
  }
}

滝 和弥滝 和弥
export const liffGetGroupId = async () => {
  const context = liffGetContext();
  if (context && context.type === 'group') {
    return context.groupId;
  }
}

liffアプリからこれでGroupIdを取得できると思ったが、このプロパティは廃止されたらしい。
なので、urlのクエリパラメータで送る必要がありあそうだ

https://developers.line.biz/ja/reference/liff/#get-context