【GAS】クラス化でLINE-botを爆速で作成する

LINEのbotを作ると、記述が冗長になったり条件分岐が多すぎたりしたので、クラスにして簡単に書けるようにしました。

<h1>ソースコード</h1>
おうむ返しbotを作成します。

```js
const client = new Client_("ACCESS_TOKEN");
client.on("message",event => {
    const text = event.message.text;
    event.reply(text);

});

function doPost(e){
    const data = JSON.parse(e.postData.contents);
    client.run(data);
}
```

完成しました。(1分)

ただし、これではClient_が定義されていないので、次のソースコードをコピペしてください。
<h1>クラスの用意</h1>

```js
class Client_{

    constructor(ACCESS_TOKEN){
        this.token = ACCESS_TOKEN;
        this.users = new UserManager_(this);
        this.followers = new FollowerManger(this);
        this.https = HttpsRequest_(this);
        this.functions = {};
    }

    on(type,func){
        const arr = this.functions[type] || [];
        arr.push(func);
    }

    onFailed(func){ //funcの引数は、event,err

        this.failedHandler = func;

    }
    run(data){

        const [events] = data.events;
        this.functions[events.type].forEach(f => {
            try{
                f(new Events_(events,this));
            }catch(err){
                this.failedHandler(events,err);
            }
        });

    }

}


class FollowerManger{

    constructor(client){
        this.client = client;
    }
    /**
     * 
     * @param {string} userId 
     * @returns {User_}
     */
    fetch(userId){

        const endpoint = "https://api.line.me/v2/bot/profile/" + userId;
        
        const userdata = this.client.https.get(endpoint);
        // {
        //     "displayName": "LINE taro",
        //     "userId": "U4af4980629...",
        //     "language": "en",
        //     "pictureUrl": "https://obs.line-apps.com/...",
        //     "statusMessage": "Hello, LINE!"
        //   }

        return new User_(client,userdata);
    }
    /**
     * 
     * @param {object} {limit,start} 
     */
    getAllFollowers({limit,start}){
        const endpoint = "https://api.line.me/v2/bot/followers/ids";
        const requestUrl = new URLSearchParams(endpoint).setParams({limit,start}).toString();
        return this.client.https.get(requestUrl);
        // {
        //     "userIds": ["U4af4980629...", "U0c229f96c4...", "U95afb1d4df..."],
        //     "next": "yANU9IA..."
        //   }

    }

}

class UserManager_{

    constructor(client){
        this.client = client;

    }
    /**
     * 
     * @param {string} userId 
     * @returns {User_}
     */
    fetch(userId){
        const endpoint = "https://api.line.me/v2/bot/profile/" + userId;
        
        const userdata = this.client.https.get(endpoint);
        // {
        //     "displayName": "LINE taro",
        //     "userId": "U4af4980629...",
        //     "language": "en",
        //     "pictureUrl": "https://obs.line-apps.com/...",
        //     "statusMessage": "Hello, LINE!"
        //   }

        return new User_(this.client,userdata);
    }

}


class User_{
    /**
     * 
     * @param {Client_} client 
     * @param {JSON} userdata 
     */
    constructor(client,userdata){

        this.displayName = userdata.displayName;
        this.userId = userdata.userId;
        this.language = userdata.language;
        this.pictureUrl = userdata.pictureUrl;
        this.statusMessage = userdata.statusMessage;
        this.client = client;

    }

    updateInfo(){

        const endpoint = "https://api.line.me/v2/bot/profile/" + this.userId;
        
        const userdata = this.client.https.get(endpoint);

        return new User_(this.client,userdata);

    }

    toString(){
        return `<m userId="${this.userId}">`
    }

}


class Events_{

    constructor(events,client){

        this.client = client;
        this.rawdata = events;
        this.type = events.type;
        this.replyToken = events.replyToken;
        this.source = new User_(client,events.source);
        this.timestamp= events.timestamp;
        this.message = events.message;
        this.postback = events.postback;
        this.follow = events.follow;

    }

    reply(messages){
        if(typeof messages === "string"){
            messages = {
                type : "text",
                text : messages
            };
        }

        if(!Array.isArray(messages)){
            messages = [messages];
        }
        
        const endpoint = "https://api.line.me/v2/bot/message/reply";
        const payload=  {
            messages : messages,
            replyToken: this.replyToken
          };

        this.client.post(endpoint,payload)
        
    }



}



class HttpsRequest_{

    constructor(client){
        this.client = client
    }
    /**
     * 
     * @param {string} url 
     * @param {object} params 
     * @returns {JSON}
     */
    get(url,params){

        const requestUrl = new URLSearchParams(url).setParams(params).toString();
        const options = {
            method: 'get',
            contentType: 'application/json',
            headers: {
              Authorization: 'Bearer ' + this.client.token
            }
          };
        const result =  UrlFetchApp.fetch(requestUrl,options).getContentText();
        return JSON.parse(result);

    }

    /**
     * 
     * @param {string} url 
     * @param {object} payload 
     */
    post(url,payload){

        const options = {
            method: 'post',
            contentType: 'application/json',
            headers: {
              Authorization: 'Bearer ' + this.client.token
            },
            payload: JSON.stringify(payload) 
        };
        const result = UrlFetchApp.fetch(url,options).getContentText();
        return JSON.parse(result);

    }


}

class URLSearchParams{
    constructor(url){

        this.originUrl = url.split("?")?.[0];
        this.param = url.split("?")?.[1] || "";

    }

    setParams(obj){
        this.param = Object.entries(obj).filter(arr => arr[1]).map(arr => arr.join("=")).join("&");
    }

    toString(){
        return this.originUrl + "?" +  this.param;
    }
}
```
(記法はdiscord.jsリスペクトです。)

<h1>使い方(簡易)</h1>
詳細は次回以降に回します。


```js
//メイン
const client = new Client_("ACCESS_TOKEN");
function doPost(e){
    const data = JSON.parse(e.postData.contents);
    client.run(data);
}

//メッセージイベントハンドラのセット。
client.on("message",event => {

    //eventは、e.postData.contents.events[0]とほぼ同等。
    const text = event.message.text; //"こんにちは"

    //rawdataはevent.rawdata。doPostで受け取ったJSONがそのまま入っている

    //replyメソッドでquick-replyが可能。
    event.reply(text);

});
//以下の内容と同等の処理をしている。
function doPost(e){
    const data = JSON.parse(e.postData.contents);
    const event = data.events[0];

    if(event.type !== "message") return;

    const text = event.message.text;
    const replyToken = event.replyToken;

    UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
      'headers': {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': 'Bearer ' + ACCESS_TOKEN,
      },
      'method': 'post',
      'payload': JSON.stringify({
        'replyToken': replyToken,
        'messages': [{
          'type': 'text',
          'text': text,
        }]
      })
    });
}

//フォローイベント
client.on("follow",event => {});
//アンフォローイベント
client.on("unfollow",event => {});
//postbackイベント
client.on("postback",event => {});
//その他イベントも同様に記述可能。

//失敗時のハンドラ
client.onFailed((event,err) => {})

//ユーザー取得
client.users.fetch("U4af4980629...");

//友達ID取得
client.followers.getAllFollowers({limit,start});

//メンション
client.on("follow",event => {
    const user = event.source; /**@type {User_} */
    const message = "登録ありがとうございます！";
    event.reply(user.toString() + message); //メンションつきメッセージ
})

//その他、Access tokenが必要なLINEのAPIを叩くとき
//get
client.https.get("url",{param1 : value1});
//post
const payload = {};
client.https.post("url",payload); 

```