{"id":21906260,"url":"https://github.com/alfex4936/rust-kakaotalk-chatbot","last_synced_at":"2026-04-20T05:04:19.404Z","repository":{"id":151852421,"uuid":"494306892","full_name":"Alfex4936/Rust-KakaoTalk-Chatbot","owner":"Alfex4936","description":"Rust언어로 카카오톡 챗봇 만들기 튜토리얼","archived":false,"fork":false,"pushed_at":"2022-06-17T02:14:02.000Z","size":92,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-27T07:41:45.541Z","etag":null,"topics":["chatbot","kakaotalk","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Alfex4936.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-20T03:25:35.000Z","updated_at":"2022-06-15T14:44:41.000Z","dependencies_parsed_at":"2023-05-12T11:45:45.855Z","dependency_job_id":null,"html_url":"https://github.com/Alfex4936/Rust-KakaoTalk-Chatbot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alfex4936%2FRust-KakaoTalk-Chatbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alfex4936%2FRust-KakaoTalk-Chatbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alfex4936%2FRust-KakaoTalk-Chatbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alfex4936%2FRust-KakaoTalk-Chatbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Alfex4936","download_url":"https://codeload.github.com/Alfex4936/Rust-KakaoTalk-Chatbot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244920342,"owners_count":20532003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chatbot","kakaotalk","rust"],"created_at":"2024-11-28T16:43:17.399Z","updated_at":"2026-04-20T05:04:19.373Z","avatar_url":"https://github.com/Alfex4936.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003cimg width=\"680\" src=\"https://github.com/Alfex4936/kakaoChatbot-Ajou/blob/main/imgs/chatbot.png\"\u003e\n    \u003cp style=\"font-size:60px\"\u003e카카오톡 챗봇 in Rust\u003c/p\u003e\n    \u003ca href=\"https://hits.seeyoufarm.com\"\u003e\u003cimg src=\"https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FAlfex4936%2F2022_FOSS\u0026count_bg=%231C56FF\u0026title_bg=%23000000\u0026icon=github.svg\u0026icon_color=%23E7E7E7\u0026title=FOSS\u0026edge_flat=true\"/\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n# 시작하기\n\nRust언어로 24시간 운영되는 간단한 카카오톡 챗봇 서버를 만들어보는 프로젝트입니다.\n\n들어가기에 앞서, 이 프로젝트에선 Rust언어, AWS 셋업 등은 자세히 설명을 하지 않습니다.\n\n(이 프로젝트는 목표는 흥미 유발 용도)\n\n다만, 알면 좋을만한 언어 문법, 설정은 같이 포함하고 있습니다.\n\n따라서 언어를 잘 몰라도 일단 따라하면서 익힐 수 있도록 목표 삼았습니다.]\n\n(Rust 1.63.0 + WSL2 환경에서 제작됨)(\n\n(Docker | RaspberryPI 지원)\n\n# 무엇을 만들 것인가?\n\n우선 이 프로젝트를 보고 만들 수 있는 결과물을 보여드리겠습니다.\n\n\u003cdiv align=\"center\"\u003e\n\u003cp\u003e\n    \u003cimg width=\"680\" src=\"https://blog.kakaocdn.net/dn/cvfXno/btrcZtC3Lxo/3Qz5ztR3B4P9LnO39SpHL0/img.gif\"\u003e\n\u003c/p\u003e\n\n위 프로젝트 소스는 [여기서](https://github.com/Alfex4936/Rust-Server/tree/main/chatbot) 확인 할 수 있습니다.\n\n또는 직접 친구 추가해서 사용해 볼 수 있습니다. [친구 추가하기](https://pf.kakao.com/_RUcxnK)\n\u003c/div\u003e\n\n---\n\n이 프로젝트에서는 MongoDB에 간단한 데이터(2022년 공휴일)를 저장하고\n\n카카오톡에서 \"달력\"과 같은 메시지를 챗봇에게 보내면 DB에 저장된 공휴일들을 불러올 예정입니다.\n\n# Why Rust?\n\n왜 Rust, 러스트 언어를 선택하였나?\n\n![mostloved](https://user-images.githubusercontent.com/2356749/169690372-7763324e-864b-4422-be7c-4be85d6b5381.png)\n\n2021년 기준 stackoverflow 개발자들 커뮤니티에서\n\n꾸준히 계속 사랑받는 언어로 뽑히고 있습니다.\n\n워낙 빠른 속도와 안정성 때문에 서버 쪽에서 인기를 받고 있으며\n\n수업 때 보신 C가 최고라며 하는 깐깐한 [Linus Torvalds](https://ko.wikipedia.org/wiki/%EB%A6%AC%EB%88%84%EC%8A%A4_%ED%86%A0%EB%A5%B4%EB%B0%9C%EC%8A%A4)가 리눅스 커널 개발 공식 언어로 추가했습니다.\n\n\u003e “Who knows. That’s not the point. The point is for a project to stay interesting — and to stay fun — you have to play with it.” - Linus Torvalds\n\n## 언어 특징\n\nRust는 C언어와 비슷한 성능을 낼 수 있는데 메모리 안전(safe rust 기준), Fearless concurrency들이 가장 큰 특징입니다.\n\n(변수 생명 주기도 컨트롤 가능)\n\n메모리 안전, 데이터 레이스 등을 방지할 수 있는 가장 큰 이유는 [소유권](https://choiseokwon.tistory.com/315)이란 개념입니다.\n\n간단한 예를 들면 변수들은 하나의 소유자만 있고, 소유권을 넘기는 컨트롤을 할 수 있습니다.\n\n따라서 아래와 같은 코드를 사용할 수 없습니다. (y가 x 데이터를 소유함)\n\n```rust\nlet x = 1;\nlet y = x;\n\nprintln!(\"{}\", x); // ERROR: value borrowed here after move\n```\n\n그러면 x도 출력하고 y도 출력하고 싶으면 `borrowing`을 해야합니다. (빌려주기)\n\n예를 들면 자신이 `자동차` 한 대를 갖고 있다고 해봅시다.\n\n그러면 사진을 찍어서 친구들에게 줄 수 있고 친구들이 내 차가 어떻게 생긴지 알지만\n\n본인 말고는 자동차를 튜닝할 수 없습니다. 이것이 read-only borrowing입니다. `\u0026borrow`\n\n또한 다른 사람이 튜닝하게 할 수 있지만 오직 동시에 한 사람만 차를 소유할 수 있습니다. (mutable borrow: `\u0026mut borrow`)\n\n```rust\n// 예제 코드\nstruct Book {}  // C의 구조체\n\nfn borrow(b: \u0026Book) {}  // 책 빌리는 함수\nfn give(b: Book) {}  // 책 주는 함수\n\nfn main() {\n    let book = Book{};  // 책 생성\n    \n    borrow(\u0026book);  // 책 빌리기\n    give(book);  // 책 소유권 전달\n    borrow(\u0026book);  // 또 빌리기, ERROR, DEAD\n    // value borrowed here after move\n    // give하고 borrow를 하기전에 give란 함수에서 다시 소유권을 보내야함\n}\n```\n\n```rust\n// 변수 lifetime\nimpl\u003c'de\u003e Deserialize\u003c'de\u003e for Button {\n    fn deserialize\u003cD\u003e(deserializer: D) -\u003e Result\u003cSelf, D::Error\u003e\n    where\n        D: Deserializer\u003c'de\u003e,\n    {\n    ...\n```\n\n```rust\n// 또한 멀티 쓰레딩 예제\nfn main() {\n  const MAX: u32 = 200_000;\n  let mut counter = 0;\n  let t1 = std::thread::spawn(|| {\n    counter += (2.. MAX/2).filter(|n| is_prime(*n)).count();\n  });\n  let t2 = std::thread::spawn(|| {\n    counter += (2.. MAX/2).filter(|n| is_prime(*n)).count();\n  });\n\n  t1.join();\n  t2.join();\n  println!(\"총 {counter}개의 프라임 넘버 찾음\");\n}\n\n/*\n위 코드는 c++과 같은 언어에서 작동하여 이상한 결과를 얻을 수 있지만\nRust에서는 아예 컴파일이 안된다.\n\n오류:\ncounter라는 변수가 1개 이상 mutable 빌리기가 있을 것 같다.\ncounter라는 변수가 빌린 값보다 더 오래 사는 것 같다. lifetime 오류\n*/\n```\n\n# 코딩 시작\n\n긴 서론을 넘어 코딩을 시작해보십니다.\n\n우선 카카오챗봇 서버를 만들기 위해서는 REST API를 이용해야합니다.\n\n무조건 POST로 된 endpoint에 HTTP 응답은 항상 200 (OK)를 보내야합니다. (오류 상황에서도)\n\n웹 프레임워크는 Rust에서 유명하고 빠른 actix를 사용할 것입니다.\n\n## 셋업\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eRust언어 설치하기\u003c/b\u003e\u003c/summary\u003e\n\n1. Rust 공식 홈페이지에서 [@다운로드](https://www.rust-lang.org/tools/install)\n\n2. 새로운 프로젝트 생성 (cmd/powershell/bash):\n\n    ```sh\n    $ cargo new my_kakao\n    ```\n    \n    ![image](https://user-images.githubusercontent.com/2356749/169691825-00e754ed-0331-4b0b-9d11-ab09e4110d77.png)\n\n3. 현재 디렉토리에 my_kakao란 폴더로 이동:\n\n    ```sh\n    $ cd my_kakao\n    $ rustup override set nightly\n    ```\n\n4. `Cargo.toml` 수정:\n\n    dependencies 아래에 코드처럼 바꿔주세요.\n    외부 라이브러리 사용할 때 cargo.toml에 적으면 됩니다.\n\n    ```toml\n    [build-dependencies]\n    dotenv = \"0.15\"  # .env 파일 환경 변수\n\n    [dependencies]\n    actix-rt = \"2\"   # 웹서버 열기\n    actix-http = \"3\"   # 웹서버 열기\n    actix-web = \"4\"   # 웹서버 열기\n    futures = \"0.3\"  # 비동기\n    serde = { version = \"1.0\", features = [\"derive\"] }  # JSON\n    serde_json = \"1.0\"  # JSON\n    serde_derive = \"1.0\"  # JSON\n    mongodb = \"2\"  # 몽고DB\n    kakao-rs = \"0.3\"  # 카카오 챗봇 JSON 만드는 라이브러리 (직접 제작)\n\n    [profile.dev]\n    opt-level = 0\n    debug = true\n\n    [profile.release]\n    opt-level = 3\n    ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eAWS EC2 셋업\u003c/b\u003e\u003c/summary\u003e\n\n1. `365일`동안 계정을 새로 만들었다면 무료로 24시간 운영할 수 있다.\n\n    EC2는 아마존 서버에 원하는 운영체제를 설치해 빌릴 수 있는 공간이다.\n\n    수업 때 하는 oss@git.ajou.ac.kr 서버랑 똑같다.\n\n    근데 이 셋업 과정이 좀 길고 귀찮다.\n\n    이 [@서버](https://ap-northeast-2.console.aws.amazon.com/console/home?region=ap-northeast-2#)로 들어가서 계정을 만든다.\n\n    그러면 메인 화면에 `EC2`가 있는데 클릭해서 `인스턴스 시작` 버튼을 눌러\n\n    원하는 OS를 설치한다. (Ubuntu 20.04 LTS 설치하세요)\n\n    ![image](https://user-images.githubusercontent.com/2356749/169937872-5db72fcc-032f-4f5d-93a3-d769c2f53e16.png)\n\n2. pem 키, 인증 파일을 받아서 `ssh -i my.pem 주소` 로 접속하는게 보통인데\n\n    파일 경로를 항상 입력해야해서 oss 처럼 비밀번호로 접속할 것이다.\n\n    내 인스턴스를 클릭하고 우측 `연결` 버튼을 눌러서 웹에서 접속한다.\n\n    ![image](https://user-images.githubusercontent.com/2356749/169938301-b337c7ae-ebfc-4767-9778-9e31364319e9.png)\n\n    먼저 계정 비밀번호를 설정한다.\n\n    ```sh\n    $ sudo passwd ubuntu\n    ```\n\n    원하는 비밀번호를 입력한 후\n\n    ```sh\n    $ sudo vim /etc/ssh/sshd_config\n    ```\n\n    위 명령어를 입력하고 `PasswordAuthentication yes`를 해준다.\n\n    ![image](https://user-images.githubusercontent.com/2356749/169939359-a953812a-3bcf-415c-a332-1707aa821b4e.png)\n\n    ```sh\n    $ sudo service sshd restart\n    ```\n\n    그러면 본인 컴퓨터 cmd/powershell/terminal에서 아래를 입력하면\n\n    접속이 성공한다. 주소는 ec2- 로 시작하는 것을 찾는다. (인스턴스 정보에서)\n\n    ```sh\n    $ ssh ubuntu@인스턴스_퍼블릭_IPv4_DNS_주소_입력하세요\n    ```\n\n    익숙한 그 화면을 볼 수 있다.\n    \n    ![image](https://user-images.githubusercontent.com/2356749/169940234-7edbcddf-5176-490e-be64-f465d9abe77a.png)\n\nTIP\n\n    서버 주소를 고정하고 싶으면 elastic ip 하나를 부여하면 된다. (한 인스턴스 연결 무료)\n\n    주의할 점은 인스턴스를 1년 후에 종료하고 elastic ip 해제를 안하면 이것도 돈이 나간다.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eMongoDB 셋업\u003c/b\u003e\u003c/summary\u003e\n\n1. MongoDB Atlas를 이용해서 무료 클러스터를 만든다. ([@링크](https://www.mongodb.com/cloud/atlas/register))\n\n    ![image](https://user-images.githubusercontent.com/2356749/169950092-a5fa9478-db64-4172-86e1-63494a72a4e2.png)\n\n    `Connect` 버튼을 누르고 `Connect your application`을 누르면 연결 주소가 나온다.\n\n    `mongodb+srv://root~~~~`\n\n2. 공휴일 데이터 입력하기\n\n    MongoDB Compass 프로그램을 이용하면 쉽게\n\n    DB를 확인할 수 있다. (mysql workbench처럼)\n\n    데이터베이스 이름은 `foss`, 이 데이터베이스 안에 `holiday` 란 collection을 만들어서 연결할 것이다.\n\n    수동으로 입력하던지 아래 json 파일을 만들어서 `ADD DATA - Import File` 한다.\n\n    ```json\n    [\n    {\n        \"name\": \"제 8회 전국동시지방선거\",\n        \"date\": \"6월 1일\",\n        \"day_of_week\": \"수\"\n    },\n    {\n        \"name\": \"현충일\",\n        \"date\": \"6월 6일\",\n        \"day_of_week\": \"월\"\n    },\n    {\n        \"name\": \"광복절\",\n        \"date\": \"8월 15일\",\n        \"day_of_week\": \"월\"\n    },\n    {\n        \"name\": \"추석\",\n        \"date\": \"9월 9일 ~ 12일\",\n        \"day_of_week\": \"금 ~ 월\"\n    },\n    {\n        \"name\": \"개천절\",\n        \"date\": \"10월 3일\",\n        \"day_of_week\": \"월\"\n    },\n    {\n        \"name\": \"한글날 대체공휴일\",\n        \"date\": \"10월 10일\",\n        \"day_of_week\": \"월\"\n    },\n    {\n        \"name\": \"크리스마스\",\n        \"date\": \"12월 25일\",\n        \"day_of_week\": \"일\"\n    }\n    ]\n    ```\n\n    ![image](https://user-images.githubusercontent.com/2356749/169946784-8579db78-4df8-49ce-bbec-dd1e6e591af9.png)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eRust MongoDB 연동\u003c/b\u003e\u003c/summary\u003e\n\n1. `src/lib.rs` 수정\n\n    사용할 library 및 전역 변수들을 `lib.rs`에 주로 넣어줍니다.\n\n    `SERVER`에 원하는 포트와 주소를 적어도 됩니다.\n    \n    `0.0.0.0`으로 하면 모든 사람이 접속 가능한 서버가 열립니다.\n\n    `MONGO_URL`은 시스템 환경변수 편집에서 `MONGODB_URL`에다 넣거나\n\n    아예 주소로 그냥 바꿔도 됩니다.\n\n    ```rust\n    // src/lib.rs\n    #![feature(proc_macro_hygiene, decl_macro)]\n\n    #[macro_use]\n    extern crate serde_derive;\n    extern crate serde_json;\n\n    extern crate mongodb;\n\n    // 아래 URL에는 mongo+srv//id:password~~~~\n    // .env.example 파일에 설정하세요.\n    pub const MONGO_URL: \u0026str = env!(\"MONGODB_URL\");\n    pub const SERVER: \u0026str = \"0.0.0.0:8010\";\n\n    // DB Holiday 모델\n    #[derive(Serialize, Deserialize, Debug, Default, Clone)]\n    pub struct Holiday {\n        pub name: String,\n        pub date: String,\n        pub day_of_week: String,\n    }\n    ```\n\n2. `src/main.rs` 메인 함수 편집\n\n    프로그램을 실행하면 main 함수가 실행됩니다.\n\n    ```rust\n    use actix_web::middleware::{Compress, Logger, NormalizePath};\n    use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};\n    use futures::TryStreamExt;\n    use kakao_rs::prelude::*;\n    use mongodb::{bson::doc, options::ClientOptions, Client};\n    use my_kakao::{Holiday, MONGO_URL, SERVER};\n    use std::sync::Mutex;\n\n    type Mongo = Mutex\u003cClient\u003e;\n\n    async fn init_mongo() -\u003e Mongo {\n        let client_options = ClientOptions::parse(MONGO_URL).await.unwrap();\n        Mutex::new(Client::with_options(client_options).unwrap())\n    }\n\n    #[post(\"/holiday\")]\n    pub async fn get_holidays(conn: web::Data\u003cMongo\u003e) -\u003e impl Responder {\n        let mut result = Template::new();\n        let mut carousel = Carousel::new().set_type(BasicCard::id());\n        carousel.set_header(\n            \"2022년 공휴일\",\n            \"FOSS\",\n            \"https://www.ajou.ac.kr/_res/ajou/kr/img/intro/img-slogan08.png\",\n        );\n\n        let db = \u0026conn;\n\n        for holiday in show_holidays(db).await.unwrap() {\n            // println!(\n            //     \"name: {}, date: {}, day_of_week: {}\",\n            //     holiday.name, holiday.date, holiday.day_of_week\n            // );\n\n            let basic_card = BasicCard::new().set_title(holiday.name).set_desc(format!(\n                \"날짜: {} ({}요일)\",\n                holiday.date, holiday.day_of_week\n            ));\n\n            carousel.add_card(basic_card.build_card());\n        }\n\n        result.add_output(carousel.build());\n\n        HttpResponse::Ok()\n            .content_type(\"application/json\")\n            .body(serde_json::to_string(\u0026result).unwrap())\n    }\n\n    pub async fn show_holidays(conn: \u0026Mongo) -\u003e Result\u003cVec\u003cHoliday\u003e, ()\u003e {\n        let holiday_collection = conn\n            .lock()\n            .unwrap()\n            .database(\"foss\")\n            .collection::\u003cHoliday\u003e(\"holiday\");\n\n        let mut holidays = holiday_collection.find(doc! {}, None).await.unwrap();\n        let mut result: Vec\u003cHoliday\u003e = Vec::new();\n        while let Some(holiday) = holidays.try_next().await.unwrap() {\n            result.push(holiday);\n        }\n\n        Ok(result)\n    }\n\n    #[actix_web::main]\n    async fn main() -\u003e std::io::Result\u003c()\u003e {\n        let data = web::Data::new(init_mongo().await); // MongoDB 초기화\n\n        println!(\"\\n===\u003e Welcome to Rust KakaoChat bot!\");\n\n        // 서버 실행\n        HttpServer::new(move || {\n            App::new()\n                .app_data(data.clone()) // \u003c- db는 이런 식으로 서버로 연동\n                .wrap(Compress::default())\n                .wrap(NormalizePath::default())\n                .wrap(Logger::default())\n                .service(get_holidays)\n        })\n        .bind(SERVER)?\n        .run()\n        .await\n    }\n    ```\n\n3. `.env.example` 파일 수정\n\n    환경 변수 값은 production에는 숨기는게 좋기 때문에 숨겨봅시다.\n\n    MONGODB_URL=자신의 URL을 넣습니다.\n\n4. `build.rs` 수정\n\n    cargo로 빌드 할 때 특정 명령어들을 실행시켜 코드에 적용시킬 수 있습니다.\n\n    빌드 시 환경변수 값을 미리 불러서 `lib.rs`에서 불러옵니다.\n\n    ```rust\n    use dotenv::dotenv;\n    use std::env;\n\n    fn main() {\n        dotenv().ok(); // dotenv 라이브러리가 .env 로드\n\n        // 이 부분 핵심\n        let database_url = env::var(\"MONGODB_URL\").expect(\"MONGODB_URL must be set\");\n\n        println!(\"cargo:rustc-env=MONGODB_URL={database_url}\");\n    }\n    ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e카카오톡 챗봇 API 셋업\u003c/b\u003e\u003c/summary\u003e\n\n1. [i.kakao.com](https://i.kakao.com/)에 들어가 카카오 계정으로 봇을 하나 만든다. (허락 요청은 몇일이 걸릴 수도 있음)\n\n![image](https://user-images.githubusercontent.com/2356749/169967058-85acda5c-ab09-42a1-906b-6ee038230a6a.png)\n\n2. 당장 알아야 할 사항은 `시나리오`, `스킬` 부분이다.\n\n    시나리오는 어떠한 메시지에 어떻게 반응할지 정하는 부분이다.\n\n    스킬에는 서버 endpoint 들을 추가하여 `밥`을 카톡에서 사용자가 보냈을 때 `http://server/bap`\n\n    이런 식으로 연결한다.\n\n    그러면 `+ 시나리오`를 눌러서 아래처럼 만든다. (공휴일, 휴일이란 메시지에 반응할 것이다.)\n\n    ![image](https://user-images.githubusercontent.com/2356749/169967598-aa52d14c-412b-49f6-99ed-cf84727d7fb9.png)\n\n    `스킬`로 이동해서 `추가` 버튼을 누른다.\n\n    ![image](https://user-images.githubusercontent.com/2356749/169968388-1eee5bd4-e20a-418e-9da2-2c94318321c9.png)\n\n    `URL` 부분에는 실제 서비스에 작동할 동작이고 `Test URL`은 웹에서 테스트할 때 작동할 동작을 지정할 수 있는데\n\n    여기선 나눌 필요가 없어서 둘 다 똑같이 설정한다.\n\n    주소는 ssh 연결한 서버에 `:8010` 포트 `/holiday` 엔드포인트 순서이다.\n\n    그러면 카카오톡에서 메시지가 오면 서버에 `POST 서버:8010/holiday`로 요청이 간다. (무조건 POST임)\n\n    그러면 아까 시나리오 부분으로 다시 가서 스킬을 연결해준다.\n\n    ![image](https://user-images.githubusercontent.com/2356749/169968912-aab32d66-7d0f-44f5-a8ee-be8fe706ae4c.png)\n\n3. 메시지 유형\n\n    공식 [@API](https://chatbot.kakao.com/docs/tutorial-chatbot-response)를 참고하면 된다.\n\n    위 메시지들을 전부 JSON 형식으로 만들어서 endpoint 함수에서 return 해줘야한다.\n\n    `fn return_holiday(...) -\u003e JSON`\n\n\u003c/details\u003e\n\n# kakao-rs\n\nRust에서 Rust 데이터들로 JSON을 만들어서 응답을 보내는 것은 까다로울 수 있다.\n\n그래서 라이브러리를 직접 제작해두었다. [@Github 주소 | MIT 라이센스](https://github.com/Alfex4936/kakao-rs)\n\n만들어둔 [@공식 문서](https://docs.rs/kakao-rs/latest/kakao_rs/)를 참고하거나\n\n`README`, `test 폴더`를 확인하면 어떻게 응답을 만드는지 볼 수 있다.\n\n예를 들면 아래와 같은 [ListCard](https://i.kakao.com/docs/skill-response-format#listcard)를 만들기 위해서\n\n![image](https://user-images.githubusercontent.com/2356749/169971426-0a689be3-e6cf-4608-8b31-46402a0a8176.png)\n\n아래와 같은 JSON을 만들어야 하는데\n\n```json\n{\n  \"response\": {\n    \"template\": {\n      \"outputs\": [\n        {\n          \"simpleText\": {\n            \"text\": \"오늘 공지 총 9개\"\n          }\n        },\n        {\n          \"listCard\": {\n            \"buttons\": [\n              {\n                \"label\": \"4개 더보기\",\n                \"action\": \"message\",\n                \"messageText\": \"더보기\"\n              },\n              {\n                \"label\": \"공유하기\",\n                \"action\": \"share\"\n              }\n            ],\n            \"header\": {\n              \"title\": \"22.05.24) 오늘 공지\"\n            },\n            \"items\": [\n              {\n                \"title\": \"[홍보] 2022 수원역 로데오 콘테스트\",\n                \"description\": \"[기타] LINC사업팀 05.24\",\n                \"link\": {\n                  \"web\": \"https://www.ajou.ac.kr/kr/ajou/notice.do?mode=view\u0026articleNo=198483\u0026article.offset=0\u0026articleLimit=30\"\n                }\n              },\n              {\n                \"title\": \"[메이커스페이스] 운영일자 변경안내 (2022.05.30-06.17)\",\n                \"description\": \"[기타] 창업지원팀 05.24\",\n                \"link\": {\n                  \"web\": \"https://www.ajou.ac.kr/kr/ajou/notice.do?mode=view\u0026articleNo=198479\u0026article.offset=0\u0026articleLimit=30\"\n                }\n              },\n              {\n                \"title\": \"소원나무 이벤트 5월 3주차 당첨자 발표!\",\n                \"description\": \"[취업] 대학일자리플러스센터 05.24\",\n                \"link\": {\n                  \"web\": \"https://www.ajou.ac.kr/kr/ajou/notice.do?mode=view\u0026articleNo=198473\u0026article.offset=0\u0026articleLimit=30\"\n                }\n              },\n              {\n                \"title\": \"[중앙도서관] 소음 허용 기준에 따른 도서관 공간 이용 안내\",\n                \"description\": \"[기타] 중앙도서관 학술정보팀 05.24\",\n                \"link\": {\n                  \"web\": \"https://www.ajou.ac.kr/kr/ajou/notice.do?mode=view\u0026articleNo=198471\u0026article.offset=0\u0026articleLimit=30\"\n                }\n              },\n              {\n                \"title\": \"2022학년도 1학기 학생상담 안내(2022.6.14.까지)\",\n                \"description\": \"[기타] 공학교육혁신센터 05.24\",\n                \"link\": {\n                  \"web\": \"https://www.ajou.ac.kr/kr/ajou/notice.do?mode=view\u0026articleNo=198468\u0026article.offset=0\u0026articleLimit=30\"\n                }\n              }\n            ]\n          }\n        }\n      ],\n      \"quickReplies\": [\n        {\n          \"action\": \"message\",\n          \"label\": \"오늘\",\n          \"messageText\": \"오늘 공지 보여줘\"\n        },\n        {\n          \"action\": \"message\",\n          \"label\": \"어제\",\n          \"messageText\": \"어제 공지 보여줘\"\n        }\n      ]\n    },\n    \"version\": \"2.0\"\n  }\n}\n```\n\n만들어둔 라이브러리를 이용하면 다음와 같다.\n\n```rust\n#[post(\"/notice/today\")]\npub async fn get_today_notice(kakao: web::Json\u003cValue\u003e) -\u003e impl Responder {\n    // println!(\"{:#?}\", kakao);\n    let mut result = Template::new();\n    result.add_qr(QuickReply::new(\"오늘\", \"오늘 공지 보여줘\"));\n    result.add_qr(QuickReply::new(\"어제\", \"어제 공지 보여줘\"));\n\n    let mut notices: Vec\u003cNotice\u003e = match notice_parse(\"ajou\", Some(30)).await {\n        Ok(yes) =\u003e yes,\n        _ =\u003e {\n            result.add_output(SimpleText::new(\"홈페이지 반응이 늦습니다. :(\").build());\n\n            return HttpResponse::Ok()\n                .content_type(\"application/json\")\n                .body(serde_json::to_string(\u0026result).unwrap());\n        }\n    };\n    let today = Local::now().format(\"%y.%m.%d\").to_string(); // \"21.07.20\"\n\n    let mut list_card = ListCard::new(format!(\"{}) 오늘 공지\", today));\n\n    // notices.iter().position(|\u0026n| n.date.ne(\u0026today)).unwrap();\n\n    notices = notices\n        .into_iter()\n        .filter(|notice| notice.date.eq(\u0026today))\n        .collect();\n\n    if !notices.is_empty() {\n        result.add_output(SimpleText::new(format!(\"오늘 공지 총 {}개\", notices.len())).build());\n    }\n\n    if notices.len() \u003e 5 {\n        let label = format!(\"{}개 더보기\", notices.len() - 5);\n        list_card.add_button(\n            Button::new(ButtonType::Text)\n                .set_label(label)\n                .set_msg(\"더보기\"),\n        );\n        notices.resize(5, Notice::default());\n    } else {\n        list_card.add_button(\n            Button::new(ButtonType::Link)\n                .set_label(\"아주대 공지\")\n                .set_link(AJOU_LINK),\n        );\n    }\n\n    if notices.is_empty() {\n        list_card.add_item(ListItem::new(\"공지가 없습니다!\").set_image(\n            \"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg\",\n        ));\n    } else {\n        list_card.add_button(Button::new(ButtonType::Share).set_label(\"공유하기\"));\n        for notice in notices.iter_mut() {\n            let description = format!(\n                \"[{}] {} {}\",\n                notice.category,\n                notice.writer,\n                notice.date[notice.date.len() - 5..].to_string()\n            );\n\n            list_card.add_item(\n                ListItem::new((*notice.title).to_string())\n                    .set_desc(description)\n                    .set_link((*notice.link).to_string()),\n            );\n        }\n    }\n\n    result.add_output(list_card.build()); // moved list_card's ownership\n\n    HttpResponse::Ok()\n        .content_type(\"application/json\")\n        .body(serde_json::to_string(\u0026result).unwrap())\n}\n```\n\n지원하는 메시지 유형: [@소스](https://github.com/Alfex4936/kakao-rs/tree/master/src/components)\n\n* QuickReply, Carousel(BasicCard|CommerceCard), ListCard\n* SimpleImage, SimpleText\n* Button(Call, Share, Link, Text)\n* BasicCard, CommerceCard, ItemCard\n\n# 빌드\n\n거의 마지막 단계이다.\n\n카카오톡 채널을 만들고 챗봇을 연결하고 배포를 하면 끝이다. (서버도 실행하고)\n\n채널은 [@여기서](https://center-pf.kakao.com/profiles) 만들고 챗봇 설정에서 채널을 연결하면 된다.\n\n![image](https://user-images.githubusercontent.com/2356749/169988569-f1e0e491-392a-4794-a665-3ac9e4f47afe.png)\n\n![image](https://user-images.githubusercontent.com/2356749/169983892-b3ebbbd4-c07d-4732-b206-6f6a7f7cea2e.png)\n\n서버 실행은 AWS EC2에서 소스를 빌드하던가 WSL2를 이용해 리눅스 버전으로 빌드한 후에\n\nSCP 명령어로 EC2에 업로드해서 바이너리만 실행해도 된다.\n\n`nohup` 명령어는 백그라운드에서 실행하게 해주는 명령어다.\n\n로그아웃하더라도 프로세스는 계속 살아있어 24시간 서버가 완성된다.\n\n```sh\n$ wsl\n$ cargo build\n$ cd target/debug\n$ scp my_kakao ubuntu@ec2-서버:~/\n$ ssh ubuntu@ec2-서버\n$ chmod +x my_kakao\n$ nohup ./my_kakao \u0026\n```\n\n![image](https://user-images.githubusercontent.com/2356749/169987894-176c500b-4cb1-4bfb-9871-350b39bbeeeb.png)\n\n결과물 카카오톡 친구 추가하기 [@링크](https://pf.kakao.com/_ESnxkb)\n\n# 마치며\n\n처음에 만들려고 할 때 리눅스, Rust, DB, 서버 등 다양한 지식들이 필요했다.\n\n하지만 수업 때 배운 지식들로도 충분히 가능하다고 생각한다.\n\n처음에 Python으로 만들어보고, Go언어로 만들어보고 Rust언어로 만들게되었는데\n\n(MySQL에서 MongoDB로 바꿈)\n\n개인적인 기준으로 Rust가 제일 빠르고, 깔끔하게 코딩이 가능했다.\n\n자신만의 좋은 아이디어로 빠른 카카오톡 챗봇 서버를 만들어보자!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falfex4936%2Frust-kakaotalk-chatbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falfex4936%2Frust-kakaotalk-chatbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falfex4936%2Frust-kakaotalk-chatbot/lists"}