TL;DR: Code đây. https://github.com/ngoctnq-1957/rasa-chatwork-echo
Mở bài
Nếu bạn là người đi làm chatbot như mình, chắc hẳn bạn đã dùng Rasa. Với các ưu điểm vượt trội như là hoàn toàn local không sợ mất thông tin, một dialog handler xịn cùng các connector (cho dù bắt entity hơi ngu), Rasa là sự lựa chọn số 1 của các dự án cần tính bảo mật/hay cần mọi thứ trong 1 gói. Đồng thời, nếu bạn làm ở một công ty sử dụng Chatwork như mình, bạn sẽ cần tìm cách sao cho Chatwork liên hệ được với Rasa để nó có thể thay thế bạn trả lời tin nhắn của sếp Vậy bài này mình sẽ hướng dân bạn làm vậy nhé. Bonus thêm cách làm một con chatbot chuyên đi nhại lại đúng lời bạn nói luôn.
Chú thích: post này sử dụng Rasa<1.8.0 vì nó tương thích với TensorFlow 1.x, do kinh nghiệm là bản TF2.0 vẫn còn nát lắm.
Cách để Rasa nhại lại bạn
Đằng nào mình cũng phải xây dựng một con bot cơ bản để demo cho các bạn mà, nên tiện mình giới thiệu luôn: hành động nhại lại bạn sẽ định nghĩa trong file actions.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">from</span> typing <span class="token keyword">import</span> Any<span class="token punctuation">,</span> Text<span class="token punctuation">,</span> Dict<span class="token punctuation">,</span> List <span class="token keyword">from</span> rasa_sdk <span class="token keyword">import</span> Action<span class="token punctuation">,</span> Tracker <span class="token keyword">from</span> rasa_sdk<span class="token punctuation">.</span>executor <span class="token keyword">import</span> CollectingDispatcher <span class="token keyword">class</span> <span class="token class-name">ActionHelloWorld</span><span class="token punctuation">(</span>Action<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">name</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Text<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"action_echo"</span> <span class="token keyword">def</span> <span class="token function">run</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> dispatcher<span class="token punctuation">:</span> CollectingDispatcher<span class="token punctuation">,</span> tracker<span class="token punctuation">:</span> Tracker<span class="token punctuation">,</span> domain<span class="token punctuation">:</span> Dict<span class="token punctuation">[</span>Text<span class="token punctuation">,</span> Any<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> List<span class="token punctuation">[</span>Dict<span class="token punctuation">[</span>Text<span class="token punctuation">,</span> Any<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">:</span> dispatcher<span class="token punctuation">.</span>utter_message<span class="token punctuation">(</span>text<span class="token operator">=</span>tracker<span class="token punctuation">.</span>latest_message<span class="token punctuation">[</span><span class="token string">"text"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> |
Và bạn để mặc định là sẽ chạy action đó trong config.yml
:
1 2 3 4 5 6 | <span class="token key atrule">policies</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"FallbackPolicy"</span> <span class="token key atrule">nlu_threshold</span><span class="token punctuation">:</span> <span class="token number">1.0</span> <span class="token key atrule">core_threshold</span><span class="token punctuation">:</span> <span class="token number">1.0</span> <span class="token key atrule">fallback_action_name</span><span class="token punctuation">:</span> <span class="token string">'action_echo'</span> |
Hết
Cách để Rasa kết nối với Chatwork
Việc đầu tiên là bạn định nghĩa webhook nhận tin nhắn vào cho Rasa.
Cấu trúc của các class extend InputChannel
cần có các methods sau: bắt đầu là name
, sẽ quyết định webhook URL của bạn.
1 2 3 4 5 | <span class="token keyword">class</span> <span class="token class-name">ChatworkInput</span><span class="token punctuation">(</span>InputChannel<span class="token punctuation">)</span><span class="token punctuation">:</span> @<span class="token builtin">classmethod</span> <span class="token keyword">def</span> <span class="token function">name</span><span class="token punctuation">(</span>cls<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Text<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"chatwork"</span> |
Ví dụ của mình để return chatwork
thì URL sẽ là /webhooks/chatwork/webhook
.
Tiếp theo là method để lấy các settings về token trong file credentials.yml
. Phần này cũng sẽ được nói sau ở cuối mục này.
1 2 3 4 5 6 7 8 9 10 | @<span class="token builtin">classmethod</span> <span class="token keyword">def</span> <span class="token function">from_credentials</span><span class="token punctuation">(</span>cls<span class="token punctuation">,</span> credentials<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> <span class="token operator">not</span> credentials<span class="token punctuation">:</span> cls<span class="token punctuation">.</span>raise_missing_credentials_exception<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">return</span> cls<span class="token punctuation">(</span>credentials<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"api_token"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> credentials<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"secret_token"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> api_token<span class="token punctuation">:</span> Text<span class="token punctuation">,</span> secret_token<span class="token punctuation">:</span> Text<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>api_token <span class="token operator">=</span> api_token self<span class="token punctuation">.</span>secret_token <span class="token operator">=</span> secret_token |
Tiếp theo là một method không bắt buộc, nhưng mình đưa vào để gỡ các loại To/Reply khỏi tin nhắn đầu vào cho nó sạch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @<span class="token builtin">staticmethod</span> <span class="token keyword">def</span> <span class="token function">_sanitize_user_message</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Remove all tags. """</span> <span class="token keyword">for</span> regex<span class="token punctuation">,</span> replacement <span class="token keyword">in</span> <span class="token punctuation">[</span> <span class="token comment"># to messages</span> <span class="token punctuation">(</span>r<span class="token string">"[[Tt][Oo]:d+]"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment"># reply messages</span> <span class="token punctuation">(</span>r<span class="token string">"[[Rr][Pp] aid=[^]]+]"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>r<span class="token string">"[Reply aid=[^]]+]"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">:</span> text <span class="token operator">=</span> re<span class="token punctuation">.</span>sub<span class="token punctuation">(</span>regex<span class="token punctuation">,</span> replacement<span class="token punctuation">,</span> text<span class="token punctuation">)</span> <span class="token keyword">return</span> text<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Quan trọng nhất trong các đoạn code là blueprint
cho sanic
server của Rasa. Trong đó, Rasa yêu cầu bạn cần implement 2 route đó là /
với tên method health
, và webhook
với tên method receive
.
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">def</span> <span class="token function">blueprint</span><span class="token punctuation">(</span> self<span class="token punctuation">,</span> on_new_message<span class="token punctuation">:</span> Callable<span class="token punctuation">[</span><span class="token punctuation">[</span>UserMessage<span class="token punctuation">]</span><span class="token punctuation">,</span> Awaitable<span class="token punctuation">[</span><span class="token boolean">None</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Blueprint<span class="token punctuation">:</span> custom_webhook <span class="token operator">=</span> Blueprint<span class="token punctuation">(</span><span class="token string">"chatwork_webhook"</span><span class="token punctuation">,</span> <span class="token string">"chatwork"</span> <span class="token punctuation">)</span> @custom_webhook<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"GET"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">health</span><span class="token punctuation">(</span>request<span class="token punctuation">:</span> Request<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> HTTPResponse<span class="token punctuation">:</span> <span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"signature_tag"</span><span class="token punctuation">:</span> <span class="token string">"o' kawaii koto."</span><span class="token punctuation">}</span><span class="token punctuation">)</span> |
Để tránh việc một ai đó bắn request láo vào webhook của bạn (mà không từ Chatwork), bạn cần kiểm tra tin nhắn đó có phải từ Chatwork không.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">def</span> <span class="token function">validate_request</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># Check the X-Hub-Signature header to make sure this is a valid request.</span> chatwork_signature <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'X-ChatWorkWebhookSignature'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span> signature <span class="token operator">=</span> hmac<span class="token punctuation">.</span>new<span class="token punctuation">(</span>base64<span class="token punctuation">.</span>b64decode<span class="token punctuation">(</span><span class="token builtin">bytes</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>secret_token<span class="token punctuation">,</span> encoding<span class="token operator">=</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span>body<span class="token punctuation">,</span> hashlib<span class="token punctuation">.</span>sha256<span class="token punctuation">)</span> expected_signature <span class="token operator">=</span> base64<span class="token punctuation">.</span>b64encode<span class="token punctuation">(</span>signature<span class="token punctuation">.</span>digest<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> hmac<span class="token punctuation">.</span>compare_digest<span class="token punctuation">(</span><span class="token builtin">bytes</span><span class="token punctuation">(</span>chatwork_signature<span class="token punctuation">,</span> encoding<span class="token operator">=</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> expected_signature<span class="token punctuation">)</span> |
Về cơ bản, header của request được gửi đến webhook sẽ bao gồm hash của nội dung tin nhắn, ký với secret token của bạn. So sánh thấy ok là ok ?
Và tâm điểm của bài này chính là webhook. Code có một số callback khá là cơ bản thôi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | @custom_webhook<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">"/webhook"</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"POST"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">receive</span><span class="token punctuation">(</span>request<span class="token punctuation">:</span> Request<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> HTTPResponse<span class="token punctuation">:</span> <span class="token keyword">if</span> <span class="token operator">not</span> validate_request<span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token string">"you've been a very bad boy!"</span><span class="token punctuation">,</span> status<span class="token operator">=</span><span class="token number">400</span><span class="token punctuation">)</span> content <span class="token operator">=</span> request<span class="token punctuation">.</span>json<span class="token punctuation">[</span><span class="token string">"webhook_event"</span><span class="token punctuation">]</span> sender_id <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token string">"from_account_id"</span><span class="token punctuation">]</span> room_id <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token string">"room_id"</span><span class="token punctuation">]</span> message_id <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token string">"message_id"</span><span class="token punctuation">]</span> text <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token string">"body"</span><span class="token punctuation">]</span> metadata <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">"sender_id"</span><span class="token punctuation">:</span> sender_id<span class="token punctuation">,</span> <span class="token string">"room_id"</span><span class="token punctuation">:</span> room_id<span class="token punctuation">,</span> <span class="token string">"message_id"</span><span class="token punctuation">:</span> message_id<span class="token punctuation">,</span> <span class="token string">"text"</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>_sanitize_user_message<span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">}</span> out_channel <span class="token operator">=</span> self<span class="token punctuation">.</span>get_output_channel<span class="token punctuation">(</span>room_id<span class="token punctuation">)</span> <span class="token keyword">try</span><span class="token punctuation">:</span> <span class="token keyword">await</span> on_new_message<span class="token punctuation">(</span> UserMessage<span class="token punctuation">(</span> text<span class="token punctuation">,</span> out_channel<span class="token punctuation">,</span> sender_id<span class="token punctuation">,</span> input_channel<span class="token operator">=</span>room_id<span class="token punctuation">,</span> metadata<span class="token operator">=</span>metadata<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">except</span> CancelledError<span class="token punctuation">:</span> logger<span class="token punctuation">.</span>error<span class="token punctuation">(</span> <span class="token string">"Message handling timed out for "</span> <span class="token string">"user message '{}'."</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">except</span> Exception<span class="token punctuation">:</span> logger<span class="token punctuation">.</span>exception<span class="token punctuation">(</span> <span class="token string">"An exception occured while handling "</span> <span class="token string">"user message '{}'."</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token string">"alles gut ?"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> custom_webhook |
Và cuối cùng là method không bắt buộc, dùng để tạo ra OutputChannel
để bạn có thể gửi trả lại tin nhắn cho người dùng.
1 2 3 | <span class="token keyword">def</span> <span class="token function">get_output_channel</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> room_id<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> OutputChannel<span class="token punctuation">:</span> <span class="token keyword">return</span> ChatworkOutput<span class="token punctuation">(</span>self<span class="token punctuation">.</span>api_token<span class="token punctuation">,</span> room_id<span class="token punctuation">)</span> |
Sau đó, bạn định nghĩa tin nhắn gửi trả sẽ như thế nào.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">class</span> <span class="token class-name">ChatworkOutput</span><span class="token punctuation">(</span>OutputChannel<span class="token punctuation">)</span><span class="token punctuation">:</span> @<span class="token builtin">classmethod</span> <span class="token keyword">def</span> <span class="token function">name</span><span class="token punctuation">(</span>cls<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"chatwork"</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> token_api<span class="token punctuation">:</span> Text<span class="token punctuation">,</span> room_id<span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>room_id <span class="token operator">=</span> room_id self<span class="token punctuation">.</span>header <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"X-ChatWorkToken"</span><span class="token punctuation">:</span> token_api<span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">send_text_message</span><span class="token punctuation">(</span> self<span class="token punctuation">,</span> recipient_id<span class="token punctuation">:</span> Optional<span class="token punctuation">[</span>Text<span class="token punctuation">]</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> Text<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">:</span> Any <span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> uri <span class="token operator">=</span> <span class="token string">"https://api.chatwork.com/v2/rooms/"</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>room_id<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"/messages"</span> data <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"body"</span><span class="token punctuation">:</span> text<span class="token punctuation">}</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>uri<span class="token punctuation">,</span> headers<span class="token operator">=</span>self<span class="token punctuation">.</span>header<span class="token punctuation">,</span> data<span class="token operator">=</span>data<span class="token punctuation">)</span> |
Về cơ bản, class này chỉ bắn một POST request lên server Chatwork theo đúng cú pháp vào đúng phòng thôi. Nếu bạn muốn thêm một phát reply người gửi gốc cho ngầu, hãy sửa thêm như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <span class="token keyword">class</span> <span class="token class-name">ChatworkOutput</span><span class="token punctuation">(</span>OutputChannel<span class="token punctuation">)</span><span class="token punctuation">:</span> @<span class="token builtin">classmethod</span> <span class="token keyword">def</span> <span class="token function">name</span><span class="token punctuation">(</span>cls<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"chatwork"</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> token_api<span class="token punctuation">:</span> Text<span class="token punctuation">,</span> sender_id<span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">,</span> room_id<span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">,</span> message_id<span class="token punctuation">:</span> <span class="token builtin">int</span> <span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>room_id <span class="token operator">=</span> room_id self<span class="token punctuation">.</span>sender_id <span class="token operator">=</span> sender_id self<span class="token punctuation">.</span>message_id <span class="token operator">=</span> message_id self<span class="token punctuation">.</span>header <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"X-ChatWorkToken"</span><span class="token punctuation">:</span> token_api<span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">send_text_message</span><span class="token punctuation">(</span> self<span class="token punctuation">,</span> recipient_id<span class="token punctuation">:</span> Optional<span class="token punctuation">[</span>Text<span class="token punctuation">]</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> Text<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">:</span> Any <span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> uri <span class="token operator">=</span> <span class="token string">"https://api.chatwork.com/v2/rooms/"</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>room_id<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"/messages"</span> name <span class="token operator">=</span> <span class="token string">'Người lạ'</span> <span class="token keyword">for</span> contact <span class="token keyword">in</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"https://api.chatwork.com/v2/contacts"</span><span class="token punctuation">,</span> headers<span class="token operator">=</span>self<span class="token punctuation">.</span>header<span class="token punctuation">)</span><span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> contact<span class="token punctuation">[</span><span class="token string">"account_id"</span><span class="token punctuation">]</span> <span class="token operator">==</span> self<span class="token punctuation">.</span>sender_id<span class="token punctuation">:</span> name <span class="token operator">=</span> contact<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span> <span class="token keyword">break</span> text <span class="token operator">=</span> f<span class="token string">'[rp aid={self.sender_id} to={self.room_id}-{self.message_id}]{name}n'</span> <span class="token operator">+</span> text data <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"body"</span><span class="token punctuation">:</span> text<span class="token punctuation">}</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>uri<span class="token punctuation">,</span> headers<span class="token operator">=</span>self<span class="token punctuation">.</span>header<span class="token punctuation">,</span> data<span class="token operator">=</span>data<span class="token punctuation">)</span> |
Nhớ thay code tạo ChatworkOutput
object trong ChatworkInput
class nhé.
Tiếp đến, bạn cần cài đặt các API cần thiết.
Cả 2 đều có thể vào được từ mục API Setting của Chatwork:
Với API key, nhập password vào mục sau và bạn sẽ lấy được flag:
Còn với webhook secret token, click vào Webhook và tạo mới một mục:
trong đó secret token là mục Token, như đã được mình highlight. Thêm nữa, bạn cần điền vào mục Webhook URL theo như cấu trúc mình đã đặt trong ảnh. Ví dụ, nếu bạn chạy Rasa và không có port proxy pass gì (như với nginx
chẳng hạn) thì link đó sẽ là
1 2 | https://<server_ip>:5005/webhooks/chatwork/webhook |
Cuối cùng, bạn cần cài đặt các settings về API.
Hãy vào file credentials.yml
và thêm 3 dòng này vào dưới cùng
1 2 3 4 | <span class="token key atrule">chatwork_connector.ChatworkInput</span><span class="token punctuation">:</span> <span class="token key atrule">api_token</span><span class="token punctuation">:</span> <span class="token string">"put_your_api_token_here"</span> <span class="token key atrule">secret_token</span><span class="token punctuation">:</span> <span class="token string">"your_secret_token_too"</span> |
với các giá trị token được lấy từ bước trước.
Và vấn đề của Chatwork Webhook
Chưa kể việc Chatwork đổi theme làm mù mắt tôi đi, thì Chatwork webhook nhiều lúc chạy vô cùng dở. Điển hình là việc mình đã thử gửi một tin nhắn và đến 5′ sau thì Rasa mới nhận là đến Để chứng minh đây không phải là vấn đề của Rasa hay là internet, mình đã thử bắn replay lại đúng payload của Chatwork vào cái webhook, và mọi thứ xảy ra bình thường như chưa hề có cuộc chia ly:
Trong file chatwork_connector.py
như trên, trong hàm receive
của Sanic blueprint, bạn hãy sửa một chút để lấy được payload của Chatwork (5′ sau khi bạn gửi):
1 2 3 4 | <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">receive</span><span class="token punctuation">(</span>request<span class="token punctuation">:</span> Request<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> HTTPResponse<span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'X-ChatWorkWebhookSignature'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
Từ đó, bạn đã có payload để replay attack server rồi Đừng lo, bạn sẽ không thể bị hack như thế này ngoài đời thật đâu, vì Chatwork yêu cầu HTTPS nên sẽ không thể có Man-in-the-Middle (MITM) attack như mình đang làm bây giờ. Bật Postman lên và gửi vào webhook URL — nhớ bao gồm cả header để tin nhắn của bạn được validate nhé:
Mất có 1.5s thôi nhé chứ không phải 5′ đâu, biết rồi nhé. Mình cũng bắn request này từ máy mình đến server EC2 dùng để host con bot này, nên không phải latency localhost đâu.
Kết luận
Nếu bạn có thể, hãy vận động sếp đổi sang Slack. Nếu bạn không thể, hãy cố gắng cam chịu với nó, và sử dụng code này để cho cuộc đời bạn dễ thở hơn một tí tẹo Cảm ơn bạn đã đọc bài này, và chúc bạn may mắn.