[![kakao-rs on crates.io](https://img.shields.io/crates/v/kakao-rs.svg)](https://crates.io/crates/kakao-rs) [![kakao-rs on docs.rs](https://docs.rs/kakao-rs/badge.svg)](https://docs.rs/kakao-rs/)

카카오톡 챗봇 빌더 도우미

Rust언어 전용

# 소개 Rust언어로 카카오 챗봇 서버를 만들 때 좀 더 쉽게 JSON 메시지 응답을 만들 수 있게 도와줍니다. SimpleText, SimpleImage, ListCard, Carousel, BasicCard, CommerceCard, ItemCard, TextCard JSON 데이터를 쉽게 만들 수 있도록 도와줍니다. # 설치 ```toml [dependencies] kakao-rs = "0.3" ``` # 응답 타입별 아이템 Button::share (공유 버튼), Button::link (링크 버튼), Button::text (일반 메시지만), Button::call(전화 버튼) Items: ListItem # 사용법 ## 카카오 JSON 데이터 Bind 예제) 유저 발화문 얻기: json.userRequest.utterance ```rust #[post("/end", format = "json", data = "")] // rocket-rs pub fn test(kakao: Json) -> String { println!("{}", kakao["userRequest"]["utterance"].as_str().unwrap()); // 발화문 unimplemented!() } #[post("/end")] pub async fn test(kakao: web::Json) -> impl Responder { // actix-rs println!("{}", kakao["userRequest"]["utterance"].as_str().unwrap()); // 발화문 unimplemented!() } ``` ## ListCard 예제 ```rust extern crate kakao_rs; use kakao_rs::prelude::*; fn main() { let mut result = Template::new(); // 빠른 응답 result.add_qr(QuickReply::new("오늘", "오늘 공지 보여줘")); result.add_qr(QuickReply::new("어제", "어제 공지 보여줘")); let list_card = ListCard::new("리스트 카드 제목!") // 제목 .add_button(Button::text("그냥 텍스트 버튼")) // 메시지 버튼 .add_button(Button::link("link label", "https://google.com")) // 링크 버튼 .add_button(Button::share("share label").set_msg("카톡에 보이는 메시지")) // 공유 버튼, 기본적으로 message_text는 없음 .add_button(Button::call("call label", "010-1234-5679")) // 전화 버튼 .add_item( ListItem::new("title") .set_desc("description") // 설명 .set_link("https://naver.com"), ); result.add_output(list_card.build()); // moved list_card's ownership println!( "Result: {}", serde_json::to_string_pretty(&result).expect("Failed") ); } /* Result: { "template": { "outputs": [ { "listCard": { "buttons": [ { "label": "그냥 텍스트 버튼", "action": "message" }, { "label": "link label", "action": "webLink", "webLinkUrl": "https://google.com" }, { "label": "share label", "action": "share", "messageText": "카톡에 보이는 메시지" }, { "label": "call label", "action": "phone", "phoneNumber": "010-1234-5679" } ], "header": { "title": "리스트 카드 제목!" }, "items": [ { "title": "title", "description": "description", "link": { "web": "https://naver.com" } } ] } } ], "quickReplies": [ { "action": "message", "label": "오늘", "messageText": "오늘 공지 보여줘" }, { "action": "message", "label": "어제", "messageText": "어제 공지 보여줘" } ] }, "version": "2.0" } */ ``` ## 다른 예제 Carousel에 Card를 추가할 때는 build_card()로 카드를 빌드하세요. Carousel 에 추가할 수 있는 카드는 다음과 같습니다. `Array, Array, Array, Array, Array` "한 종류의 카드만 담아서 Carousel 을 만들어야 합니다. (여러 종류 같이 카카오에서 지원 안 함)" 자세한 사용법은 [tests](https://github.com/Alfex4936/kakao-rs/tree/master/tests) 폴더를 참고하세요. ```rust use kakao_rs::prelude::*; // 따로 import // use kakao_rs::components::basics::*; // use kakao_rs::components::buttons::*; // use kakao_rs::components::cards::*; use std::matches; #[test] fn simple_text_test() { let mut result = Template::new(); result.add_qr(QuickReply::new( "빠른 응답", "빠른 응답 ㅋㅋ", )); let simple_text = SimpleText::new("심플 텍스트 테스트"); result.add_output(simple_text.build()); let serialized = r#"{"template":{"outputs":[{"simpleText":{"text":"심플 텍스트 테스트"}}],"quickReplies":[{"action":"message","label":"빠른 응답","messageText":"빠른 응답 ㅋㅋ"}]},"version":"2.0"}"#; assert_eq!(serialized, result.to_string()); } // 아래처럼 Carousel에 여러 타입의 카드를 넣지 마시오. // 카카오에서 지원하지 않습니다. // 마지막에 추가된 카드 타입으로 json을 만듭니다. #[test] fn carousel_all_cards_test() { let mut result = Template::new(); result.add_qr(QuickReply::new("빠른 응답", "빠른 응답 ㅋㅋ")); let mut carousel = Carousel::new(); carousel.set_header("오늘 공지 n개", "n개를 더 불러왔습니다!", "https://"); let basic_card = BasicCard::new() .set_title("1번") .set_thumbnail("http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"); let text_card = TextCard::new() .set_title("챗봇 관리자센터에 오신 것을 환영합니다.") .set_description("챗봇 관리자센터로 챗봇을 제작해 보세요.") .add_button( Button::new(ButtonType::Link) .set_label("View Boarding Pass") .set_link("https://namu.wiki/w/%EB%82%98%EC%97%B0(TWICE)"), ); let item_card = ItemCard::new() .set_title("title") .set_desc("desc") .set_thumbnail("http://dev-mk.kakao.com/dn/bot/scripts/with_barcode_blue_1x1.png") .set_thumbnail_width(800) .set_thumbnail_height(800) .set_image_title("DOFQTK") .set_image_desc("Boarding Number") .set_item_list_alignment("right") .set_item_list_summary("total", "$4,032.54") .add_button( Button::new(ButtonType::Link) .set_label("View Boarding Pass") .set_link("https://namu.wiki/w/%EB%82%98%EC%97%B0(TWICE)"), ) .set_button_layout("vertical"); let commerce_card = CommerceCard::new() .set_desc("커머스 카드") .set_price(15000) .set_thumbnail("http://dev-mk.kakao.com/dn/bot/scripts/with_barcode_blue_1x1.png"); carousel.add_card(text_card.build_card()); carousel.add_card(basic_card.build_card()); carousel.add_card(item_card.build_card()); carousel.add_card(commerce_card.build_card()); result.add_output(carousel.build()); let serialized = r#"{"template":{"outputs":[{"carousel":{"type":"listCard","items":[{"title":"챗봇 관리자센터에 오신 것을 환영합니다.","description":"챗봇 관리자센터로 챗봇을 제작해 보세요.","buttons":[{"label":"View Boarding Pass","action":"webLink","webLinkUrl":"https://namu.wiki/w/%EB%82%98%EC%97%B0(TWICE)"}]},{"title":"1번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"}},{"thumbnail":{"imageUrl":"http://dev-mk.kakao.com/dn/bot/scripts/with_barcode_blue_1x1.png","width":800,"height":800},"imageTitle":{"title":"DOFQTK","description":"Boarding Number"},"itemList":[],"itemListAlignment":"right","itemListSummary":{"title":"total","description":"$4,032.54"},"title":"title","description":"desc","buttons":[{"label":"View Boarding Pass","action":"webLink","webLinkUrl":"https://namu.wiki/w/%EB%82%98%EC%97%B0(TWICE)"}],"buttonLayout":"vertical"},{"description":"커머스카드","price":15000,"currency":"","thumbnails":[{"imageUrl":"http://dev-mk.kakao.com/dn/bot/scripts/with_barcode_blue_1x1.png"}]}],"header":{"title":"오늘 공지 n개","description":"n개를 더 불러왔습니다!","thumbnail":{"imageUrl":"https://"}}}}],"quickReplies":[{"action":"message","label":"빠른 응답","messageText":"빠른 응답 ㅋㅋ"}]},"version":"2.0"}"#; assert_eq!(serialized, serde_json::to_string(&result).expect("Failed")); } #[test] fn multiple_outputs_test() { let mut result = Template::new(); result.add_qr(QuickReply::new( "빠른 응답", "빠른 응답 ㅋㅋ", )); let mut carousel = Carousel::new().set_type(BasicCard::id()); for i in 0..5 { let basic_card = BasicCard::new() .set_title(format!("{}번", i)) .set_thumbnail( "http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg" ); carousel.add_card(basic_card.build_card()); } result.add_output(carousel.build()); let simple_text = SimpleText::new("심플 텍스트 테스트"); result.add_output(simple_text.build()); let serialized = r#"{"template":{"outputs":[{"carousel":{"type":"basicCard","items":[{"title":"0번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg",}},{"title":"1번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"}},{"title":"2번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"}},{"title":"3번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"}},{"title":"4번","thumbnail":{"imageUrl":"http://k.kakaocdn.net/dn/APR96/btqqH7zLanY/kD5mIPX7TdD2NAxgP29cC0/1x1.jpg"}}]}},{"simpleText":{"text":"심플 텍스트 테스트"}}],"quickReplies":[{"action":"message","label":"빠른 응답","messageText":"빠른 응답 ㅋㅋ"}]},"version":"2.0"}"#; // Carousel BasicCards 뒤 SimpleText 발화 assert_eq!(serialized, result.to_string()); } ``` # TODO - use PyO3 to export this library in Python How to make codes more maintainable? ```rust pub fn set_field>(mut self, field: &str, value: T) -> Self { match field { "desc" | "description" => self.content.description = Some(value.into()), "title" => self.content.title = Some(value.into()), "thumbnail" => self.content.thumbnail.image_url = value.into(), "link" => self.content.thumbnail.link = Some(Link { web: value.into() }), "fixed_ratio" => self.content.thumbnail.fixed_ratio = value.into(), "width" => self.content.thumbnail.width = Some(value.into()), "height" => self.content.thumbnail.height = Some(value.into()), _ => {} } self } ```