echobot/
echobot.rs

1use std::{env, sync::Arc};
2
3use actix_web::{middleware::Logger, rt::spawn, web, App, HttpResponse, HttpServer, Responder};
4
5use line_bot_sdk::{
6    extractor::CustomHeader,
7    models::message::MessageObject,
8    models::webhook_event,
9    models::{
10        message::text::TextMessage,
11        webhook_event::{Event, Message, Root, Text},
12    },
13    Client,
14};
15use log::info;
16
17use serde::{Deserialize, Serialize};
18
19pub struct AppState {
20    line_client: Arc<Client>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25struct ReplyMessage {
26    reply_token: String,
27    messages: Vec<MessageObject>,
28}
29
30fn get_signature_from_header(custom_header: &CustomHeader) -> &str {
31    &custom_header.x_line_signature
32}
33
34fn verify_signature(client: &Client, signature: &str, context: &str) {
35    client.verify_signature(signature, context).unwrap()
36}
37
38fn get_webhook_event(context: &str) -> Root {
39    serde_json::from_str(context).unwrap()
40}
41
42async fn webhook_handler(context: &webhook_event::Root, client: &Client) -> HttpResponse {
43    for event in &context.events {
44        reply(event, client).await;
45    }
46    HttpResponse::Ok().json("Ok")
47}
48
49fn get_text_message(event: &Event) -> Option<&Text> {
50    match &event.message {
51        Some(Message::Text(message)) => Some(message),
52        _ => None,
53    }
54}
55
56fn create_text_message(text: &str) -> MessageObject {
57    MessageObject::Text(TextMessage::builder().text(text).build())
58}
59
60async fn reply(event: &Event, client: &Client) {
61    let reply_token = event.reply_token.clone().unwrap();
62
63    let messages = match get_text_message(event) {
64        Some(text) => vec![create_text_message(&text.text)],
65        None => vec![create_text_message(
66            "Events other than text messages are not supported",
67        )],
68    };
69
70    client.reply(&reply_token, messages, None).await.unwrap();
71}
72
73pub async fn handler(
74    context: String,
75    custom_header: CustomHeader,
76    app_state: web::Data<AppState>,
77) -> impl Responder {
78    info!("Request body: {}", context);
79
80    let client = Arc::clone(&app_state.line_client);
81
82    let signature = get_signature_from_header(&custom_header);
83
84    verify_signature(&client, signature, &context);
85
86    let webhook_event = get_webhook_event(&context);
87
88    spawn(async move { webhook_handler(&webhook_event, &client).await });
89
90    HttpResponse::Ok().body("")
91}
92
93pub fn router(cfg: &mut web::ServiceConfig) {
94    cfg.route("/webhook", web::post().to(handler));
95}
96
97#[actix_web::main]
98async fn main() -> std::io::Result<()> {
99    env_logger::init();
100    HttpServer::new(move || {
101        App::new()
102            .wrap(Logger::default())
103            .configure(router)
104            .app_data(web::Data::new(AppState {
105                line_client: Arc::new(Client::new(
106                    env::var("CHANNEL_ACCESS_TOKEN").unwrap(),
107                    env::var("CHANNEL_SECRET").unwrap(),
108                )),
109            }))
110    })
111    .bind(("localhost", 8080))?
112    .run()
113    .await
114}