Node.jsでTypeScriptの実行を試しつつHonoでLINE Botも作ってみるメモ

Node.jsでTypeScript実行がサポートされた（実験的に）模様なので試してみます。

## Node.jsでTypeScriptの実行

v23系

```bash
$ node -v
v23.5.0
```

```hello.ts
interface User {
    name: string;
    age: number;
}

function greet(user: User): string {
    return `Hello, ${user.name}! You are ${user.age} years old.`;
}

const user: User = {
    name: "田中さん",
    age: 25
};

console.log(greet(user));
```

`--experimental-transform-types`を付けて`.ts`ファイルを実行

```bash
$ node --experimental-transform-types hello.ts
Hello, 田中さん! You are 25 years old.
```

追記: `v23.6.0`にしたらフラグいらなくなりました。 （記事公開した直後)

```bash
$ node server.ts 
Hello, 田中さん! You are 25 years old.
```

という感じで実行できました。

ただこれだけだとカンタンすぎたので他にも試してみます。

## HonoでLINE Bot作ってオウム返し

シンプルなオウム返しBOTを作ります。

> ![スクリーンショット 2025-01-08 19.02.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/daae570f-9c51-dc7e-68de-627458301818.png)

利用準備

```bash
$ npm init -y
$ npm pkg set type=module
$ touch server.ts
```

npmパッケージのインストール

```bash
$ npm i @hono/node-server @line/bot-sdk hono-linebot-middleware
```

`server.ts`には以下のコードを利用

```server.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

import * as line from '@line/bot-sdk'
import { lineBotMiddleware } from 'hono-linebot-middleware'

const app = new Hono()
  
const lineConfig = {
    channelSecret: 'チャンネルシークレット'
}
  
const client = new line.messagingApi.MessagingApiClient({
  channelAccessToken: 'チャンネルアクセストークン'
})

async function handleEvent(event: line.WebhookEvent) {
    console.log(event);
    if (event.type !== 'message' || event.message.type !== 'text') return;
    
    await client.replyMessage({
        replyToken: event.replyToken,
        messages: [{
            type: 'text',
            text: `え、「${event.message.text}」って言った？` //聞き返すBOT
        }]
    })
}

app.get('/', (c) => c.text('LINE Bot is running'))

app.post('/callback', lineBotMiddleware(lineConfig), async (c) => {
    try {
        const { events } = c.get('body')
        await Promise.all(events.map(handleEvent))
        return c.json({ status: 'success' })  // 必ず Response を返す
    } catch (err) {
        console.error(err)
        return c.json({ status: 'error' }, 500)  // エラー時も Response を返す
    }
  })

serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`)
})
```

`--experimental-transform-types`をつけて実行

```bash
$ node --experimental-transform-types server.ts
```

> こんな感じ
> ![スクリーンショット 2025-01-08 19.07.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/d5c3ff07-c6d2-97d5-a3c2-0e716284d541.png)

↑にも追記しましたが、v23.6.0だとフラグなしでいけます。

```
$ nvm use 23
Now using node v23.6.0 (npm v10.9.2)
```

```bash
$ node server.ts
```

## hono-linebot-middleware

expressな書き方を参考にしれっと作ってみました。

https://www.npmjs.com/package/hono-linebot-middleware

Honoのmiddlewareの書き方お作法とかありそうですがそこまでちゃんとみてないので`hono-line-middleware`的な名前は避けて空けておきました。

express版っぽく書きたいなと思ってこの書き方を参考にしてます。

```ts
import * as line from '@line/bot-sdk'
import express from 'express'

const config = {
    channelSecret: process.env.CHANNEL_SECRET || '',
};
  
// create LINE SDK client
const client = new line.messagingApi.MessagingApiClient({
    channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || ''
});

// create Express app
const app = express();

// 型定義を追加
interface TextMessage {
  type: 'text';
  text: string;
}

interface Event {
  type: string;
  message: {
    type: string;
    text: string;
  };
  replyToken: string;
}

// event handler
async function handleEvent(event: Event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return null;
  }

  // create an echoing text message
  const echo: TextMessage = { type: 'text', text: event.message.text };

  // use reply API
  return client.replyMessage({
    replyToken: event.replyToken,
    messages: [echo],
  });
}

// register a webhook handler with middleware
app.post('/callback', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});


// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`listening on ${port}`));
```