iT邦幫忙

2025 iThome 鐵人賽

DAY 6
1
Modern Web

Line Bot × NestJS:30 天開發日記系列 第 6

Day 6:整合第三方 API 天氣查詢服務

  • 分享至 

  • xImage
  •  

2025 鐵人賽背景圖

前言

Day 5 帶大家了解 LINE Bot 能接收使用者傳遞的「6+1」種訊息類型。之所以這樣強調,是因為 File 類型僅能在群組聊天中使用,這個限制我自己也經常忘記,所以習慣用「6+1」來提醒自己。

今天我們將整合 OpenWeatherMap API。當使用者分享位置資訊時,我們的 LINE Bot 將能夠自動查詢並回傳該地點的天氣資訊,實現智能天氣查詢的服務。

本日程式碼的範例連結

前置作業:取得 API Key

在開始串接 OpenWeatherMap API 之前,需要先完成帳號註冊。造訪 OpenWeatherMap 官方網站進行免費帳號註冊。完成註冊後登入帳號並前往帳號管理頁面,即可找到專屬的 API Key。

OpenWeatherMap API Key 畫面

LINE Bot 天氣服務開發說明

本次開發會調整 Day 5 messageEventHandlerMap 函式。當使用者透過 LINE 傳送地理位置訊息時,系統會擷取經緯度座標資訊向 OpenWeatherMap API 發送請求,取得該位置的當日天氣資訊,並將原始資料轉換為包含溫度、濕度、天氣狀況等詳細資料的格式化訊息,最後透過 LINE Bot 回覆功能傳送給使用者。

Step 1:設定 env WEATHER_BASE_URL & WEATHER_API_KEY

API KEY 存放在環境變數檔案中統一管理

.env

WEATHER_BASE_URL=https://api.openweathermap.org/data/2.5/weather
WEATHER_API_KEY=你的 OpenWeatherMap API 金鑰

Step 2:line-webhook.service 初始化 WEATHER 變數

line-webhook.service 中初始化天氣相關的配置變數,後續將透過這些變數撰寫 API 請求函式,實現統一的環境變數管理。採用動態讀取方式,若環境變數不存在則會拋出錯誤

line-webhook.service.ts:

// 略
import { ConfigService } from '@nestjs/config';

@Injectable()
export class LineWebhookService {
  private readonly WEATHER_API_BASE_URL: string;
  private readonly WEATHER_API_KEY: string;

  constructor(
    // 略
    private readonly configService: ConfigService
  ) {
    // 略
    this.WEATHER_API_BASE_URL =
      this.configService.getOrThrow<string>('WEATHER_BASE_URL');
    this.WEATHER_API_KEY =
      this.configService.getOrThrow<string>('WEATHER_API_KEY');
  }
  // 略
}

Step 3:安裝 HTTP module 相關套件

pnpm i --save @nestjs/axios axios

Step 4:配置 HTTP Module

line-webhook.module.ts

// 略
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [HttpModule],
  // 略
})
export class LineWebhookModule {}

Step 5:設置 Weather API 回傳物件型別

定義 OpenWeatherMap API 回傳資料的型別介面,僅包含後續回覆訊息所需的主要參數

line-webhook.types.ts

export interface CurrentWeatherResponse {
  cod: number;
  message?: string;
  main: {
    temp: number;
    feels_like: number;
    humidity: number;
  };
  weather: {
    id: number;
    main: string;
    description: string;
    icon: string;
  }[];
  name: string;
  coord: {
    lon: number;
    lat: number;
  };
}

Step 6: 獨立 Weather API 請求函式

建立專門處理天氣 API 請求的私有方法,並整合 catchError 錯誤處理機制,確保當 API 請求失敗時能夠適當地捕獲異常並向外拋出具體的錯誤訊息

line-webhook.service.ts

  async #fetchWeatherData(latitude: number, longitude: number) {
    // 查詢參數
    const queryParams = {
      lat: latitude,
      lon: longitude,
      appid: this.WEATHER_API_KEY,
      units: 'metric',
      lang: 'zh_tw',
    };

    // 接收處理完的天氣數據
    const responseData = await firstValueFrom(
      this.httpService
        .get(this.WEATHER_API_BASE_URL, { params: queryParams })
        .pipe(
          catchError((err: AxiosError) => {
            return throwError(
              () =>
                new Error(
                  `Weather API request failed: ${JSON.stringify(err.response?.data)}`,
                ),
            );
          }),
        ),
    );

    return responseData.data;
  }

Step 7: 撰寫天氣 API 回傳訊息格式化函式

建立私有方法將 OpenWeatherMap API 回傳的原始資料轉換為使用者易讀的訊息格式

line-webhook.service.ts

#formateWeatherInfo(weatherData: CurrentWeatherResponse) {
const locationName = weatherData.name || '該區域';
const temp = weatherData.main.temp;
const feelsLike = weatherData.main.feels_like;
const humidity = weatherData.main.humidity;
const description = weatherData.weather[0]?.description || '未知天氣狀況';

return `🌤️ ${locationName} 的天氣:\n🌡️ 溫度:${temp}°C (體感:${feelsLike}°C)\n💧 濕度:${humidity}%\n☁️ 狀況:${description}`;
}

Step 8:修改 MessageEventHandlerMap 型別支援非同步處理

更新型別定義以支援 Promise 回傳值,讓訊息處理器能夠執行非同步操作。

line.webhook.types.ts

export type MessageEventHandlerMap = {
  [K in EventMessage['type']]: (
    event: Extract<EventMessage, { type: K }>,
  ) => Promise<string>; // 改成使用 Promise 處理非同步操作
};

Step 9:修改 location 訊息處理邏輯整合天氣查詢

更新 Message Event 的 location 訊息處理器,整合天氣 API 查詢功能,並重構 -handleMessageEvent 方法以支援完整的非同步處理機制。

line-webhook.service.ts

  private async handleMessageEvent(event: MessageEvent): Promise<void> {
    const messageEventHandlerMap = {
      // 略
      location: async (message) => {
        const { address, longitude, latitude } = message;
        const defaultMsg = `📍 收到位置訊息\n🏠 地址:${address}\n🧭 精度:${longitude}\n🧭 緯度:${latitude}`;
          
        const weatherData: CurrentWeatherResponse =
          await this.#fetchWeatherData(latitude, longitude);
        const weatherInfoText = this.#formateWeatherInfo(weatherData);
          
        return `${defaultMsg}\n\n${weatherInfoText}`;
      },
    } satisfies Partial<MessageEventHandlerMap>;

    let replyMessage = '✨ 感謝你的訊息,我們已經收到了!';
    const handler = messageEventHandlerMap[event.message.type];
    // await 很重要喔,因為型別上我們已經改成使用 Promise 處理
    if (handler) replyMessage = await handler(event.message);

    await this.lineClient.replyMessage({
      replyToken: event.replyToken,
      messages: [{ type: 'text', text: replyMessage }],
    });
  }

成果展現

Line Bot 回覆天氣服務 API 資料

本日結語

今天成功整合了 OpenWeatherMap API,讓 LINE Bot 具備天氣查詢功能。現在只要使用者分享位置,就能立即回傳當地的天氣資訊。從單純的訊息回覆進化到提供實用服務,LINE Bot 展現作為溝通工具的潛力,至於要整合什麼服務,完全由開發者的創意決定。


上一篇
Day 5:整合 Pino Logger 提升 LINE Bot 專案的可維護性
下一篇
Day 7:Joi 環境變數統一驗證,重構 LINE Bot 天氣服務模組
系列文
Line Bot × NestJS:30 天開發日記8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言