Day 5 帶大家了解 LINE Bot 能接收使用者傳遞的「6+1」種訊息類型。之所以這樣強調,是因為 File
類型僅能在群組聊天中使用,這個限制我自己也經常忘記,所以習慣用「6+1」來提醒自己。
今天我們將整合 OpenWeatherMap API
。當使用者分享位置資訊時,我們的 LINE Bot 將能夠自動查詢並回傳該地點的天氣資訊,實現智能天氣查詢的服務。
本日程式碼的範例連結
在開始串接 OpenWeatherMap API
之前,需要先完成帳號註冊。造訪 OpenWeatherMap 官方網站進行免費帳號註冊。完成註冊後登入帳號並前往帳號管理頁面,即可找到專屬的 API Key。
本次開發會調整 Day 5 messageEventHandlerMap
函式。當使用者透過 LINE 傳送地理位置訊息時,系統會擷取經緯度座標資訊向 OpenWeatherMap API 發送請求,取得該位置的當日天氣資訊,並將原始資料轉換為包含溫度、濕度、天氣狀況等詳細資料的格式化訊息,最後透過 LINE Bot 回覆功能傳送給使用者。
將
API KEY
存放在環境變數檔案中統一管理
.env
WEATHER_BASE_URL=https://api.openweathermap.org/data/2.5/weather
WEATHER_API_KEY=你的 OpenWeatherMap API 金鑰
在
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');
}
// 略
}
pnpm i --save @nestjs/axios axios
line-webhook.module.ts
// 略
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [HttpModule],
// 略
})
export class LineWebhookModule {}
定義 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;
};
}
建立專門處理天氣 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;
}
建立私有方法將
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}`;
}
更新型別定義以支援 Promise 回傳值,讓訊息處理器能夠執行非同步操作。
line.webhook.types.ts
export type MessageEventHandlerMap = {
[K in EventMessage['type']]: (
event: Extract<EventMessage, { type: K }>,
) => Promise<string>; // 改成使用 Promise 處理非同步操作
};
更新 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 }],
});
}
今天成功整合了 OpenWeatherMap API
,讓 LINE Bot 具備天氣查詢功能。現在只要使用者分享位置,就能立即回傳當地的天氣資訊。從單純的訊息回覆進化到提供實用服務,LINE Bot 展現作為溝通工具的潛力,至於要整合什麼服務,完全由開發者的創意決定。