Synology/2. Synology Chat 봇 개발하기

[Synology Chat 봇 만들기] 챕터1. Webhook & Bot 사용해보기

cha2hyun 2021. 12. 9. 19:49

안녕하세요 회사에서 사용하는 메신져를 슬랙에서 시놀로지 챗으로 옮겨오는 과정을 남겨보려합니다. synology chat에서 지원하는 공식문서가 굉장히 심플하고 구글링할 때도 자료가 많지 않기 때문에 어떤걸 할 수 있는지 예제와 함께 풀어보려합니다. 해당 챕터에서는 Django 서버를 사용합니다. 포트포워딩 및 서버 설치 과정은 이전 게시글을 확인해주시기 바랍니다.
2021.12.07 - [Synology/1. NAS를 서버로 사용하기] - 1. 시놀로지 설정, 로컬에서 DRF 프로젝트 생성 만들기 (docker, nginx, django, gunicorn)

 

1. 시놀로지 설정, 로컬에서 DRF 프로젝트 생성 만들기 (docker, nginx, django, gunicorn)

시놀로지에 서버를 띄우기 위해서 다음과 순서로 진행할 예정이다. 처음으로는 외부에서 NAS에 접속 가능하게 DDNS를 설정해 줄 것이고 그 다음으로는 외부에서 NAS 도커로 접속할 포트를 열어주

cha2hyun.tistory.com


이번 게시글에서는 다음과 같은 내용을 진행해보도록 하겠습니다.
1. 외부(Synology Chat)에서 로컬서버(Django)랑 통신하기
2. Synology Chat 들어오는 Webhooks 알아보기
3. Synology Chat 나가는 Webhooks 알아보기
4. Synology Chat 들어오는 Webhooks + 나가는 Webhooks 응용편
5. Synology Chat 슬래쉬 명령
6. Synology Chat 봇 알아보기
7. Synology Chat 봇 알아보기 응용

앞으로 챗봇으로 만들어보려는 건 다음과 같습니다.
- 챕터1] Synology Chat Webhook & Bot 사용해보기 (현재 게시글)
- 챕터2] 구글 시트 api 활용하여 시트의 데이터 조회하고 특정한 계산을 도출해내는 봇 (Bot)
- 챕터3] 카페24 api 활용하여 실시간 주문, 게시글을 채널에 알려주는 봇 (Incoming Webhook)
- 챕터4] Synology Calendar와 연동하여 하루 일정을 채널에 노티해주는 봇 (Incoming Webhook)
- 챕터5] 일정, 연차, 휴가 등을 Synology Calendar에 등록해주는 봇 (Bot)
- 챕터6] 네이버 API 이용하여 카페 게시물 크롤링 및 결과 도출 봇 (Bot)
- 챕터7] 유튜브 API 이용하여 동영상 정보 실시간으로 보여주는 봇 (Bot)


 

외부(Synology Chat)에서 로컬서버(Django)랑 통신하기

사용하는 환경은 로컬에서 진행하려 합니다. 서버 환경은 로컬이지만 Synology Chat에서 제 로컬 서버로 연결되기 위해서 다음 작업을 진행하였으니 참고해주시기 바랍니다.

1. settings.py 에서 ALLOWED_HOSTS = ['*'] 로 열어주었습니다.

모두 허용한다는 뜻

2. 서버 실행 시 python manage.py runserver 0.0.0.0:30000 으로 실행합니다. (30000포트를 열어놨습니다, 외부에서 접속하려면 0.0.0.0으로 열어주시면 됩니다)

0.0.0.0 으로 외부 접속이 가능하게 열어주면 됩니다.

3. 공유기에서 30000포트를 포트포워딩하면 어디서든 http://외부ip:30000 으로 통신이 가능합니다.

사용하시는 서버의 내부 IP 주소가 변경될 수 있으므로 DHCP가 아니라 수동으로 ip 설정하시는 걸 추천드립니다.

4. Synology Chat 에서 "test"라는 채널을 만들어 주었습니다.

열일하는 채대리

5. Synology Chat에서 지원하는 기능은 다음과 같습니다.

우측 상단 프로필사진-통합

- 들어오는 Webhooks : 서버에서 채널에 POST를 보내서 메세지를 채널에 울리게 할 수 있습니다.
- 나가는 Webhooks : 채널에서 서버로 데이터를 보낼 수 있습니다.
- 슬래시 명령 : 채널에 "/'를 입력해 명령어를 입력하여 서버로 명령어가 실행되었음을 알려줍니다.
- 봇 : 나가는 웹훅과 비슷한데 모든 내용을 서버로 리퀘스트 보냅니다. 단, 봇과 유저 1:1 채팅만 가능합니다.


Synology Chat 들어오는 Webhooks 알아보기

들어오는 Webhooks을 알아보겠습니다. 공식문서를 확인해보면 다음과 같습니다. 서버에서 해당 채널에 POST 를 payload라는 데이터와 함께 보내면 수신할 수 있다고 합니다.

알아보기 어려운 문서의 좋은(?) 예시

보시면 아시겠지만 payload로는 텍스트 혹은 이미지 밖에 전송되지 않습니다. 특정한 시간 혹은 조건이 되면 채널에 노티해주는 용도로 사용되면 좋을 것 같습니다. 바로 테스트를 진행해보겠습니다.

1. 통합 설정 > 들어오는 Webhooks > 생성 으로 적당한 이름을 만들어주시고 Webhook URL을 복사해줍니다.

복사버튼 그냥 누르시면 됩니다.

* 토큰 사이에 있는 %22는 "(큰따옴표) 를 의미합니다.
* https://xxx.synology.me:5001/ 가 아닌 https://xxx.synology.me/chat/ 으로 설정할 수 도 있습니다..


2. 해당 채널에서 "Hello World"를 보내보겠습니다. 어떠한 툴을 사용해도 괜찮습니다. 저는 "Talend API Tester" 구글 익스텐션을 사용하고 있습니다. METHOD 는 POST로 설정하고 URL을 붙혀넣기 하고. BODY에 공식문서 처럼 payload를 넣으시면 됩니다.

payload={"text": "Hello World"}

Talend API Tester


해당 리퀘스트를 보냈을 시 채널에 정상적으로 Hello World가 표시되었습니다 !

Hello World


꿀팁] 리스폰스가 너무 느릴경우 (30초 이상씩 걸릴 경우)
저의 경우 아래 사진 처럼 리스폰스가 30초 이상씩 걸려서 때려칠뻔했습니다. 아무리 구글링해도 나오질 않아서 DSM에서 여러 설정을 변경(삽질)하면서 알게되었는데요. 저는 다음처럼 설정 후 해결하였습니다.

평균 30초 이상 걸려서 진짜 ? 였었음... 문제점 해결한 후엔 20ms 정도로 슬랙보다 응답이 훨씬 빨라졌음

1. NAS를 재부팅해주시고 제어판 > 하드웨어 및 전원 > HDD 대기 기능에서 절전모드를 없음 체크
2. 출근 시간 전에 NAS를 재부팅되도록 전원 예약

한번 대기 기능으로 들어가면 채팅 API가 다시 깨어나질 않는 것 버그가 있는 것 같습니다. 이렇게 설정해주시면 20ms 안으로 응답 받으실 수 있습니다. 슬랙보다 빠르네요.


 

Synology Chat 나가는 Webhooks

나가는 웹훅은 채팅방에 어떠한 명령어가 트리거 됬을 시 내 서버로 해당 데이터가 트리거 되었다고 알려줍니다. 특정한 "단어"를 입력해서 트리거 시켜주면 입력한 Url로 데이터와 함께 리퀘스트가 보내집니다.

안좋은 공식문서 2번째 예시

공식문서에서는 알려주지 않지만 발신 Webhook에서는 http://외부ip주소:30000 이 불가능합니다.(봇은 가능) 도메인 주소가 꼭 필요한데 큰 문제는 아닙니다. 시놀로지 DDNS를 설정해 놓았으면 http://www.xxx.synology.me:30000/api_url/ 이런식으로 입력 할 수 있습니다. DDNS 설정이 궁금하면 이전 게시글을 참고해주시 바랍니다.

1. 간단하게 views.py 와 urls.py로 제가 설정해 놓은 단어가 채널에 트리거 되었을 시 print 하는 예제를 작성해보겠습니다.

- views.py
from rest_framework.views import APIView from rest_framework.response import Response class OutgoingWebhooks(APIView): def post(self, request): print(request.data) return Response(status=200)
- urls.py
from django.urls import path from . import views app_name = 'chatbot' urlpatterns = [ path(r'outgoing/', views.OutgoingWebhooks.as_view(), name='Outgoing'), ]

http://www.xxx.synology.me:30000/chatbot/outgoing/ 요청을 받고 어떻게 데이터가 들어왔는지 출력합니다.



2. 통합 설정 > 나가는 Webhooks > 생성 해주세요

저는 채팅방에 "나간다" 라고 입력하면 해당 url로 데이터가 가집니다.


3. "나간다" 라는 단어를 채팅방에 입력하니 트리거 되어 다음과 같이 데이터가 날라옵니다.

"나간다" 뿐만 아닌 "나간다 ㅎㅎ" 이런것도 트리거 됩니다. 단, "나간다잉" 이런건 안되네요 (띄어쓰기)

{ 'token': [ 'XXX' ], 'channel_id': [ '46' ], 'channel_type': [ '0' ], 'channel_name': [ 'test' ], 'user_id': [ '5' ], 'username': [ '채수현' ], 'post_id': [ '197568495647' ], 'thread_id': [ '0' ], 'timestamp': [ '1639045985401' ], 'text': [ '나간다' ], 'trigger_word': [ '나간다' ] }


아쉬운점] 슬랙 VS Synology Chat

슬랙에서는 outgoing webhook 사용 시 Modal을 잘 활용하였는데요, 시놀로지 채팅에서는 모달을 사용하지 못하기 때문에 나가는 웹훅은 사용이 좀 제한적일 것 같아요.

Slack Modal로 Outgoing Webhook을 보냈던 예시. 확실히 Slack이 더 기능이 많아서 좋..지만 비싸잖아

 


 

Synology Chat 들어오는 Webhooks + 나가는 Webhooks 응용편

이제 두가지 웹훅을 섞어서 몇시인지 물어보면 현재 시간을 알려주는 webhook을 만들어 보겠습니다. 사실 해당 기능은 아래에서 진행할 봇으로 활용하는게 더 좋긴 한데 예제로 생각 해주시고 봐주시기 바랍니다.

1. 나가는 웹훅 : "몇시야?" 가 트리거 되면
2. 들어오는 웹훅 : 현재 시간을 리턴해줌

예시의 최종 결과는 다음과 같습니다. "몇시야?" 라는 말에만 응답합니다.

"안녕" 에는 반응이 없지만 "몇시야?" 라는 단어가 트리거 되면 시간을 알려준다.

views.py
import requests from rest_framework.views import APIView from rest_framework.response import Response import requests, datetime class OutgoingWebhooks(APIView): headers = {'Content-Type': 'text/text; charset=utf-8'} api_url = "https://xxx.synology.me/chat/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=%22토큰%22" def post(self, request): if request.data.get("trigger_word") == "몇시야?": self.send_time() return Response(status=200) else: return Response(status=400) def send_time(self): message = str(datetime.datetime.now()) payload = 'payload={"text": "' + message + '"}' requests.post(self.api_url, data=payload.encode('utf-8'), headers=self.headers)

주의 - 공식문서 내용(?)처럼 json으로 보내면 안됩니다.

여기서도 꽤 삽질을 했던건데 공식문서에서는 json으로 데이터를 보내라 하였지만 실제로는 "payload="까지 포함되어있는 문자열을 보내야 했습니다. (심지어 띄어쓰기도 없이 동일하게 보내야한다) 추가로 한글의 경우 utf-8 인코딩을 해줘야 정확히 표시되어 requests의 data와 헤더에 인코딩 관련 부분을 추가하였습니다.

 


Synology Chat 슬래쉬 명령

나가는 웹훅이랑 비슷하지만 한번 만들면 어느 채널에서든 "/명령어"로 봇을 호출할 수 있습니다. 단점중의 하나로 명령어는 영어만 가능합니다. 공식문서는.. 별 내용 없습니다.

공식문서~ 끝 !


통합 설정에서 적당하게 만들어줍니다. /whattime 명령어가 실행되면 서버에서 출력해보겠습니다.


아까 만들어 놓았던 Outgoingwebhooks 클래스를 그대로 사용해보겠습니다.

views.py
import requests from rest_framework.views import APIView from rest_framework.response import Response import requests, datetime class OutgoingWebhooks(APIView): headers = {'Content-Type': 'text/text; charset=utf-8'} api_url = "https://xxx.synology.me:5001/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=%22토큰%22" def post(self, request): if request.data.get("trigger_word") == "몇시야?": self.send_time() else: print(request.text) return Response(status=200) def send_time(self): message = str(datetime.datetime.now()) payload = 'payload={"text": "' + message + '"}' requests.post(self.api_url, data=payload.encode('utf-8'), headers=self.headers)


그리고 아무 채널에서 해당 명령어를 입력해줍시다.

어느 채널이든 명령어를 실행시킬 수 있다.


서버에서 리스폰스는 다음과 같다.

별거 안준다..

어떠한 명령어가 주어졌는지 표시가 되기 때문에 서버에서는 명령어에 따라 if-else 문이나 switch 문을 통해서 어떤 명령을 줄 것인지 정하면 될 것 같습니다. 혹은 명령어별로 url을 다르게 설정해놓을 수 도 있습니다.

서버에서 어떤 유저가 어떤 명령어를 입력했는지 표시되기 때문에 text나가는 웹훅과 슬래쉬 명령은 비슷합니다. 다만 나가는 웹훅은 채팅창에 단어가 트리거 되면 유저의 의도와는 상관없이 리퀘스트가 가는 형태이고 슬래쉬 명령어는 직접 의도하고 명령어를 요청하는 거라 상황에 따라 맞는 봇을 개발하시는게 좋을 것 같습니다.


 

Synology Chat 봇 알아보기

봇도 outgoing webhook과 비슷한 개념인데 트리거 단어와 상관없이 모든 채팅 내용을 리퀘스트로 보내줍니다. 봇은 특정 채널에 포함될 수 없고 사용자처럼 새로 만들어집니다. 따라서 1:1 대화를 통해서만 가능합니다. 다음 챕터 "구글 시트 api 활용하여 시트의 데이터 조회하고 특정한 계산을 도출해내는 봇 (Bot)" 에서 봇을 사용해 볼 예정입니다. 우선 간단하게 봇 사용 방법을 알아보도록 하겠습니다.

앞서 만약 봇에게 무슨 말을 했을 경우 어떤 데이터들이 오는지 미리 말씀드리겠습니다.

{ 'token': [ 'xxxx' ], 'user_id': [ '5' ], 'username': [ '채수현' ], 'post_id': [ '274877906947' ], 'thread_id': [ '0' ], 'timestamp': [ '1639103934341' ], 'text': [ 'Hi' ] }

봇이랑 채널에서 대화를 하면 모든 대화마다 위 형식의 데이터를 받아오게 됩니다. 희한하게 토큰까지 같이 오기 때문에 url을 구성할때 토큰은 제외하는게 나중에 토큰이 바뀌어도 수동으로 변경하지 않아도 되어 좋을 것 같습니다.

url 예시
#asis url = "https://url/...&token=%22xx%22" #tobe url = f"https://url/...&token=%22{token}%22"


제가 보내는 모든 말을 따라하는 봇을 만드는 예시를 진행해보겠습니다.

views.py
from rest_framework.views import APIView from rest_framework.response import Response from .static import API_URL, CHAT, VERSION import requests class Bot(APIView): headers = {'Content-Type': 'text/text; charset=utf-8'} def post(self, request): if self.initialize(request): if self.send_message(self.text): return Response(status=200) return Response(status=400) else: return Response(status=400) def initialize(self, request): try: self.token = request.data.get("token") self.user_id = request.data.get("user_id") self.username = request.data.get("username") self.post_id = request.data.get("post_id") self.thread_id = request.data.get("thread_id") self.timestamp = request.data.get("timestamp") self.text = request.data.get("text") self.api = CHAT self.method = "chatbot" self.version = VERSION self.channel = f"{API_URL}?api={self.api}&method={self.method}&version={self.version}&token=%22{self.token}%22" return True except Exception as e: print(f"Initailize Error {e}") return False def send_message(self, message): payload = 'payload={"text": "' + message + '", "user_ids":[' + self.user_id + ']}' res = requests.post(self.channel, data=payload.encode('utf-8'), headers=self.headers) if res.status_code == 200: return True return False

해당 클래스를 살펴보겠습니다. 리퀘스트를 받으면 해당 데이터들을 모두 클래스 변수로 저장하는 것을 시도합니다. 시도에 성공하면 유저가 보낸 text를 똑같이 해당 채널에 보내주고 리퀘스트까지 성공하면 최종적으로 Response 200을 리턴합니다. 실패할 경우엔 400을 리턴합니다. 앞으로 여러 클래스를 만들어서 사용해야하기 때문에 변경될 가능성이 있는 url, api종류, 버전 등은 static.py 파일에 만들어서 관리하는게 좋습니다.

따라쟁이 봇 만들기 완성


통합설정에서 봇은 이렇게 만들었습니다. 봇 목록에서 숨기기는 체크해제 해주셔야 다른 유저들도 직접 봇을 추가해서 대화를 시작할 수 있습니다.

봇 목록에서 숨기기는 해제해주세요

 


 

Synology Chat 봇 알아보기 응용

만약 봇을 통해서 유저들에게 먼저 대화를 걸고 싶으시면 걸고 싶은 user_id를 받아와서 먼저 메세지를 보내면 됩니다. 공식문서를 확인해보면 다음과 같습니다.

공식문서가 참 심플하다


공식문서대로 Talend API Tester에 집어넣고 GET Method로 보내보겠습니다.


다음과 같은 유저 리스트를 반환해줍니다.

열일하는 채대리 2


위의 주소로 어떤 유저들에게 보낼 수 있는지 확인하고 유저들의 user_id가 무엇인지 확인해 볼 수 있습니다. 이것을 활용한다면 특정한 유저에게 봇이 먼저 대화를 걸 수 있게끔 코딩할 수 있습니다. 한가지 예시로 저희 챗에서는 총 20명의 유저가 있어서 모든 사람들에게 먼저 대화를 걸어서 봇이 만들어졌음을 알려보겠습니다.

from rest_framework.views import APIView 
from rest_framework.response import Response 
from .static import API_URL, CHAT, VERSION import requests 

class Bot(APIView): 
	headers = {'Content-Type': 'text/text; charset=utf-8'} 
	def post(self, request): 
    	if self.initialize(request): 
        	self.validate(self.text) 
            return Response(status=200) 
        else: 
    return Response(status=400) 
            
	def initialize(self, request): 
    	try: self.token = request.data.get("token") 
        	self.user_id = request.data.get("user_id") 
            self.username = request.data.get("username") 
            self.post_id = request.data.get("post_id") 
            self.thread_id = request.data.get("thread_id") 
            self.timestamp = request.data.get("timestamp") 
            self.text = request.data.get("text") 
            self.api = CHAT 
            self.method = "chatbot" 
            self.version = VERSION 
            self.channel = f"{API_URL}?api={self.api}&method={self.method}&version={self.version}&token=%22{self.token}%22" 
            return True 
    	except Exception as e: 
        	print(f"Initailize Error {e}") 
            return False 
            
    def send_message(self, message): 
   		payload = 'payload={"text": "' + message + '", "user_ids":[' + self.user_id + ']}' 
        res = requests.post(self.channel, data=payload.encode('utf-8'), headers=self.headers) 
        if res.status_code == 200: 
        	return True 
        return False 
        
    def validate(self, text): 	
    	if text == "greeting": 
        	self.greeting() 
        else: 
        	self.send_message(text(
            
    def greeting(self): 
    	for i in range(0,20): 
        	payload = 'payload={"text": "' + "안녕하세요 따라쟁이 봇입니다." + '", "user_ids":[' + str(i) + ']}' requests.post(self.channel, data=payload.encode('utf-8'), headers=self.headers)

봇과의 1:1 채팅창에 greeting 이라고 하면 해당 문구는 따라하지 않고 greeting 함수를 실행시켜서 0~19까지의 user_ids에 봇이 먼저 메세지를 보낼 수 있도록 해봤습니다.


챕터1]" Synology Chat Webhook & Bot 사용해보기"를 마치며... (api 활용 관점에서)

슬랙과 시놀로지 챗을 비교해 보았을 때 둘다 장단점은 있는 것 같습니다. 50인 미만의 사업장이라면 시놀로지 채팅도 충분히 활용가능한 좋은 대안이 될 수 있게 잘 만들어진 것 같습니다. 오히려 시놀로지의 경우 슬랙보다 리스폰스 시간이 훨씬 짧습니다. 슬랙은 채팅방에서 서버로 요청을 보내고 몇초 이내로 리스폰스 받지 못하면 에러를 표시합니다. 이게 시간이 너무 짧아서 서버에서 크롤링을 하거나 계산을 하게되면 바로 에러가 표시되더라구요 (이부분은 서버에서 쓰레드를 이용해서 리스폰스 200먼저 보내는 방식으로 해결 할 수 있습니다) 이런 부분에서 슬랙 보다는 시놀로지가 더 쾌적하게 느껴졌습니다.

시놀로지를 슬랙 대신 사용하는게 훨씬 저렴합니다. 예를 들어 50인 사업장의 경우, 슬랙에 매년 500만원 이상 + 서버비용 을 지불해야하는데 차라리 제가 구매한 하드웨어(거의 풀옵)으로 해도 140만원 안팍으로 서버와 클라우드까지 구성할 수 있습니다. (이전 게시글 참고) 비용 관점에서는 매우 좋지만, 사용하는 인원이 50명이 넘어갈 경우 채널을 관리하기에 어려울 것 같다는 생각도 들엇습니다.

Synology Chat api를 활용하는게 Slack애서 제공하는 Modal 처럼 직접 커스터마이징 하여 유저에게 편하게 봇을 사용할 수 있게 하는 기능들은 없습니다. 또한 익스텐션(앱), 커넥트, 사이드바섹션 등... 많은 기능들을 포기해야하지만!! 무료로 이용할 수 있다는 것에 충분한 메리트가 있다고 생각합니다.

공식문서가 조금 부족하고 구글링해도 잘 나오지 않는게 어려울 수 있으나 저와 함께 여러 컨텐츠를 진행해보면 좋을 것 같아요. 이제 챕터1은 여기서 마무리하고 다음 챕터2에서는 구글 시트 api 활용하여 시트의 데이터 조회하고 특정한 계산을 도출해내는 봇 (Bot)을 만드는 과정에 대해서 말씀드리도록 하겠습니다.

해당 게시글이 도움이 되셨으면 공감과 구독신청을 눌러주시기 바랍니다.