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}