From d4183a012564c93591a5fb8cceae3613e2aa335b Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 20 Oct 2021 09:42:10 +0900 Subject: [PATCH 01/56] [google_chat_ros] google chat ros REST client add actionmsg, catkin install, ros action Add action server for google chat ros rename action msg, edit more info to readme add figs to readme and fix style Add roseus client add euslisp sample to readme add link for jsk internal users use dummy spacename Add some comments and fix typo key args to euslisp fix with flake8 fix README as review comment set default type name fix CMake install directory rename action name --- google_chat_ros/CMakeLists.txt | 43 +++++++++++ google_chat_ros/README.md | 67 +++++++++++++++++ google_chat_ros/action/SendMessage.action | 35 +++++++++ google_chat_ros/launch/google_chat.launch | 11 +++ google_chat_ros/package.xml | 25 +++++++ google_chat_ros/scripts/google-chat.l | 22 ++++++ .../scripts/google_chat_ros_server.py | 72 +++++++++++++++++++ google_chat_ros/setup.py | 9 +++ .../src/google_chat_ros/__init__.py | 0 .../src/google_chat_ros/google_chat.py | 34 +++++++++ 10 files changed, 318 insertions(+) create mode 100644 google_chat_ros/CMakeLists.txt create mode 100644 google_chat_ros/README.md create mode 100644 google_chat_ros/action/SendMessage.action create mode 100644 google_chat_ros/launch/google_chat.launch create mode 100644 google_chat_ros/package.xml create mode 100755 google_chat_ros/scripts/google-chat.l create mode 100755 google_chat_ros/scripts/google_chat_ros_server.py create mode 100644 google_chat_ros/setup.py create mode 100644 google_chat_ros/src/google_chat_ros/__init__.py create mode 100644 google_chat_ros/src/google_chat_ros/google_chat.py diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt new file mode 100644 index 000000000..2cd699742 --- /dev/null +++ b/google_chat_ros/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.8.3) +project(google_chat_ros) + +find_package( + catkin REQUIRED COMPONENTS + actionlib_msgs +) + +catkin_python_setup() + +add_action_files( + FILES + SendMessage.action +) + +generate_messages( + DEPENDENCIES + std_msgs + actionlib_msgs +) + +catkin_package() + +include_directories() + +# install +# euslisp +file(GLOB EUSLISP_SCRIPTS scripts/*.l) +install(FILES ${EUSLISP_SCRIPTS} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) +# python +file(GLOB PYTHON_SCRIPTS scripts/*.py) +catkin_install_python( + PROGRAMS ${PYTHON_SCRIPTS} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) + +# launch +install(DIRECTORY launch + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +) + diff --git a/google_chat_ros/README.md b/google_chat_ros/README.md new file mode 100644 index 000000000..9f07ac9d8 --- /dev/null +++ b/google_chat_ros/README.md @@ -0,0 +1,67 @@ +# The package for using Google chat services with ROS + +## What is this? +Use Google Chat API with ROS. +![Screenshot from 2021-11-01 15-53-27](https://user-images.githubusercontent.com/27789460/139635911-66232c88-d3b9-4d7d-940e-966fbac9d800.png) +System components +![GoogleChatROS_system](https://user-images.githubusercontent.com/27789460/139635648-4ddbf9da-90e9-4b87-b958-ca996a8ffc4f.png) + +## How to use? +### 1. Create a service account and private key +See [Google Official Document](https://developers.google.com/chat/how-tos/service-accounts#step_1_create_service_account_and_private_key). Please ensure to get JSON credetial file and save it. DO NOT LOST IT! +For JSK members, all keys are available at [Google Drive](https://drive.google.com/drive/folders/1Enbbta5QuZ-hrUWdTjVDEjDJc3j7Abxo?usp=sharing). If you make new API keys, please upload them here. + +### 2. Build ROS workspace +```bash +source /opt/ros/${ROS_DISTRO}/setup.bash +mkdir -p ~/catkin_ws/src && cd ~/catkin_ws/src +git clone https://github.com/jsk-ros-pkg/jsk_3rdparty +rosdep install --ignore-src --from-paths . -y -r +cd .. +catkin build +``` + +### 3. Use google chat ros +#### 3.1 Run the server +Execute +```bash +roslaunch google_chat_ros google_chat.launch keyfile:=${PATH_TO_keyfile.json} +``` +and run the action server. + +#### 2.2 Run the client +First, you have to identify your chat room. You can get it from chat room's URL. If it is `https://mail.google.com/chat/u/0/#chat/space/XXXXXXXXXXX`, `XXXXXXXXXXX` becomes the space name. +##### terminal example +```bash +rostopic pub /google_chat_ros/send/goal google_chat_ros/GoogleChatRESTActionGoal "header: + seq: 0 + stamp: + secs: 0 + nsecs: 0 + frame_id: '' +goal_id: + stamp: + secs: 0 + nsecs: 0 + id: '' +goal: + space: 'YOUR_SPACE' + message_type: 'text' + content: 'Hello world from ROS!'" +``` +##### roseus example +```lisp +(load "package://google_chat_ros/scripts/google-chat.l") +(send-google-chat-message "YOUR_SPACE" "text" "Hello world from eus!") +``` + +## Google Chat Message types +You can set Google Chat message type by setting `message_type` in ros message. +### text +Send simple text message. +### card +Send Google Chat Card message. +See [here](https://developers.google.com/chat/api/guides/message-formats/cards) for details. + +## Sending Images +You have to get image's permanent link and attach its url to card type message. To get it, please consider using jsk_3rdparty/gdrive_ros. diff --git a/google_chat_ros/action/SendMessage.action b/google_chat_ros/action/SendMessage.action new file mode 100644 index 000000000..4505fa57f --- /dev/null +++ b/google_chat_ros/action/SendMessage.action @@ -0,0 +1,35 @@ +# Define the goal +string space # space name, identify the chat room +string message_type # supports "text" or "card". +string content # if message_type is text, content is just string type. if message_type is card, content is expetcted json style below. See https://developers.google.com/chat/api/guides/message-formats/cards for details. +##### RAW JSON TEXT EXAMPLE ##### + # "sections": [ + # { + # "widgets": [ + # { + # "image": { "imageUrl": "https://..." } + # }, + # { + # "buttons": [ + # { + # "textButton": { + # "text": "OPEN IN GOOGLE MAPS", + # "onClick": { + # "openLink": { + # "url": "https://..." + # } + # } + # } + # } + # ] + # } + # ] + # } + # ] +### END RAW JSON TEXT EXAMPLE ### +--- +# Define the result +bool done +--- +# Define a feedback message +string status diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch new file mode 100644 index 000000000..e8c1a562e --- /dev/null +++ b/google_chat_ros/launch/google_chat.launch @@ -0,0 +1,11 @@ + + + + + + + keyfile: $(arg keyfile) + + + diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml new file mode 100644 index 000000000..65b1e9209 --- /dev/null +++ b/google_chat_ros/package.xml @@ -0,0 +1,25 @@ + + + google_chat_ros + 2.1.25 + Use Google Chat API clients via ROS + + Yoshiki Obinata + Kei Okada + + BSD + + catkin + python-setuptools + python3-setuptools + + python-googleapi + python3-googleapi + python-httplib2 + python-oauth2client + rospy + std_msgs + + + + diff --git a/google_chat_ros/scripts/google-chat.l b/google_chat_ros/scripts/google-chat.l new file mode 100755 index 000000000..992dc8253 --- /dev/null +++ b/google_chat_ros/scripts/google-chat.l @@ -0,0 +1,22 @@ +#!/usr/bin/env roseus + +(ros::load-ros-manifest "google_chat_ros") +(ros::roseus "google_chat_eus_client") + +(defun send-google-chat-message (space content &key (message-type "text") (topic-name "google_chat_ros/send") (wait nil)) + (when (boundp 'google_chat_ros::SendMessageAction) + (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) + (ac (instance ros::simple-action-client :init + topic-name google_chat_ros::SendMessageAction))) + (when (send ac :wait-for-server 1) + (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) + (send ac :cancel-goal) + (send ac :wait-for-result :timeout 5)) + (send goal :goal :space space) + (send goal :goal :message_type message-type) + (send goal :goal :content content) + (send ac :send-goal goal) + (if wait + (return-from send-google-chat-message (send ac :wait-for-result :timeout 5)) + (return-from send-google-chat-message t))))) +) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py new file mode 100755 index 000000000..b4d640398 --- /dev/null +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +import actionlib +from google_chat_ros.google_chat import GoogleChatRESTClient +from google_chat_ros.msg import SendMessageAction +from google_chat_ros.msg import SendMessageFeedback +from google_chat_ros.msg import SendMessageResult +import rospy + + +class GoogleChatActionServer: + """ + Send request to Google Chat REST API via ROS + """ + def __init__(self): + keyfile = rospy.get_param('~keyfile') + self._client = GoogleChatRESTClient(keyfile) + # Start google chat authentication and service + rospy.loginfo("Starting Google Chat service...") + try: + self._client.build_service() + rospy.loginfo("Succeeded in starting Google Chat service") + except Exception as e: + rospy.logwarn("Failed to start Google Chat service") + rospy.logerr(e) + # ActionLib + self._as = actionlib.SimpleActionServer( + '~send', SendMessageAction, + execute_cb=self.execute_cb, auto_start=False + ) + self._as.start() + + def execute_cb(self, goal): + feedback = SendMessageFeedback() + result = SendMessageResult() + r = rospy.Rate(1) + success = True + # start executing the action + space = goal.space + message_type = goal.message_type + content = goal.content + try: + # establish the service + self._client.build_service() + if message_type == 'text': + rospy.loginfo("Send text type message") + feedback.status = str( + self._client.send_text( + space=space, + text=content + )) + elif message_type == 'card': + rospy.loginfo("Send card type message") + feedback.status = str( + self._client.send_card( + space=space, + content=content + )) + except Exception as e: + rospy.logerr(str(e)) + feedback.status = str(e) + success = False + finally: + self._as.publish_feedback(feedback) + r.sleep() + result.done = success + self._as.set_succeeded(result) + + +if __name__ == '__main__': + rospy.init_node('google_chat') + server = GoogleChatActionServer() + rospy.spin() diff --git a/google_chat_ros/setup.py b/google_chat_ros/setup.py new file mode 100644 index 000000000..7953ab089 --- /dev/null +++ b/google_chat_ros/setup.py @@ -0,0 +1,9 @@ +from catkin_pkg.python_setup import generate_distutils_setup +from setuptools import setup + +d = generate_distutils_setup( + packages=['google_chat_ros'], + package_dir={'': 'src'} +) + +setup(**d) diff --git a/google_chat_ros/src/google_chat_ros/__init__.py b/google_chat_ros/src/google_chat_ros/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py new file mode 100644 index 000000000..8ec8b8ba3 --- /dev/null +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -0,0 +1,34 @@ +from __future__ import print_function + +from apiclient.discovery import build +from httplib2 import Http +from oauth2client.service_account import ServiceAccountCredentials + +class GoogleChatRESTClient(): + def __init__(self, keyfile): + self._auth_scopes = "https://www.googleapis.com/auth/chat.bot" + self.keyfile = keyfile + self.__credentials = None + self._chat = None + + def build_service(self): + """Authenticate Googel REST API and start service by json key file. + Please see https://developers.google.com/chat/how-tos/service-accounts#step_1_create_service_account_and_private_key for details. + :param keyfile_path: str, the file path of json key file + """ + self.__credentials = ServiceAccountCredentials.from_json_keyfile_name(self.keyfile, self._auth_scopes) + self._chat = build('chat', 'v1', http=self.__credentials.authorize(Http())) + + def send_text(self, space, text): + parent = 'spaces/' + space + # returns same 403 error both authenticate error and not connected error + return self._chat.spaces().messages().create(parent=parent, body={'text': text}).execute() + + def send_card(self, space, content): + pass + + def list_members(self, space): + """Show member list in the space. + """ + parent = 'spaces/' + space + return self._chat.spaces().members().list(parent=parent).execute() From 31bd5e0c1a43feeffc8691f75998e40092688639 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 28 Dec 2021 16:06:21 +0900 Subject: [PATCH 02/56] [google_chat_ros] define new message for recieving message add space activity msg fix message names, add more units add rosparam in launch using request, not flask fix so many bugs and works fine with dm, still bugs with space supress SSL reset error and add header debug fix unicode bugs, set default dic value --- google_chat_ros/CMakeLists.txt | 17 ++ google_chat_ros/launch/google_chat.launch | 17 +- google_chat_ros/msg/Annotation.msg | 8 + google_chat_ros/msg/Attachment.msg | 10 ++ google_chat_ros/msg/Message.msg | 8 + google_chat_ros/msg/MessageEvent.msg | 5 + google_chat_ros/msg/SlashCommand.msg | 6 + google_chat_ros/msg/Space.msg | 4 + google_chat_ros/msg/SpaceEvent.msg | 5 + google_chat_ros/msg/User.msg | 7 + google_chat_ros/package.xml | 6 + .../scripts/google_chat_ros_server.py | 170 ++++++++++++++++-- .../src/google_chat_ros/google_chat.py | 99 ++++++++++ 13 files changed, 347 insertions(+), 15 deletions(-) create mode 100644 google_chat_ros/msg/Annotation.msg create mode 100644 google_chat_ros/msg/Attachment.msg create mode 100644 google_chat_ros/msg/Message.msg create mode 100644 google_chat_ros/msg/MessageEvent.msg create mode 100644 google_chat_ros/msg/SlashCommand.msg create mode 100644 google_chat_ros/msg/Space.msg create mode 100644 google_chat_ros/msg/SpaceEvent.msg create mode 100644 google_chat_ros/msg/User.msg diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 2cd699742..50233fa39 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -3,11 +3,27 @@ project(google_chat_ros) find_package( catkin REQUIRED COMPONENTS + rospy actionlib_msgs + sensor_msgs + std_msgs + message_generation ) catkin_python_setup() +add_message_files( + FILES + Annotation.msg + Attachment.msg + MessageEvent.msg + Message.msg + SlashCommand.msg + Space.msg + SpaceEvent.msg + User.msg +) + add_action_files( FILES SendMessage.action @@ -15,6 +31,7 @@ add_action_files( generate_messages( DEPENDENCIES + sensor_msgs std_msgs actionlib_msgs ) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index e8c1a562e..7dbdc46d3 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -1,11 +1,24 @@ - + + + + + + + + + + + - keyfile: $(arg keyfile) + recieving_mode: $(arg recieving_mode) + download_data_timeout: $(arg download_data_timeout) + download_data: $(arg download_data) + download_avatar: $(arg download_avatar) diff --git a/google_chat_ros/msg/Annotation.msg b/google_chat_ros/msg/Annotation.msg new file mode 100644 index 000000000..720f6a16f --- /dev/null +++ b/google_chat_ros/msg/Annotation.msg @@ -0,0 +1,8 @@ +int32 length +int32 start_index +google_chat_ros/User user +## START AnnotationType ## +bool mention +bool slash_command +### END AnnotationType ### +google_chat_ros/SlashCommand command diff --git a/google_chat_ros/msg/Attachment.msg b/google_chat_ros/msg/Attachment.msg new file mode 100644 index 000000000..8b4be8f10 --- /dev/null +++ b/google_chat_ros/msg/Attachment.msg @@ -0,0 +1,10 @@ +string name +string content_name +string content_type +string thumnail_uri +string download_uri +string localpath +bool drive_file +bool uploaded_content +string attachment_resource_name +string drive_field_id diff --git a/google_chat_ros/msg/Message.msg b/google_chat_ros/msg/Message.msg new file mode 100644 index 000000000..fa28dbcd0 --- /dev/null +++ b/google_chat_ros/msg/Message.msg @@ -0,0 +1,8 @@ +string name +google_chat_ros/User sender +string create_time +string text +string thread_name +google_chat_ros/Annotation[] annotations +string argument_text +google_chat_ros/Attachment[] attachments diff --git a/google_chat_ros/msg/MessageEvent.msg b/google_chat_ros/msg/MessageEvent.msg new file mode 100644 index 000000000..12d9153f0 --- /dev/null +++ b/google_chat_ros/msg/MessageEvent.msg @@ -0,0 +1,5 @@ +# ROS message for recieving Google Chat message +string event_time +google_chat_ros/Space space +google_chat_ros/Message message +google_chat_ros/User user diff --git a/google_chat_ros/msg/SlashCommand.msg b/google_chat_ros/msg/SlashCommand.msg new file mode 100644 index 000000000..cd1dad57f --- /dev/null +++ b/google_chat_ros/msg/SlashCommand.msg @@ -0,0 +1,6 @@ +google_chat_ros/User user +bool added +bool invoke +string command_name +string command_id +bool triggers_dialog diff --git a/google_chat_ros/msg/Space.msg b/google_chat_ros/msg/Space.msg new file mode 100644 index 000000000..692b40cd2 --- /dev/null +++ b/google_chat_ros/msg/Space.msg @@ -0,0 +1,4 @@ +string name +string display_name +bool room # is chat room +bool dm # is direct message diff --git a/google_chat_ros/msg/SpaceEvent.msg b/google_chat_ros/msg/SpaceEvent.msg new file mode 100644 index 000000000..079edb4bb --- /dev/null +++ b/google_chat_ros/msg/SpaceEvent.msg @@ -0,0 +1,5 @@ +bool added +bool removed +string event_time +google_chat_ros/Space space +google_chat_ros/User user diff --git a/google_chat_ros/msg/User.msg b/google_chat_ros/msg/User.msg new file mode 100644 index 000000000..d813fdf47 --- /dev/null +++ b/google_chat_ros/msg/User.msg @@ -0,0 +1,7 @@ +string name +string display_name +string avatar_url +sensor_msgs/Image avatar +string email +bool bot +bool human diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index 65b1e9209..a6fb1a237 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -13,11 +13,17 @@ python-setuptools python3-setuptools + message_generation + python-googleapi python3-googleapi + message_runtime python-httplib2 python-oauth2client + python-flask + python-gdown rospy + sensor_msgs std_msgs diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index b4d640398..22671c426 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -1,35 +1,71 @@ #!/usr/bin/env python import actionlib +import gdown from google_chat_ros.google_chat import GoogleChatRESTClient +from google_chat_ros.google_chat import GoogleChatHTTPSServer +from google_chat_ros.msg import * from google_chat_ros.msg import SendMessageAction from google_chat_ros.msg import SendMessageFeedback from google_chat_ros.msg import SendMessageResult +import requests +from requests.exceptions import Timeout import rospy -class GoogleChatActionServer: +class GoogleChatROS(object): """ Send request to Google Chat REST API via ROS """ def __init__(self): - keyfile = rospy.get_param('~keyfile') - self._client = GoogleChatRESTClient(keyfile) - # Start google chat authentication and service - rospy.loginfo("Starting Google Chat service...") + recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'dialogflow', 'url', 'none' + + # For REST, sending message + rest_keyfile = rospy.get_param('~google_cloud_credentials_json') + self._client = GoogleChatRESTClient(rest_keyfile) + rospy.loginfo("Starting Google Chat REST service...") try: - self._client.build_service() - rospy.loginfo("Succeeded in starting Google Chat service") + self._client.build_service() # Start google chat authentication and service + rospy.loginfo("Succeeded in starting Google Chat REST service") except Exception as e: - rospy.logwarn("Failed to start Google Chat service") + rospy.logwarn("Failed to start Google Chat REST service") rospy.logerr(e) - # ActionLib + + # For POST, recieving message + if recieving_chat_mode in ("url", "dialogflow"): + self.host = rospy.get_param('~host') + self.port = int(rospy.get_param('~port')) + self.ssl_certfile = rospy.get_param('~ssl_certfile') + self.ssl_keyfile = rospy.get_param('~ssl_keyfile') + self.download_timeout = rospy.get_param('~download_data_timeout') + self.download_data = rospy.get_param('~download_data') + self.download_avatar = rospy.get_param('~download_avatar') + self._recieve_message_pub = rospy.Publisher("~recieve", MessageEvent, queue_size=1) + self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) + rospy.loginfo("Starting Google Chat HTTPS server...") + self._server = GoogleChatHTTPSServer( + self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.https_post_cb) + self._server.run() + # elif recieving_chat_mode == "dialogflow": + # # TODO: subscribe dialogflow msg and pipe to chat message + # pass + elif recieving_chat_mode == "none": + pass + else: + rospy.logerr("Please choose recieving_mode param from dialogflow, https, none.") + + rospy.on_shutdown(self.killnode) + + # ROS ActionLib for REST self._as = actionlib.SimpleActionServer( '~send', SendMessageAction, - execute_cb=self.execute_cb, auto_start=False + execute_cb=self.rest_cb, auto_start=False ) self._as.start() - def execute_cb(self, goal): + def killnode(self): + self._server.kill() + + def rest_cb(self, goal): feedback = SendMessageFeedback() result = SendMessageResult() r = rospy.Rate(1) @@ -65,8 +101,116 @@ def execute_cb(self, goal): result.done = success self._as.set_succeeded(result) + def https_post_cb(self, event): + """Parse Google Chat API json content and publish as a ROS Message. + See https://developers.google.com/chat/api/reference/rest + to check what contents are included in the json. + :param event: A google Chat API POST request json content. + See https://developers.google.com/chat/api/guides/message-formats/events#event_fields for details. + :rtype: None + """ + # rospy.loginfo(str(event)) + # event/eventTime + event_time = event['eventTime'] + # event/space + space = Space() + space.name = event['space']['name'] + space.room = True if event['space']['type'] == "ROOM" else False + if space.room: + space.display_name = event['space']['displayName'] + space.dm = True if event['space']['type'] == "DM" else False + # event/user + user = self._get_user_info(event['user']) + if event['type'] == 'ADDED_TO_SPACE' or event['type'] == 'REMOVED_FROM_SPACE': + msg = SpaceEvent() + msg.event_time = event_time + msg.space = space + msg.user = user + msg.added = True if event['type'] == "ADDED_TO_SPACE" else False + msg.removed = True if event['type'] == "REMOVED_FROM_SPACE" else False + self._space_activity_pub.publish(msg) + return + elif event['type'] == 'MESSAGE': + msg = MessageEvent() + msg.event_time = event_time + msg.space = space + msg.user = user + message = Message() + message_content = event['message'] + message.name = message_content.get('name') + # event/message/sender + message.sender = self._get_user_info(message_content['sender']) + message.create_time = message_content.get('createTime') + message.text = message_content.get('text') + message.thread_name = message_content.get('thread').get('name') # TODO maybe error if not exists + # event/messsage/annotations + if 'annotations' in message_content: + for item in message_content['annotations']: + annotation = Annotation() + annotation.length = item.get('length') + annotation.start_index = item.get('startIndex') + annotation.mention = True if item.get('type') == 'USER_MENTION' else False + if annotation.mention: + annotation.user = self._get_user_info(item['userMention']['user']) + annotation.slash_command = True if item.get('type') == 'SLASH_COMMAND' else False + message.annotations.append(annotation) + message.argument_text = message_content.get('argumentText', '') + # event/message/attachment + if 'attachment' in message_content: + for item in message_content['attachment']: + message.attachments.append(self._get_attachment(item)) + msg.message = message + rospy.logwarn(msg.event_time) + try: + self._recieve_message_pub.publish(msg) + except Exception as e: + from IPython.terminal import embed; ipshell=embed.InteractiveShellEmbed(config=embed.load_default_config())(local_ns=locals()) + return + else: + rospy.logerr("Got unknown event type.") + return + + def _get_user_info(self, item): + user = User() + user.name = item.get('name', '') + user.display_name = item.get('displayName', '') + user.avatar_url = item.get('avatarUrl', '') + if self.download_avatar: + user.avatar = self._get_image_from_uri(item['avatar']) + user.email = item.get('email', '') + user.bot = True if item.get('type') == "BOT" else False + user.human = True if item.get('type') == "HUMAN" else False + return user + + def _get_attachment(self, item, download=False): + attachment = Attachment() + attachment.name = item.get('name', '') + attachment.content_name = item.get('contentName', '') + attachment.content_type = item.get('contentType', '') + attachment.thumnail_uri = item.get('thumnailUri', '') + attachment.download_uri = item.get('downloadUri', '') + attachment.drive_file = True if item.get('source') == 'DRIVE_FILE' else False + attachment.uploaded_content = True if item.get('source') == 'UPLOADED_CONTENT' else False + attachment.attachment_resource_name = item.get('attachmentDataRef', {}).get('resourceName', '') + attachment.drive_field_id = item.get('driveDataRef', {}).get('driveFileId', '') + # attachment.localpath = item[''] # TODO enable download option + return attachment + + def _get_image_from_uri(self, uri): + img = None + try: + img = requests.get(uri, timeout=self.download_timeout).content + except Timeout: + rospy.logerr("Exceeded timelimit ({} sec) when downloading {}.".format(self.download_timeout, uri)) + except Exception as e: + rospy.logwarn("Failed to get image from {}".format(uri)) + rospy.logerr(e) + return img + + def _download_attachment_from_uri(self, uri): + return if __name__ == '__main__': - rospy.init_node('google_chat') - server = GoogleChatActionServer() + rospy.init_node('google_chat', disable_signals=True) + node = GoogleChatROS() rospy.spin() diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 8ec8b8ba3..14fc25399 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -2,7 +2,12 @@ from apiclient.discovery import build from httplib2 import Http +import http.server as s +import json from oauth2client.service_account import ServiceAccountCredentials +import rospy # for logging +import socket +import ssl class GoogleChatRESTClient(): def __init__(self, keyfile): @@ -32,3 +37,97 @@ def list_members(self, space): """ parent = 'spaces/' + space return self._chat.spaces().members().list(parent=parent).execute() + +class GoogleChatHTTPSServer(): + """The server for getting https request from Google Chat + """ + def __init__(self, host, port, certfile, keyfile, callback): + """ + :param host: str, hostname + :param port: int, port number + :param certfile: str, ssl certfile path + :param keyfile: str, ssl keyfile path + """ + self._host = host + self._port = port + self._certfile = certfile + self._keyfile = keyfile + self._callback = callback + + def __handler(self, *args): + try: + GoogleChatHTTPSHandler(self._callback, *args) + except socket.error as e: + if e.errno == 104: # SSL Connection reset error which can be ignored + rospy.logdebug(e) + else: + raise e + + def run(self): + self._httpd = s.HTTPServer((self._host, self._port), self.__handler) + self._httpd.socket = ssl.wrap_socket(self._httpd.socket, certfile=self._certfile, keyfile=self._keyfile) + self._httpd.serve_forever() + + def kill(self): + self._httpd.shutdown() + +class GoogleChatHTTPSHandler(s.BaseHTTPRequestHandler): + """The handler for https request from Google chat API. Mainly used for recieving messages, events. + """ + def __init__(self, callback, *args): + self._callback = callback + s.BaseHTTPRequestHandler.__init__(self, *args) + + def do_POST(self): + """Handles an event from Google Chat. + Please see https://developers.google.com/chat/api/guides/message-formats/events for details. + """ + user_agent = self.headers.get("User-Agent") + print('user_agent' + str(user_agent)) + self._parse_json() + self._callback(self.json_content) + self._response() + + def _parse_json(self): + content_len = int(self.headers.get("content-length")) + request_body = self.rfile.read(content_len).decode('utf-8') + rospy.loginfo(str(request_body)) + self.json_content = json.loads(request_body, object_hook=self._decode_dict) # json.loads returns unicode by default + + def _response(self): + self.send_response(200) + # self.send_header('Content-type', 'application/json; charset=utf-8') + # self.send_header('Content-length', len(self.res_body.encode())) + self.end_headers() + # self.wfile.write(self.res_body.encode('utf-8')) + + def _bad_request(self): + self.send_response(400) + self.end_headers() + + ### helper functions + def _decode_list(self, data): + rv = [] + for item in data: + if isinstance(item, unicode): + item = item.encode('utf-8') + elif isinstance(item, list): + item = self._decode_list(item) + elif isinstance(item, dict): + item = self._decode_dict(item) + rv.append(item) + return rv + + def _decode_dict(self, data): + rv = {} + for key, value in data.iteritems(): + if isinstance(key, unicode): + key = key.encode('utf-8') + if isinstance(value, unicode): + value = value.encode('utf-8') + elif isinstance(value, list): + value = self._decode_list(value) + elif isinstance(value, dict): + value = self._decode_dict(value) + rv[key] = value + return rv From 287bd9066edaf2a21b04e8fa25b51b233f340a3c Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 13 Jan 2022 16:23:38 +0900 Subject: [PATCH 03/56] [google_chat_ros] support download data --- google_chat_ros/launch/google_chat.launch | 2 + .../scripts/google_chat_ros_server.py | 76 +++++++++++++------ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 7dbdc46d3..804d46041 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -2,6 +2,7 @@ + @@ -18,6 +19,7 @@ recieving_mode: $(arg recieving_mode) download_data_timeout: $(arg download_data_timeout) download_data: $(arg download_data) + download_directory: $(arg download_directory) download_avatar: $(arg download_avatar) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 22671c426..c64b36447 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -7,6 +7,7 @@ from google_chat_ros.msg import SendMessageAction from google_chat_ros.msg import SendMessageFeedback from google_chat_ros.msg import SendMessageResult +import os import requests from requests.exceptions import Timeout import rospy @@ -36,8 +37,8 @@ def __init__(self): self.port = int(rospy.get_param('~port')) self.ssl_certfile = rospy.get_param('~ssl_certfile') self.ssl_keyfile = rospy.get_param('~ssl_keyfile') - self.download_timeout = rospy.get_param('~download_data_timeout') self.download_data = rospy.get_param('~download_data') + self.download_directory = rospy.get_param('~download_directory') self.download_avatar = rospy.get_param('~download_avatar') self._recieve_message_pub = rospy.Publisher("~recieve", MessageEvent, queue_size=1) self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) @@ -109,18 +110,19 @@ def https_post_cb(self, event): See https://developers.google.com/chat/api/guides/message-formats/events#event_fields for details. :rtype: None """ - # rospy.loginfo(str(event)) + # GET EVENT TYPE # event/eventTime - event_time = event['eventTime'] + event_time = event.get('eventTime') # event/space space = Space() - space.name = event['space']['name'] - space.room = True if event['space']['type'] == "ROOM" else False + space.name = event.get('space').get('name') + space.room = True if event.get('space').get('type') == "ROOM" else False if space.room: - space.display_name = event['space']['displayName'] - space.dm = True if event['space']['type'] == "DM" else False + space.display_name = event.get('space').get('displayName') + space.dm = True if event.get('space').get('type') == "DM" else False # event/user - user = self._get_user_info(event['user']) + user = self._get_user_info(event.get('user')) + if event['type'] == 'ADDED_TO_SPACE' or event['type'] == 'REMOVED_FROM_SPACE': msg = SpaceEvent() msg.event_time = event_time @@ -130,19 +132,22 @@ def https_post_cb(self, event): msg.removed = True if event['type'] == "REMOVED_FROM_SPACE" else False self._space_activity_pub.publish(msg) return + elif event['type'] == 'MESSAGE': msg = MessageEvent() msg.event_time = event_time msg.space = space msg.user = user message = Message() - message_content = event['message'] - message.name = message_content.get('name') + message_content = event.get('message', '') + message.name = message_content.get('name', '') + # event/message/sender - message.sender = self._get_user_info(message_content['sender']) - message.create_time = message_content.get('createTime') - message.text = message_content.get('text') - message.thread_name = message_content.get('thread').get('name') # TODO maybe error if not exists + message.sender = self._get_user_info(message_content.get('sender')) + message.create_time = message_content.get('createTime', '') + message.text = message_content.get('text', '') + message.thread_name = message_content.get('thread', {}).get('name', '') + # event/messsage/annotations if 'annotations' in message_content: for item in message_content['annotations']: @@ -151,21 +156,28 @@ def https_post_cb(self, event): annotation.start_index = item.get('startIndex') annotation.mention = True if item.get('type') == 'USER_MENTION' else False if annotation.mention: - annotation.user = self._get_user_info(item['userMention']['user']) + annotation.user = self._get_user_info(item.get('userMention').get('user')) annotation.slash_command = True if item.get('type') == 'SLASH_COMMAND' else False message.annotations.append(annotation) message.argument_text = message_content.get('argumentText', '') + # event/message/attachment if 'attachment' in message_content: for item in message_content['attachment']: message.attachments.append(self._get_attachment(item)) msg.message = message - rospy.logwarn(msg.event_time) + + # rospy Publish try: self._recieve_message_pub.publish(msg) except Exception as e: from IPython.terminal import embed; ipshell=embed.InteractiveShellEmbed(config=embed.load_default_config())(local_ns=locals()) return + + elif event['type'] == 'CARD_CLICKED': + # TODO + return + else: rospy.logerr("Got unknown event type.") return @@ -182,7 +194,7 @@ def _get_user_info(self, item): user.human = True if item.get('type') == "HUMAN" else False return user - def _get_attachment(self, item, download=False): + def _get_attachment(self, item): attachment = Attachment() attachment.name = item.get('name', '') attachment.content_name = item.get('contentName', '') @@ -193,22 +205,38 @@ def _get_attachment(self, item, download=False): attachment.uploaded_content = True if item.get('source') == 'UPLOADED_CONTENT' else False attachment.attachment_resource_name = item.get('attachmentDataRef', {}).get('resourceName', '') attachment.drive_field_id = item.get('driveDataRef', {}).get('driveFileId', '') - # attachment.localpath = item[''] # TODO enable download option + if self.download_data and attachment.download_uri: + self._download_content(uri=attachment.download_uri, filename=attachment.content_name) + if self.download_data and attachment.drive_field_id: + self._download_content(drive_id=attachment.drive_field_id) return attachment def _get_image_from_uri(self, uri): - img = None try: - img = requests.get(uri, timeout=self.download_timeout).content + img = requests.get(uri, timeout=10).content except Timeout: - rospy.logerr("Exceeded timelimit ({} sec) when downloading {}.".format(self.download_timeout, uri)) + rospy.logerr("Exceeded timeout when downloading {}.".format(uri)) except Exception as e: rospy.logwarn("Failed to get image from {}".format(uri)) rospy.logerr(e) - return img + else: + return img - def _download_attachment_from_uri(self, uri): - return + def _download_content(self, uri=None, drive_id=None, filename=''): + try: + if drive_id: + path = os.path.join(self.download_directory, drive_id) + gdown.download(id=drive_id, output=path) + elif uri: + path = os.path.join(self.download_directory, filename) + gdown.download(url=uri, output=path) + except Exception as e: + rospy.logwarn("Failed to download the attatched file.") + rospy.logerr(e) + else: + rospy.loginfo("Suceeded in downloading the attached file. Saved at {}".format(path)) + finally: + return path if __name__ == '__main__': rospy.init_node('google_chat', disable_signals=True) From 00bbf87e95080f0c9b16a4f5b3d3aa9329e122ff Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 13 Jan 2022 19:57:07 +0900 Subject: [PATCH 04/56] [google_chat_ros]fix REST bugs --- .../scripts/google_chat_ros_server.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index c64b36447..25e0893ec 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -27,12 +27,19 @@ def __init__(self): try: self._client.build_service() # Start google chat authentication and service rospy.loginfo("Succeeded in starting Google Chat REST service") + # ROS ActionLib + self._as = actionlib.SimpleActionServer( + '~send', SendMessageAction, + execute_cb=self.rest_cb, auto_start=False + ) + self._as.start() except Exception as e: rospy.logwarn("Failed to start Google Chat REST service") rospy.logerr(e) # For POST, recieving message if recieving_chat_mode in ("url", "dialogflow"): + # rosparams self.host = rospy.get_param('~host') self.port = int(rospy.get_param('~port')) self.ssl_certfile = rospy.get_param('~ssl_certfile') @@ -40,6 +47,8 @@ def __init__(self): self.download_data = rospy.get_param('~download_data') self.download_directory = rospy.get_param('~download_directory') self.download_avatar = rospy.get_param('~download_avatar') + rospy.on_shutdown(self.killnode) # shutdown https server + # ROS publisher self._recieve_message_pub = rospy.Publisher("~recieve", MessageEvent, queue_size=1) self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) rospy.loginfo("Starting Google Chat HTTPS server...") @@ -54,15 +63,6 @@ def __init__(self): else: rospy.logerr("Please choose recieving_mode param from dialogflow, https, none.") - rospy.on_shutdown(self.killnode) - - # ROS ActionLib for REST - self._as = actionlib.SimpleActionServer( - '~send', SendMessageAction, - execute_cb=self.rest_cb, auto_start=False - ) - self._as.start() - def killnode(self): self._server.kill() From 0bfec1f61d60a1fc365976f3a11b1d187c50a160 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 13 Jan 2022 21:19:36 +0900 Subject: [PATCH 05/56] [google_chat_ros]support dialogflow mode --- google_chat_ros/launch/google_chat.launch | 5 --- .../scripts/google_chat_ros_server.py | 34 ++++++++++++++----- .../src/google_chat_ros/google_chat.py | 1 - 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 804d46041..064276bcd 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -5,11 +5,6 @@ - - - - - Date: Thu, 13 Jan 2022 21:40:39 +0900 Subject: [PATCH 06/56] [google_chat_ros]support download avatar --- google_chat_ros/msg/User.msg | 2 +- google_chat_ros/scripts/google_chat_ros_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/google_chat_ros/msg/User.msg b/google_chat_ros/msg/User.msg index d813fdf47..268c4c5e4 100644 --- a/google_chat_ros/msg/User.msg +++ b/google_chat_ros/msg/User.msg @@ -1,7 +1,7 @@ string name string display_name string avatar_url -sensor_msgs/Image avatar +uint8[] avatar string email bool bot bool human diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 0e4b9d1bf..61b014418 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -206,7 +206,7 @@ def _get_user_info(self, item): user.display_name = item.get('displayName', '') user.avatar_url = item.get('avatarUrl', '') if self.download_avatar: - user.avatar = self._get_image_from_uri(item['avatar']) + user.avatar = self._get_image_from_uri(user.avatar_url) user.email = item.get('email', '') user.bot = True if item.get('type') == "BOT" else False user.human = True if item.get('type') == "HUMAN" else False From 78cea55f6b9eabe51d3feb0509510318f378bb95 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 13 Jan 2022 22:22:09 +0900 Subject: [PATCH 07/56] [google_chat_ros]support dialogflow message publish --- google_chat_ros/package.xml | 1 + .../scripts/google_chat_ros_server.py | 54 ++++++++++++------- .../src/google_chat_ros/google_chat.py | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index a6fb1a237..011c04f3e 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -25,6 +25,7 @@ rospy sensor_msgs std_msgs + dialogflow_task_executive diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 61b014418..ce18e50b6 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import actionlib +from dialogflow_task_executive.msg import DialogResponse import gdown from google_chat_ros.google_chat import GoogleChatRESTClient from google_chat_ros.google_chat import GoogleChatHTTPSServer @@ -7,9 +8,11 @@ from google_chat_ros.msg import SendMessageAction from google_chat_ros.msg import SendMessageFeedback from google_chat_ros.msg import SendMessageResult +import json import os import requests from requests.exceptions import Timeout +from requests.exceptions import ConnectionError import rospy @@ -29,9 +32,9 @@ def __init__(self): rospy.loginfo("Succeeded in starting Google Chat REST service") # ROS ActionLib self._as = actionlib.SimpleActionServer( - '~send', SendMessageAction, - execute_cb=self.rest_cb, auto_start=False - ) + '~send', SendMessageAction, + execute_cb=self.rest_cb, auto_start=False + ) self._as.start() except Exception as e: rospy.logwarn("Failed to start Google Chat REST service") @@ -53,20 +56,22 @@ def __init__(self): self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) rospy.loginfo("Starting Google Chat HTTPS server...") - # try: - if recieving_chat_mode == "url": - rospy.loginfo("Expected to get Google Chat Bot URL request") - self._server = GoogleChatHTTPSServer( - self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb) - elif recieving_chat_mode == "dialogflow": - rospy.loginfo("Expected to get Google Chat Dialogflow request") - self._server = GoogleChatHTTPSServer( - self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.dialogflow_cb) - # TODO: dialogflow publisher - self._server.run() - # except Exception as e: - # rospy.logwarn("The error occurred while starting HTTPS server") - # rospy.logerr(e) + try: + if recieving_chat_mode == "url": + rospy.loginfo("Expected to get Google Chat Bot URL request") + self._server = GoogleChatHTTPSServer( + self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb) + elif recieving_chat_mode == "dialogflow": + rospy.loginfo("Expected to get Google Chat Dialogflow request") + self._server = GoogleChatHTTPSServer( + self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.dialogflow_cb) + self._dialogflow_pub = rospy.Publisher("~dialogflow_response", DialogResponse, queue_size=1) + + self._server.run() + + except ConnectionError as e: + rospy.logwarn("The error occurred while starting HTTPS server") + rospy.logerr(e) elif recieving_chat_mode == "none": pass @@ -121,7 +126,7 @@ def event_cb(self, event): See https://developers.google.com/chat/api/guides/message-formats/events#event_fields for details. :rtype: None """ - import json; rospy.loginfo(json.dumps(event, indent=2)) + rospy.loginfo(json.dumps(event, indent=2)) # TODO delete # GET EVENT TYPE # event/eventTime event_time = event.get('eventTime') @@ -196,6 +201,19 @@ def event_cb(self, event): def dialogflow_cb(self, dialogflow_json): original_request = dialogflow_json.get('originalDetectIntentRequest') + query_result = dialogflow_json.get('queryResult') + # make ROS Dialogflow message + msg = DialogResponse() + msg.header.stamp = rospy.Time.now() + msg.query = query_result.get('queryText', '') + msg.action = query_result.get('action', '') + msg.response = query_result.get('fulfillmentText', '') + msg.fulfilled = True if query_result.get('allRequiredParamsPresent') == 'True' else False + msg.parameters = json.dumps(query_result['parameters']) + msg.speech_score = 1.0 + msg.intent_score = query_result.get('intentDetectionConfidence') + self._dialogflow_pub.publish(msg) + if original_request.get('source') == "hangouts": data = original_request.get('payload').get('data') self.event_cb(data.get('event')) diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 349df81d4..7e499b2ef 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -58,7 +58,7 @@ def __handler(self, *args): try: GoogleChatHTTPSHandler(self._callback, *args) except socket.error as e: - if e.errno == 104: # SSL Connection reset error which can be ignored + if e.errno == 104: # ignore SSL Connection reset error rospy.logdebug(e) else: raise e From 3befcdee4b37ac600cd5b716262acac86e80edc4 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 13 Jan 2022 23:05:16 +0900 Subject: [PATCH 08/56] [google_chat_ros]remove unnecessary response --- google_chat_ros/src/google_chat_ros/google_chat.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 7e499b2ef..3395a8557 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -95,10 +95,7 @@ def _parse_json(self): def _response(self): self.send_response(200) - # self.send_header('Content-type', 'application/json; charset=utf-8') - # self.send_header('Content-length', len(self.res_body.encode())) self.end_headers() - # self.wfile.write(self.res_body.encode('utf-8')) def _bad_request(self): self.send_response(400) From 3843073883940c74bb9ce2bb685df23b26787cbe Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 14 Jan 2022 14:15:50 +0900 Subject: [PATCH 09/56] [google_chat_ros]add msg for card event and fix function style Add and fix message type for supporting card type support card type, TODO: uploader --- google_chat_ros/CMakeLists.txt | 10 +- google_chat_ros/action/SendMessage.action | 33 +-- google_chat_ros/msg/ActionParameter.msg | 2 + google_chat_ros/msg/Button.msg | 10 + google_chat_ros/msg/Card.msg | 4 + google_chat_ros/msg/CardAction.msg | 2 + google_chat_ros/msg/CardEvent.msg | 5 + google_chat_ros/msg/CardHeader.msg | 5 + google_chat_ros/msg/FormAction.msg | 3 + google_chat_ros/msg/Image.msg | 4 + google_chat_ros/msg/KeyValue.msg | 9 + google_chat_ros/msg/Message.msg | 16 +- google_chat_ros/msg/OnClick.msg | 3 + google_chat_ros/msg/Section.msg | 2 + google_chat_ros/msg/WidgetMarkup.msg | 4 + .../scripts/google_chat_ros_server.py | 267 ++++++++++++++---- .../src/google_chat_ros/google_chat.py | 7 +- 17 files changed, 277 insertions(+), 109 deletions(-) create mode 100644 google_chat_ros/msg/ActionParameter.msg create mode 100644 google_chat_ros/msg/Button.msg create mode 100644 google_chat_ros/msg/Card.msg create mode 100644 google_chat_ros/msg/CardAction.msg create mode 100644 google_chat_ros/msg/CardEvent.msg create mode 100644 google_chat_ros/msg/CardHeader.msg create mode 100644 google_chat_ros/msg/FormAction.msg create mode 100644 google_chat_ros/msg/Image.msg create mode 100644 google_chat_ros/msg/KeyValue.msg create mode 100644 google_chat_ros/msg/OnClick.msg create mode 100644 google_chat_ros/msg/Section.msg create mode 100644 google_chat_ros/msg/WidgetMarkup.msg diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 50233fa39..d595ad152 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -13,15 +13,7 @@ find_package( catkin_python_setup() add_message_files( - FILES - Annotation.msg - Attachment.msg - MessageEvent.msg - Message.msg - SlashCommand.msg - Space.msg - SpaceEvent.msg - User.msg + DIRECTORY msg ) add_action_files( diff --git a/google_chat_ros/action/SendMessage.action b/google_chat_ros/action/SendMessage.action index 4505fa57f..e980c1227 100644 --- a/google_chat_ros/action/SendMessage.action +++ b/google_chat_ros/action/SendMessage.action @@ -1,32 +1,9 @@ # Define the goal -string space # space name, identify the chat room -string message_type # supports "text" or "card". -string content # if message_type is text, content is just string type. if message_type is card, content is expetcted json style below. See https://developers.google.com/chat/api/guides/message-formats/cards for details. -##### RAW JSON TEXT EXAMPLE ##### - # "sections": [ - # { - # "widgets": [ - # { - # "image": { "imageUrl": "https://..." } - # }, - # { - # "buttons": [ - # { - # "textButton": { - # "text": "OPEN IN GOOGLE MAPS", - # "onClick": { - # "openLink": { - # "url": "https://..." - # } - # } - # } - # } - # ] - # } - # ] - # } - # ] -### END RAW JSON TEXT EXAMPLE ### +string text +google_chat_ros/Card[] cards +string thread_name +string space +google_chat_ros/Attachment[] attachments --- # Define the result bool done diff --git a/google_chat_ros/msg/ActionParameter.msg b/google_chat_ros/msg/ActionParameter.msg new file mode 100644 index 000000000..6f825d5f6 --- /dev/null +++ b/google_chat_ros/msg/ActionParameter.msg @@ -0,0 +1,2 @@ +string key +string value diff --git a/google_chat_ros/msg/Button.msg b/google_chat_ros/msg/Button.msg new file mode 100644 index 000000000..f83b9d200 --- /dev/null +++ b/google_chat_ros/msg/Button.msg @@ -0,0 +1,10 @@ +### FIELD DATA CAN BE ONLY ONE OF THE TEXT BUTTON OR IMAGE BUTTON ### +# A button with text and onclick action +string text_button_name +google_chat_ros/OnClick text_button_on_click +# An image button with an onclick action +string image_button_name +google_chat_ros/OnClick image_button_on_click +string icon # see https://developers.google.com/chat/api/reference/rest/v1/cards#icon +string original_icon_url # Original icon image's url +string original_icon_filepath # Original icon diff --git a/google_chat_ros/msg/Card.msg b/google_chat_ros/msg/Card.msg new file mode 100644 index 000000000..be826b8d1 --- /dev/null +++ b/google_chat_ros/msg/Card.msg @@ -0,0 +1,4 @@ +google_chat_ros/CardHeader header # The header of the card +google_chat_ros/Section[] sections # Sections are separated by a line divider +google_chat_ros/CardAction[] card_actions # The actions of this card +string name # Name of the card diff --git a/google_chat_ros/msg/CardAction.msg b/google_chat_ros/msg/CardAction.msg new file mode 100644 index 000000000..1b74d469d --- /dev/null +++ b/google_chat_ros/msg/CardAction.msg @@ -0,0 +1,2 @@ +string action_label # The label used to be displayed in the action menu item +google_chat_ros/OnClick on_click diff --git a/google_chat_ros/msg/CardEvent.msg b/google_chat_ros/msg/CardEvent.msg new file mode 100644 index 000000000..c3ff7821b --- /dev/null +++ b/google_chat_ros/msg/CardEvent.msg @@ -0,0 +1,5 @@ +string event_time +google_chat_ros/Space space +google_chat_ros/Message message # TODO:check is it required +google_chat_ros/User user +google_chat_ros/FormAction action diff --git a/google_chat_ros/msg/CardHeader.msg b/google_chat_ros/msg/CardHeader.msg new file mode 100644 index 000000000..4aa453bbb --- /dev/null +++ b/google_chat_ros/msg/CardHeader.msg @@ -0,0 +1,5 @@ +string title +string subtitle +bool image_style_circular # The image's type, makes border +string image_url +string image_filepath # The image in the card header diff --git a/google_chat_ros/msg/FormAction.msg b/google_chat_ros/msg/FormAction.msg new file mode 100644 index 000000000..01177d90e --- /dev/null +++ b/google_chat_ros/msg/FormAction.msg @@ -0,0 +1,3 @@ +# The form action data associated with an interactive card that was clicked. Only populated for CARD_CLICKED events. +string action_method_name +google_chat_ros/ActionParameter[] parameters diff --git a/google_chat_ros/msg/Image.msg b/google_chat_ros/msg/Image.msg new file mode 100644 index 000000000..62412143a --- /dev/null +++ b/google_chat_ros/msg/Image.msg @@ -0,0 +1,4 @@ +string image_url +string localpath # If you want to upload new image on google drive, please set this element, not image_uri. +google_chat_ros/OnClick on_click +float64 aspect_ratio # The aspect ratio of this image (width/height). If unset, the server fills it by prefetching the image diff --git a/google_chat_ros/msg/KeyValue.msg b/google_chat_ros/msg/KeyValue.msg new file mode 100644 index 000000000..9f1fa75d3 --- /dev/null +++ b/google_chat_ros/msg/KeyValue.msg @@ -0,0 +1,9 @@ +string top_label # The text of the top label +string content # The text of the content. Always required +bool content_multiline # If the content should be multiline +string bottom_label # The text of the bottom label +google_chat_ros/OnClick on_click +string icon # see https://developers.google.com/chat/api/reference/rest/v1/cards#icon +string original_icon_url # Original icon image's url +string original_icon_localpath # If you want to upload new image on google drive, please set this element, not original_icon_url. +google_chat_ros/Button button # A button that can be clicked to trigger an action diff --git a/google_chat_ros/msg/Message.msg b/google_chat_ros/msg/Message.msg index fa28dbcd0..37b5b9e55 100644 --- a/google_chat_ros/msg/Message.msg +++ b/google_chat_ros/msg/Message.msg @@ -1,8 +1,8 @@ -string name -google_chat_ros/User sender -string create_time -string text -string thread_name -google_chat_ros/Annotation[] annotations -string argument_text -google_chat_ros/Attachment[] attachments +string name # Resource name in form spaces/*/messages/* +google_chat_ros/User sender # The user who created the message +string create_time # The time at which the message was created in Google Chat server +string text # Plain-text body of the message +string thread_name # The thread the message belongs to +google_chat_ros/Annotation[] annotations # Annotations associated with the plain-text body of the message +string argument_text # Plain-text body of the message with all bot mentions stripped out +google_chat_ros/Attachment[] attachments # User uploaded attachment diff --git a/google_chat_ros/msg/OnClick.msg b/google_chat_ros/msg/OnClick.msg new file mode 100644 index 000000000..a13e01268 --- /dev/null +++ b/google_chat_ros/msg/OnClick.msg @@ -0,0 +1,3 @@ +### FIELD DATA CAN BE ONLY ONE OF THE FOLLOWING ### +google_chat_ros/FormAction action # A form action will be triggered by this onclick if specified +string open_link_url # A link that opens a new window diff --git a/google_chat_ros/msg/Section.msg b/google_chat_ros/msg/Section.msg new file mode 100644 index 000000000..102d26350 --- /dev/null +++ b/google_chat_ros/msg/Section.msg @@ -0,0 +1,2 @@ +string header # The header of the section +google_chat_ros/WidgetMarkup[] widgets diff --git a/google_chat_ros/msg/WidgetMarkup.msg b/google_chat_ros/msg/WidgetMarkup.msg new file mode 100644 index 000000000..4c8e48506 --- /dev/null +++ b/google_chat_ros/msg/WidgetMarkup.msg @@ -0,0 +1,4 @@ +google_chat_ros/Button[] buttons +string text_paragraph # Display a text paragraph in this widget +google_chat_ros/Image image # Display an image in this widget +google_chat_ros/KeyValue key_value # Display a key value item in this widget diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index ce18e50b6..5310d45d9 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -5,9 +5,6 @@ from google_chat_ros.google_chat import GoogleChatRESTClient from google_chat_ros.google_chat import GoogleChatHTTPSServer from google_chat_ros.msg import * -from google_chat_ros.msg import SendMessageAction -from google_chat_ros.msg import SendMessageFeedback -from google_chat_ros.msg import SendMessageResult import json import os import requests @@ -52,8 +49,9 @@ def __init__(self): self.download_avatar = rospy.get_param('~download_avatar') rospy.on_shutdown(self.killnode) # shutdown https server # ROS publisher - self._recieve_message_pub = rospy.Publisher("~recieve", MessageEvent, queue_size=1) + self._message_activity_pub = rospy.Publisher("~message_activity", MessageEvent, queue_size=1) self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) + self._card_activity_pub = rospy.Publisher("~card_activity", CardEvent, queue_size=1) rospy.loginfo("Starting Google Chat HTTPS server...") try: @@ -74,7 +72,7 @@ def __init__(self): rospy.logerr(e) elif recieving_chat_mode == "none": - pass + rospy.logwarn("You cannot recieve Google Chat event because HTTPS server is not running.") else: rospy.logerr("Please choose recieving_mode param from dialogflow, https, none.") @@ -83,31 +81,58 @@ def killnode(self): self._server.kill() def rest_cb(self, goal): + """Get ROS SendMessageAction Goal and send request to Google Chat API. + :param goal: ROS SendMessageAction Goal + :rtype: none + """ feedback = SendMessageFeedback() result = SendMessageResult() r = rospy.Rate(1) success = True - # start executing the action - space = goal.space - message_type = goal.message_type - content = goal.content + + json_body = {} + json_body['text'] = goal.text + json_body['thread'] = {'name': goal.thread_name} + json_body['cards'] = [] + + # Card + if goal.cards: + for card in goal.cards: + card_body = {} + # card/header + header = {} + header['title'] = card.header.title + header['subtitle'] = card.header.subtitle + header['imageStyle'] = 'AVATAR' if card.header.image_style_circular else 'IMAGE' + if card.header.image_url: + header['imageUrl'] = card.header.image_url + elif card.header.image_filepath: + header['imageUrl'] = self._upload_file(card.header.image_filepath) + # card/sections + sections = [] + sections = self._make_sections_json(card.sections) + # card/actions + card_actions = [] + for card_action_msg in card.card_actions: + card_action = {} + card_action['actionLabel'] = card_action_msg.action_label + card_action['onClick'] = self._make_on_click_json(card_action_msg.on_click) + card_actions.append(card_action) + card_body['header'] = header + card_body['sections'] = sections + card_body['cardActions'] = card_actions + card_body['name'] = card.name + json_body['cards'].append(card_body) + try: # establish the service self._client.build_service() - if message_type == 'text': - rospy.loginfo("Send text type message") - feedback.status = str( - self._client.send_text( - space=space, - text=content - )) - elif message_type == 'card': - rospy.loginfo("Send card type message") - feedback.status = str( - self._client.send_card( - space=space, - content=content - )) + rospy.loginfo("Send text type message") + feedback.status = str( + self._client.message_request( + space=goal.space, + json_body=json_body + )) except Exception as e: rospy.logerr(str(e)) feedback.status = str(e) @@ -126,7 +151,8 @@ def event_cb(self, event): See https://developers.google.com/chat/api/guides/message-formats/events#event_fields for details. :rtype: None """ - rospy.loginfo(json.dumps(event, indent=2)) # TODO delete + rospy.logdebug("GOOGLE CHAT ORIGINAL JSON EVENT") + rospy.logdebug(json.dumps(event, indent=2)) # GET EVENT TYPE # event/eventTime event_time = event.get('eventTime') @@ -155,44 +181,28 @@ def event_cb(self, event): msg.event_time = event_time msg.space = space msg.user = user - message = Message() - message_content = event.get('message', '') - message.name = message_content.get('name', '') - - # event/message/sender - message.sender = self._get_user_info(message_content.get('sender')) - message.create_time = message_content.get('createTime', '') - message.text = message_content.get('text', '') - message.thread_name = message_content.get('thread', {}).get('name', '') - - # event/messsage/annotations - if 'annotations' in message_content: - for item in message_content['annotations']: - annotation = Annotation() - annotation.length = int(item.get('length', 0)) - annotation.start_index = int(item.get('startIndex', 0)) - annotation.mention = True if item.get('type') == 'USER_MENTION' else False - if annotation.mention: - annotation.user = self._get_user_info(item.get('userMention').get('user')) - annotation.slash_command = True if item.get('type') == 'SLASH_COMMAND' else False - message.annotations.append(annotation) - message.argument_text = message_content.get('argumentText', '') - - # event/message/attachment - if 'attachment' in message_content: - for item in message_content['attachment']: - message.attachments.append(self._get_attachment(item)) - msg.message = message - - # rospy Publish - try: - self._recieve_message_pub.publish(msg) - except Exception as e: - from IPython.terminal import embed; ipshell=embed.InteractiveShellEmbed(config=embed.load_default_config())(local_ns=locals()) + msg.message = self._make_message_msg(event) + self._message_activity_pub.publish(msg) return elif event['type'] == 'CARD_CLICKED': - # TODO + msg = CardEvent() + msg.event_time = event_time + msg.space = space + msg.user = user + msg.message = self._make_message_msg(event) + if event.get('action'): + action = event.get('action') + msg.action.action_method_name = action.get('actionMethodName') + if action.get('actionMethodName', {}).get('parameters'): + parameters = [] + for param in action.get('actionMethodName').get('parameters'): + action_parameter = ActionParameter() + action_parameter.key = param.get('key') + action_parameter.value = param.get('value') + parameters.append(action_parameter) + msg.action.parameters = parameters + self._card_activity_pub.publish(msg) return else: @@ -218,6 +228,140 @@ def dialogflow_cb(self, dialogflow_json): data = original_request.get('payload').get('data') self.event_cb(data.get('event')) + def _make_message_msg(self, event): + message = Message() + message_content = event.get('message', '') + message.name = message_content.get('name', '') + + # event/message/sender + message.sender = self._get_user_info(message_content.get('sender')) + message.create_time = message_content.get('createTime', '') + message.text = message_content.get('text', '') + message.thread_name = message_content.get('thread', {}).get('name', '') + + # event/messsage/annotations + if 'annotations' in message_content: + for item in message_content['annotations']: + annotation = Annotation() + annotation.length = int(item.get('length', 0)) + annotation.start_index = int(item.get('startIndex', 0)) + annotation.mention = True if item.get('type') == 'USER_MENTION' else False + if annotation.mention: + annotation.user = self._get_user_info(item.get('userMention').get('user')) + annotation.slash_command = True if item.get('type') == 'SLASH_COMMAND' else False + message.annotations.append(annotation) + message.argument_text = message_content.get('argumentText', '') + + # event/message/attachment + if 'attachment' in message_content: + for item in message_content['attachment']: + message.attachments.append(self._get_attachment(item)) + + return message + + def _make_sections_json(self, sections_msg): + """ + :type msg: list of google_chat_ros.msgs/Section + :rtype json_body: list of json + """ + json_body = [] + for msg in sections_msg: + section = {} + section['header'] = msg.header + section['widgets'] = self._make_widget_markups_json(msg.widgets) + json_body.append(section) + return json_body + + def _make_widget_markups_json(self, widgets_msg): + """Make widget markup json lists. + See https://developers.google.com/chat/api/reference/rest/v1/cards#widgetmarkup for details. + :rtype widgets_msg: list of google_chat_ros.msgs/WidgetMarkup + :rtype json_body: list of json + """ + json_body = [] + for msg in widgets_msg: + msg = WidgetMarkup() # TODO DEBUG + is_text = bool(msg.text_paragraph) + is_image = bool(msg.image.image_uri) or bool(msg.image.localpath) + is_keyval = bool(msg.key_value.content) + if (is_text & is_image) | (is_image & is_keyval) | (is_keyval & is_text): + rospy.logerr("Error happened when making widgetMarkup json. Please fill in one of the text_paragraph, image, key_value. Do not fill in more than two at the same time.") + elif is_text: + json_body.append({'textParagraph':msg.text_paragraph}) + elif is_image: + image_json = {} + if msg.image.image_uri: + image_json['imageUrl'] = msg.image.image_uri + elif msg.image.localpath: + image_json['imageUrl'] = self._upload_file(msg.image.localpath) + image_json['onClick'] = self._make_on_click_json(msg.image.on_click) + image_json['aspectRatio'] = msg.image.aspect_ratio + json_body.append(image_json) + elif is_keyval: + keyval_json = {} + keyval_json['topLabel'] = msg.key_value.top_label + keyval_json['content'] = msg.key_value.content + keyval_json['contentMultiline'] = msg.key_value.content_multiline + keyval_json['bottomLabel'] = msg.key_value.bottom_label + keyval_json['onClick'] = self._make_on_click_json(msg.key_value.on_click) + if msg.key_value.icon: + keyval_json['icon'] = msg.key_value.icon + elif msg.key_value.original_icon_url: + keyval_json['iconUrl'] = msg.key_value.original_icon_url + elif msg.key_value.original_icon_localpath: + keyval_json['iconUrl'] = self._upload_file(msg.key_value.original_icon_localpath) + keyval_json['button'] = self._make_button_json(msg.key_value.button) + json_body.append(keyval_json) + return json_body + + def _make_on_click_json(self, on_click_msg): + """Make onClick json. + See https://developers.google.com/chat/api/reference/rest/v1/cards#onclick for details. + :rtype on_click_msg: google_chat_ros.msg/OnClick.msg + :rtype json_body: json + """ + json_body = {} + if on_click_msg.action.action_method_name and on_click_msg.open_link_url: + rospy.logerr("Error happened when making onClick json. Please fill in one of the action, open_link_url. Do not fill in more than two at the same time.") + elif on_click_msg.action.action_method_name: + action = {} + action['actionMethodName'] = on_click_msg.action.action_method_name + parameters = [] + for parameter in on_click_msg.action.parameters: + parameters.append({parameter['key']: parameter['value']}) + action['parameters'] = parameters + json_body['action'] = action + elif on_click_msg.open_link_url: + json_body['openLink'] = on_click_msg.open_link_url + return json_body + + def _make_button_json(self, button_msg): + """Make button json. + See https://developers.google.com/chat/api/reference/rest/v1/cards#button for details. + :rtype button_msg: google_chat_ros.msg/Button.msg + :rtype json_body: json + """ + json_body = {} + if button_msg.text_button_name and button_msg.image_button_name: + rospy.logerr("Error happened when making Button json. Please fill in one of the text_button_name or image_button_name. Do not fill in more than two at the same time.") + elif button_msg.text_button_name: + rospy.loginfo("Build text button:{}".format(button_msg.text_button_name)) + text_button = {} + text_button['text'] = button_msg.text_button_name + text_button['onClick'] = self._make_on_click_json(button_msg.text_button_on_click) + json_body['textButton'] = text_button + elif button_msg.image_button_name: + rospy.loginfo("Build image button:{}".format(button_msg.image_button_name)) + image_button = {} + image_button['onClick'] = self._make_on_click_json(button_msg.image_button_on_click) + if button_msg.icon: + image_icon['icon'] = button_msg.icon + elif button_msg.original_icon_url: + image_icon['iconUrl'] = button_msg.original_icon_url + elif button_msg.original_icon_filepath: + image_icon['iconUrl'] = self._upload_file(button_msg.original_icon_filepath) + return json_body + def _get_user_info(self, item): user = User() user.name = item.get('name', '') @@ -230,6 +374,11 @@ def _get_user_info(self, item): user.human = True if item.get('type') == "HUMAN" else False return user + def _upload_file(self, filepath): + # TODO + url = None + return url + def _get_attachment(self, item): attachment = Attachment() attachment.name = item.get('name', '') diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 3395a8557..152a8834a 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -24,13 +24,10 @@ def build_service(self): self.__credentials = ServiceAccountCredentials.from_json_keyfile_name(self.keyfile, self._auth_scopes) self._chat = build('chat', 'v1', http=self.__credentials.authorize(Http())) - def send_text(self, space, text): + def message_request(self, space, json_body): parent = 'spaces/' + space # returns same 403 error both authenticate error and not connected error - return self._chat.spaces().messages().create(parent=parent, body={'text': text}).execute() - - def send_card(self, space, content): - pass + return self._chat.spaces().messages().create(parent=parent, body=json_body).execute() def list_members(self, space): """Show member list in the space. From 430a70eb03421d64490de0a29a1d69c0a7f5b3ef Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 18 Jan 2022 15:18:14 +0900 Subject: [PATCH 10/56] [google_chat_ros]add gdrive_ros service client --- google_chat_ros/package.xml | 1 + .../scripts/google_chat_ros_server.py | 38 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index 011c04f3e..c7ab0f8f5 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -26,6 +26,7 @@ sensor_msgs std_msgs dialogflow_task_executive + gdrive_ros diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 5310d45d9..4022cab5e 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -1,15 +1,18 @@ #!/usr/bin/env python -import actionlib -from dialogflow_task_executive.msg import DialogResponse import gdown -from google_chat_ros.google_chat import GoogleChatRESTClient -from google_chat_ros.google_chat import GoogleChatHTTPSServer -from google_chat_ros.msg import * import json import os import requests from requests.exceptions import Timeout from requests.exceptions import ConnectionError + +# ROS libraries +import actionlib +from dialogflow_task_executive.msg import DialogResponse +from gdrive_ros.srv import * +from google_chat_ros.google_chat import GoogleChatRESTClient +from google_chat_ros.google_chat import GoogleChatHTTPSServer +from google_chat_ros.msg import * import rospy @@ -280,7 +283,6 @@ def _make_widget_markups_json(self, widgets_msg): """ json_body = [] for msg in widgets_msg: - msg = WidgetMarkup() # TODO DEBUG is_text = bool(msg.text_paragraph) is_image = bool(msg.image.image_uri) or bool(msg.image.localpath) is_keyval = bool(msg.key_value.content) @@ -375,9 +377,27 @@ def _get_user_info(self, item): return user def _upload_file(self, filepath): - # TODO - url = None - return url + """Get local filepath and upload to Google Drive + :param filepath: local file's path you want to upload + :type filepath: string + :returns: URL file exists + :rtype: string + """ + # ROS service client + try: + rospy.wait_for_service("~upload", timeout=5.0) + gdrive_upload = rospy.ServiceProxy("~upload", Upload) + except rospy.ROSException as e: + rospy.logerr("No Google Drive ROS upload service was found. Please check gdrive_ros is correctly launched and service name is correct.") + return + # upload + try: + res = gdrive_upload(filepath) + except rospy.ServiceException as e: + rospy.logerr("Failed to call Google Drive upload service, status:{}".format(str(e))) + else: + url = res.file_url + return url def _get_attachment(self, item): attachment = Attachment() From c30a371f363308a9678ea9779d7d4c0645bbf21d Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 19 Jan 2022 13:45:45 +0900 Subject: [PATCH 11/56] [google_chat_ros]remove dialogflow https server. instead, subscribe OriginalDetectIntentRequest.msg --- .../scripts/google_chat_ros_server.py | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 4022cab5e..7972373bc 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -9,6 +9,7 @@ # ROS libraries import actionlib from dialogflow_task_executive.msg import DialogResponse +from dialogflow_webhook_ros.msg import OriginalDetectIntentRequest from gdrive_ros.srv import * from google_chat_ros.google_chat import GoogleChatRESTClient from google_chat_ros.google_chat import GoogleChatHTTPSServer @@ -43,14 +44,9 @@ def __init__(self): # For POST, recieving message if recieving_chat_mode in ("url", "dialogflow"): # rosparams - self.host = rospy.get_param('~host') - self.port = int(rospy.get_param('~port')) - self.ssl_certfile = rospy.get_param('~ssl_certfile') - self.ssl_keyfile = rospy.get_param('~ssl_keyfile') self.download_data = rospy.get_param('~download_data') self.download_directory = rospy.get_param('~download_directory') self.download_avatar = rospy.get_param('~download_avatar') - rospy.on_shutdown(self.killnode) # shutdown https server # ROS publisher self._message_activity_pub = rospy.Publisher("~message_activity", MessageEvent, queue_size=1) self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) @@ -60,15 +56,17 @@ def __init__(self): try: if recieving_chat_mode == "url": rospy.loginfo("Expected to get Google Chat Bot URL request") + self.host = rospy.get_param('~host') + self.port = int(rospy.get_param('~port')) + self.ssl_certfile = rospy.get_param('~ssl_certfile') + self.ssl_keyfile = rospy.get_param('~ssl_keyfile') self._server = GoogleChatHTTPSServer( self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb) + rospy.on_shutdown(self.killhttpd) # shutdown https server + self._server.run() elif recieving_chat_mode == "dialogflow": rospy.loginfo("Expected to get Google Chat Dialogflow request") - self._server = GoogleChatHTTPSServer( - self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.dialogflow_cb) - self._dialogflow_pub = rospy.Publisher("~dialogflow_response", DialogResponse, queue_size=1) - - self._server.run() + self._sub = rospy.Subscriber("~original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) except ConnectionError as e: rospy.logwarn("The error occurred while starting HTTPS server") @@ -80,7 +78,7 @@ def __init__(self): else: rospy.logerr("Please choose recieving_mode param from dialogflow, https, none.") - def killnode(self): + def killhttpd(self): self._server.kill() def rest_cb(self, goal): @@ -212,24 +210,10 @@ def event_cb(self, event): rospy.logerr("Got unknown event type.") return - def dialogflow_cb(self, dialogflow_json): - original_request = dialogflow_json.get('originalDetectIntentRequest') - query_result = dialogflow_json.get('queryResult') - # make ROS Dialogflow message - msg = DialogResponse() - msg.header.stamp = rospy.Time.now() - msg.query = query_result.get('queryText', '') - msg.action = query_result.get('action', '') - msg.response = query_result.get('fulfillmentText', '') - msg.fulfilled = True if query_result.get('allRequiredParamsPresent') == 'True' else False - msg.parameters = json.dumps(query_result['parameters']) - msg.speech_score = 1.0 - msg.intent_score = query_result.get('intentDetectionConfidence') - self._dialogflow_pub.publish(msg) - - if original_request.get('source') == "hangouts": - data = original_request.get('payload').get('data') - self.event_cb(data.get('event')) + def dialogflow_cb(self, msg): + if msg.source == "hangouts": + json_data = json.loads(msg.payload) + self.event_cb(json_data.get('data', {}).event('event', {})) def _make_message_msg(self, event): message = Message() From 8cd11dd6d418880068e6343d948bb29e34343967 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 19 Jan 2022 20:32:56 +0900 Subject: [PATCH 12/56] [google_chat_ros]fix launch file for loading param and convert str -> dic problem --- google_chat_ros/launch/google_chat.launch | 6 ++++-- google_chat_ros/scripts/google_chat_ros_server.py | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 064276bcd..a356c70b8 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -4,18 +4,20 @@ - + + - recieving_mode: $(arg recieving_mode) download_data_timeout: $(arg download_data_timeout) download_data: $(arg download_data) download_directory: $(arg download_directory) download_avatar: $(arg download_avatar) + google_cloud_credentials_json: $(arg google_cloud_credentials_json) + diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 7972373bc..b9054994f 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -8,6 +8,7 @@ # ROS libraries import actionlib +import ast from dialogflow_task_executive.msg import DialogResponse from dialogflow_webhook_ros.msg import OriginalDetectIntentRequest from gdrive_ros.srv import * @@ -65,8 +66,8 @@ def __init__(self): rospy.on_shutdown(self.killhttpd) # shutdown https server self._server.run() elif recieving_chat_mode == "dialogflow": - rospy.loginfo("Expected to get Google Chat Dialogflow request") - self._sub = rospy.Subscriber("~original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) + rospy.loginfo("Expected to get OriginalDetectIntentRequest.msg from dialogflow webhook ros node.") + self._sub = rospy.Subscriber("dialogflow_original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) except ConnectionError as e: rospy.logwarn("The error occurred while starting HTTPS server") @@ -212,8 +213,10 @@ def event_cb(self, event): def dialogflow_cb(self, msg): if msg.source == "hangouts": - json_data = json.loads(msg.payload) - self.event_cb(json_data.get('data', {}).event('event', {})) + ast_data = ast.literal_eval(msg.payload) + json_dumped = json.dumps(ast_data) + json_data = json.loads(json_dumped) + self.event_cb(json_data.get('data', {}).get('event', {})) def _make_message_msg(self, event): message = Message() From 1e6f6492eda8d99e6e5bb1e1dd36e9e2223cf4eb Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 20 Jan 2022 15:28:25 +0900 Subject: [PATCH 13/56] [google_chat_ros]add dependency --- google_chat_ros/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index c7ab0f8f5..4af341528 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -26,6 +26,7 @@ sensor_msgs std_msgs dialogflow_task_executive + dialogflow_webhook_ros gdrive_ros From 59a452eeeac7dbaae50a68687ae81b0b7399a2ea Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 20 Jan 2022 16:11:53 +0900 Subject: [PATCH 14/56] [google_chat_ros]support upload attachments to gdrive fix upload service fix name bug, remove upload parent dir --- google_chat_ros/action/SendMessage.action | 1 - google_chat_ros/launch/google_chat.launch | 4 ++ .../scripts/google_chat_ros_server.py | 44 ++++++++++++------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/google_chat_ros/action/SendMessage.action b/google_chat_ros/action/SendMessage.action index e980c1227..baa0ccdca 100644 --- a/google_chat_ros/action/SendMessage.action +++ b/google_chat_ros/action/SendMessage.action @@ -3,7 +3,6 @@ string text google_chat_ros/Card[] cards string thread_name string space -google_chat_ros/Attachment[] attachments --- # Define the result bool done diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index a356c70b8..e531a1ac6 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -1,5 +1,7 @@ + + @@ -12,6 +14,8 @@ respawn="$(arg respawn)" output="screen"> recieving_mode: $(arg recieving_mode) + gdrive_upload_service: $(arg gdrive_upload_service) + upload_data_timeout: $(arg upload_data_timeout) download_data_timeout: $(arg download_data_timeout) download_data: $(arg download_data) download_directory: $(arg download_directory) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index b9054994f..0aba31818 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -24,7 +24,7 @@ class GoogleChatROS(object): """ def __init__(self): recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'dialogflow', 'url', 'none' - + self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service') # For REST, sending message rest_keyfile = rospy.get_param('~google_cloud_credentials_json') self._client = GoogleChatRESTClient(rest_keyfile) @@ -45,6 +45,8 @@ def __init__(self): # For POST, recieving message if recieving_chat_mode in ("url", "dialogflow"): # rosparams + self.upload_data_timeout = rospy.get_param('~upload_data_timeout') + self.upload_data_parents_path = rospy.get_param('~upload_data_parents_path') self.download_data = rospy.get_param('~download_data') self.download_directory = rospy.get_param('~download_directory') self.download_avatar = rospy.get_param('~download_avatar') @@ -53,10 +55,10 @@ def __init__(self): self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) self._card_activity_pub = rospy.Publisher("~card_activity", CardEvent, queue_size=1) - rospy.loginfo("Starting Google Chat HTTPS server...") try: if recieving_chat_mode == "url": rospy.loginfo("Expected to get Google Chat Bot URL request") + rospy.loginfo("Starting Google Chat HTTPS server...") self.host = rospy.get_param('~host') self.port = int(rospy.get_param('~port')) self.ssl_certfile = rospy.get_param('~ssl_certfile') @@ -95,9 +97,9 @@ def rest_cb(self, goal): json_body = {} json_body['text'] = goal.text json_body['thread'] = {'name': goal.thread_name} - json_body['cards'] = [] # Card + json_body['cards'] = [] if goal.cards: for card in goal.cards: card_body = {} @@ -128,8 +130,10 @@ def rest_cb(self, goal): try: # establish the service + # TODO debug + rospy.loginfo("Send json") + rospy.loginfo(str(json_body)) self._client.build_service() - rospy.loginfo("Send text type message") feedback.status = str( self._client.message_request( space=goal.space, @@ -271,7 +275,7 @@ def _make_widget_markups_json(self, widgets_msg): json_body = [] for msg in widgets_msg: is_text = bool(msg.text_paragraph) - is_image = bool(msg.image.image_uri) or bool(msg.image.localpath) + is_image = bool(msg.image.image_url) or bool(msg.image.localpath) is_keyval = bool(msg.key_value.content) if (is_text & is_image) | (is_image & is_keyval) | (is_keyval & is_text): rospy.logerr("Error happened when making widgetMarkup json. Please fill in one of the text_paragraph, image, key_value. Do not fill in more than two at the same time.") @@ -279,13 +283,14 @@ def _make_widget_markups_json(self, widgets_msg): json_body.append({'textParagraph':msg.text_paragraph}) elif is_image: image_json = {} - if msg.image.image_uri: - image_json['imageUrl'] = msg.image.image_uri + if msg.image.image_url: + image_json['imageUrl'] = msg.image.image_url elif msg.image.localpath: image_json['imageUrl'] = self._upload_file(msg.image.localpath) image_json['onClick'] = self._make_on_click_json(msg.image.on_click) - image_json['aspectRatio'] = msg.image.aspect_ratio - json_body.append(image_json) + if msg.image.aspect_ratio: + image_json['aspectRatio'] = msg.image.aspect_ratio + json_body.append({'image':image_json}) elif is_keyval: keyval_json = {} keyval_json['topLabel'] = msg.key_value.top_label @@ -300,7 +305,7 @@ def _make_widget_markups_json(self, widgets_msg): elif msg.key_value.original_icon_localpath: keyval_json['iconUrl'] = self._upload_file(msg.key_value.original_icon_localpath) keyval_json['button'] = self._make_button_json(msg.key_value.button) - json_body.append(keyval_json) + json_body.append({'KeyValue':keyval_json}) return json_body def _make_on_click_json(self, on_click_msg): @@ -363,7 +368,7 @@ def _get_user_info(self, item): user.human = True if item.get('type') == "HUMAN" else False return user - def _upload_file(self, filepath): + def _upload_file(self, filepath, return_id=False): """Get local filepath and upload to Google Drive :param filepath: local file's path you want to upload :type filepath: string @@ -372,19 +377,26 @@ def _upload_file(self, filepath): """ # ROS service client try: - rospy.wait_for_service("~upload", timeout=5.0) - gdrive_upload = rospy.ServiceProxy("~upload", Upload) + rospy.wait_for_service(self.gdrive_ros_srv, timeout=self.upload_data_timeout) + gdrive_upload = rospy.ServiceProxy(self.gdrive_ros_srv, Upload) except rospy.ROSException as e: rospy.logerr("No Google Drive ROS upload service was found. Please check gdrive_ros is correctly launched and service name is correct.") + rospy.logerr(e) return # upload try: - res = gdrive_upload(filepath) + res = gdrive_upload(file_path=filepath) except rospy.ServiceException as e: rospy.logerr("Failed to call Google Drive upload service, status:{}".format(str(e))) else: - url = res.file_url - return url + if return_id: + drive_id = res.file_id + rospy.loginfo("Google drive ID:{}".format(drive_id)) + return drive_id + else: + url = res.file_url + rospy.loginfo("Google drive URL:{}".format(url)) + return url def _get_attachment(self, item): attachment = Attachment() From b4a68fbcdc1b7836c1d42bc27607205e1b04dc09 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 21 Jan 2022 21:14:47 +0900 Subject: [PATCH 15/56] [google_chat_ros]fix google chat cards not shown correctly bugs fix botton key value json bug fix value error when get card activity and parse key value --- .../scripts/google_chat_ros_server.py | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index 0aba31818..ab50a8807 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -46,7 +46,6 @@ def __init__(self): if recieving_chat_mode in ("url", "dialogflow"): # rosparams self.upload_data_timeout = rospy.get_param('~upload_data_timeout') - self.upload_data_parents_path = rospy.get_param('~upload_data_parents_path') self.download_data = rospy.get_param('~download_data') self.download_directory = rospy.get_param('~download_directory') self.download_avatar = rospy.get_param('~download_avatar') @@ -104,14 +103,16 @@ def rest_cb(self, goal): for card in goal.cards: card_body = {} # card/header - header = {} - header['title'] = card.header.title - header['subtitle'] = card.header.subtitle - header['imageStyle'] = 'AVATAR' if card.header.image_style_circular else 'IMAGE' - if card.header.image_url: - header['imageUrl'] = card.header.image_url - elif card.header.image_filepath: - header['imageUrl'] = self._upload_file(card.header.image_filepath) + if card.header: + header = {} + header['title'] = card.header.title + header['subtitle'] = card.header.subtitle + header['imageStyle'] = 'AVATAR' if card.header.image_style_circular else 'IMAGE' + card_body['header'] = header + if card.header.image_url: + header['imageUrl'] = card.header.image_url + elif card.header.image_filepath: + header['imageUrl'] = self._upload_file(card.header.image_filepath) # card/sections sections = [] sections = self._make_sections_json(card.sections) @@ -122,9 +123,9 @@ def rest_cb(self, goal): card_action['actionLabel'] = card_action_msg.action_label card_action['onClick'] = self._make_on_click_json(card_action_msg.on_click) card_actions.append(card_action) - card_body['header'] = header card_body['sections'] = sections - card_body['cardActions'] = card_actions + if card_actions: + card_body['cardActions'] = card_actions card_body['name'] = card.name json_body['cards'].append(card_body) @@ -200,9 +201,9 @@ def event_cb(self, event): if event.get('action'): action = event.get('action') msg.action.action_method_name = action.get('actionMethodName') - if action.get('actionMethodName', {}).get('parameters'): + if action.get('parameters'): parameters = [] - for param in action.get('actionMethodName').get('parameters'): + for param in action.get('parameters'): action_parameter = ActionParameter() action_parameter.key = param.get('key') action_parameter.value = param.get('value') @@ -261,7 +262,8 @@ def _make_sections_json(self, sections_msg): json_body = [] for msg in sections_msg: section = {} - section['header'] = msg.header + if msg.header: + section['header'] = msg.header section['widgets'] = self._make_widget_markups_json(msg.widgets) json_body.append(section) return json_body @@ -277,17 +279,26 @@ def _make_widget_markups_json(self, widgets_msg): is_text = bool(msg.text_paragraph) is_image = bool(msg.image.image_url) or bool(msg.image.localpath) is_keyval = bool(msg.key_value.content) + # make buttons + buttons = [] + buttons_msg = msg.buttons + for button_msg in buttons_msg: + buttons.append(self._make_button_json(button_msg)) + if buttons: + json_body.append({'buttons':buttons}) + if (is_text & is_image) | (is_image & is_keyval) | (is_keyval & is_text): rospy.logerr("Error happened when making widgetMarkup json. Please fill in one of the text_paragraph, image, key_value. Do not fill in more than two at the same time.") elif is_text: - json_body.append({'textParagraph':msg.text_paragraph}) + json_body.append({'textParagraph':{'text':msg.text_paragraph}}) elif is_image: image_json = {} if msg.image.image_url: image_json['imageUrl'] = msg.image.image_url elif msg.image.localpath: image_json['imageUrl'] = self._upload_file(msg.image.localpath) - image_json['onClick'] = self._make_on_click_json(msg.image.on_click) + if msg.image.on_click.action.action_method_name or msg.image.on_click.open_link_url: + image_json['onClick'] = self._make_on_click_json(msg.image.on_click) if msg.image.aspect_ratio: image_json['aspectRatio'] = msg.image.aspect_ratio json_body.append({'image':image_json}) @@ -297,15 +308,17 @@ def _make_widget_markups_json(self, widgets_msg): keyval_json['content'] = msg.key_value.content keyval_json['contentMultiline'] = msg.key_value.content_multiline keyval_json['bottomLabel'] = msg.key_value.bottom_label - keyval_json['onClick'] = self._make_on_click_json(msg.key_value.on_click) + if msg.key_value.on_click.action.action_method_name or msg.key_value.on_click.open_link_url: + keyval_json['onClick'] = self._make_on_click_json(msg.key_value.on_click) if msg.key_value.icon: keyval_json['icon'] = msg.key_value.icon elif msg.key_value.original_icon_url: keyval_json['iconUrl'] = msg.key_value.original_icon_url elif msg.key_value.original_icon_localpath: keyval_json['iconUrl'] = self._upload_file(msg.key_value.original_icon_localpath) - keyval_json['button'] = self._make_button_json(msg.key_value.button) - json_body.append({'KeyValue':keyval_json}) + if msg.key_value.button.text_button_name or msg.key_value.button.image_button_name: + keyval_json['button'] = self._make_button_json(msg.key_value.button) + json_body.append({'keyValue':keyval_json}) return json_body def _make_on_click_json(self, on_click_msg): @@ -322,11 +335,11 @@ def _make_on_click_json(self, on_click_msg): action['actionMethodName'] = on_click_msg.action.action_method_name parameters = [] for parameter in on_click_msg.action.parameters: - parameters.append({parameter['key']: parameter['value']}) + parameters.append({'key':parameter.key, 'value':parameter.value}) action['parameters'] = parameters json_body['action'] = action elif on_click_msg.open_link_url: - json_body['openLink'] = on_click_msg.open_link_url + json_body['openLink'] = {'url': on_click_msg.open_link_url} return json_body def _make_button_json(self, button_msg): From 3ce7ea8d5fbf0ea45e54be7f17a295bdc5efc1e5 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 21 Jan 2022 23:11:11 +0900 Subject: [PATCH 16/56] [google_chat_ros]add use_yaml arg --- google_chat_ros/launch/google_chat.launch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index e531a1ac6..1b5b4e577 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -7,7 +7,8 @@ - + + - + From de4ae74002b7e426c2213db2c82faab5e6e2925f Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sun, 20 Feb 2022 18:50:54 +0900 Subject: [PATCH 17/56] [google_chat_ros] add helper node of google chat ros, especially dialogflow, soundplay [google_chat_ros] chmod +x helper.py --- google_chat_ros/launch/google_chat.launch | 13 ++++ google_chat_ros/scripts/helper.py | 77 +++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100755 google_chat_ros/scripts/helper.py diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 1b5b4e577..c73272e4d 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -11,6 +11,11 @@ + + + + + @@ -25,4 +30,12 @@ + + + + to_dialogflow_task_executive: $(arg to_dialogflow_task_executive) + sound_play_jp: $(arg sound_play_jp) + + diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py new file mode 100755 index 000000000..6945b9fb9 --- /dev/null +++ b/google_chat_ros/scripts/helper.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +import queue + +import rospy +import actionlib + +from std_msgs.msg import String +from google_chat_ros.msg import * +from dialogflow_task_executive.msg import * +from sound_play.msg import * + +class GoogleChatROSHelper(object): + """ + Helper node for google chat ROS + """ + def __init__(self): + # Get configuration params + self.to_dialogflow_task_executive = rospy.get_param("~to_dialogflow_task_executive", False) + self.sound_play_jp = rospy.get_param("~sound_play_jp", False) + self._message_sub = rospy.Subscriber("~message_activity", MessageEvent, callback=self._message_cb) + self.recent_message_event = None + + # GOOGLE CHAT + def send_chat_client(self, goal): + client = actionlib.SimpleActionClient('google_chat_ros_client', SendMessageAction) + client.wait_for_server() + client.send_goal(goal) + client.wait_for_result() + return client.get_result() + + def dialogflow_task_exec_client(self, query): + """ + :rtype: TextResult + """ + client = actionlib.SimpleActionClient('dialogflow_task_executive_client', TextAction) + client.wait_for_server() + goal = TextGoal() + goal.query = query + client.send_goal(query) + client.wait_for_result() + return client.get_result() + + # SOUND + def sound_client(self, goal): + client = actionlib.SimpleActionClient('robotsound_jp', SoundRequestAction) + client.wait_for_server() + client.send_goal(goal) + client.wait_for_result() + return client.get_result() + + def _message_cb(self, data): + """ + Callback function for subscribing MessageEvent.msg + """ + sender_id = data.message.sender.name + sender_name = data.message.sender.display_name + thread_name = data.message.thread_name + text = data.message.argument_text + if self.to_dialogflow_task_executive: + chat_goal = SendMessageGoal() + chat_goal.thread_name = thread_name + dialogflow_res = self.dialogflow_task_exec_client(text) + content = " {}".format(sender_id, dialogflow_res.response.response) + chat_goal.text = content + send_chat_client(chat_goal) + if self.sound_play_jp: + sound_goal = SoundRequestGoal() + sound_goal.sound_request.sound = sound_goal.sound_request.SAY + sound_goal.sound_request.command = sound_goal.sound_request.PLAY_ONCE + sound_goal.sound_request.volume = 1.0 + sound_goal.sound_request.arg = "{}さんから,{}というメッセージを受信しました".format(sender_name, text) + sound_client(sound_goal) + +if __name__ == '__main__': + rospy.init_node('google_chat_helper') + node = GoogleChatROSHelper() + rospy.spin() From 951b25af094e8c17a9118922e8413c4308848faf Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sun, 20 Feb 2022 19:12:43 +0900 Subject: [PATCH 18/56] [google_chat_ros] add coding --- google_chat_ros/scripts/helper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index 6945b9fb9..dbc144f4e 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- import queue import rospy From 078695871370b85f5acc43def786c175713c4d1b Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sun, 20 Feb 2022 20:12:27 +0900 Subject: [PATCH 19/56] [google_chat_ros] set user_agent --- .../scripts/google_chat_ros_server.py | 14 +++++++------- google_chat_ros/scripts/helper.py | 16 ++++++++-------- .../src/google_chat_ros/google_chat.py | 17 ++++++++++------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index ab50a8807..e504013e5 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -63,7 +63,7 @@ def __init__(self): self.ssl_certfile = rospy.get_param('~ssl_certfile') self.ssl_keyfile = rospy.get_param('~ssl_keyfile') self._server = GoogleChatHTTPSServer( - self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb) + self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb, user_agent='Google-Dynamite') rospy.on_shutdown(self.killhttpd) # shutdown https server self._server.run() elif recieving_chat_mode == "dialogflow": @@ -162,16 +162,16 @@ def event_cb(self, event): rospy.logdebug(json.dumps(event, indent=2)) # GET EVENT TYPE # event/eventTime - event_time = event.get('eventTime') + event_time = event.get('eventTime', '') # event/space space = Space() - space.name = event.get('space').get('name') - space.room = True if event.get('space').get('type') == "ROOM" else False + space.name = event.get('space', {}).get('name', '') + space.room = True if event.get('space', {}).get('type', '') == "ROOM" else False if space.room: - space.display_name = event.get('space').get('displayName') - space.dm = True if event.get('space').get('type') == "DM" else False + space.display_name = event.get('space', {}).get('displayName', '') + space.dm = True if event.get('space', {}).get('type', '') == "DM" else False # event/user - user = self._get_user_info(event.get('user')) + user = self._get_user_info(event.get('user', {})) if event['type'] == 'ADDED_TO_SPACE' or event['type'] == 'REMOVED_FROM_SPACE': msg = SpaceEvent() diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index dbc144f4e..591e87f4b 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -16,14 +16,14 @@ class GoogleChatROSHelper(object): """ def __init__(self): # Get configuration params - self.to_dialogflow_task_executive = rospy.get_param("~to_dialogflow_task_executive", False) - self.sound_play_jp = rospy.get_param("~sound_play_jp", False) - self._message_sub = rospy.Subscriber("~message_activity", MessageEvent, callback=self._message_cb) + self.to_dialogflow_task_executive = rospy.get_param("~to_dialogflow_task_executive") + self.sound_play_jp = rospy.get_param("~sound_play_jp") + self._message_sub = rospy.Subscriber("google_chat_ros/message_activity", MessageEvent, callback=self._message_cb) self.recent_message_event = None # GOOGLE CHAT def send_chat_client(self, goal): - client = actionlib.SimpleActionClient('google_chat_ros_client', SendMessageAction) + client = actionlib.SimpleActionClient('google_chat_ros/send', SendMessageAction) client.wait_for_server() client.send_goal(goal) client.wait_for_result() @@ -33,11 +33,11 @@ def dialogflow_task_exec_client(self, query): """ :rtype: TextResult """ - client = actionlib.SimpleActionClient('dialogflow_task_executive_client', TextAction) + client = actionlib.SimpleActionClient('dialogflow_client/text_request', TextAction) client.wait_for_server() goal = TextGoal() goal.query = query - client.send_goal(query) + client.send_goal(goal) client.wait_for_result() return client.get_result() @@ -63,14 +63,14 @@ def _message_cb(self, data): dialogflow_res = self.dialogflow_task_exec_client(text) content = " {}".format(sender_id, dialogflow_res.response.response) chat_goal.text = content - send_chat_client(chat_goal) + self.send_chat_client(chat_goal) if self.sound_play_jp: sound_goal = SoundRequestGoal() sound_goal.sound_request.sound = sound_goal.sound_request.SAY sound_goal.sound_request.command = sound_goal.sound_request.PLAY_ONCE sound_goal.sound_request.volume = 1.0 sound_goal.sound_request.arg = "{}さんから,{}というメッセージを受信しました".format(sender_name, text) - sound_client(sound_goal) + self.sound_client(sound_goal) if __name__ == '__main__': rospy.init_node('google_chat_helper') diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 152a8834a..3a06a298d 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -38,7 +38,7 @@ def list_members(self, space): class GoogleChatHTTPSServer(): """The server for getting https request from Google Chat """ - def __init__(self, host, port, certfile, keyfile, callback): + def __init__(self, host, port, certfile, keyfile, callback, user_agent): """ :param host: str, hostname :param port: int, port number @@ -50,10 +50,11 @@ def __init__(self, host, port, certfile, keyfile, callback): self._certfile = certfile self._keyfile = keyfile self._callback = callback + self.user_agent = user_agent def __handler(self, *args): try: - GoogleChatHTTPSHandler(self._callback, *args) + GoogleChatHTTPSHandler(self._callback, self.user_agent, *args) except socket.error as e: if e.errno == 104: # ignore SSL Connection reset error rospy.logdebug(e) @@ -71,8 +72,9 @@ def kill(self): class GoogleChatHTTPSHandler(s.BaseHTTPRequestHandler): """The handler for https request from Google chat API. Mainly used for recieving messages, events. """ - def __init__(self, callback, *args): + def __init__(self, callback, user_agent, *args): self._callback = callback + self.user_agent = user_agent s.BaseHTTPRequestHandler.__init__(self, *args) def do_POST(self): @@ -80,10 +82,11 @@ def do_POST(self): Please see https://developers.google.com/chat/api/guides/message-formats/events for details. """ user_agent = self.headers.get("User-Agent") - print('user_agent' + str(user_agent)) - self._parse_json() - self._callback(self.json_content) - self._response() + print('user_agent ' + str(user_agent)) + if user_agent == self.user_agent: + self._parse_json() + self._callback(self.json_content) + self._response() def _parse_json(self): content_len = int(self.headers.get("content-length")) From 26462f82367fb866ce5964954dfb3768532dc552 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sun, 20 Feb 2022 20:49:12 +0900 Subject: [PATCH 20/56] [google_chat_ros] fix send message format in helper --- google_chat_ros/scripts/helper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index 591e87f4b..e9543134d 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -55,13 +55,15 @@ def _message_cb(self, data): """ sender_id = data.message.sender.name sender_name = data.message.sender.display_name + space = data.space.name thread_name = data.message.thread_name text = data.message.argument_text if self.to_dialogflow_task_executive: chat_goal = SendMessageGoal() + chat_goal.space = space.replace('spaces/', '') chat_goal.thread_name = thread_name dialogflow_res = self.dialogflow_task_exec_client(text) - content = " {}".format(sender_id, dialogflow_res.response.response) + content = "<{}> {}".format(sender_id, dialogflow_res.response.response) chat_goal.text = content self.send_chat_client(chat_goal) if self.sound_play_jp: From ed82f990261c88cfb81688049ed6630b27b6f524 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Mon, 25 Apr 2022 18:06:20 +0900 Subject: [PATCH 21/56] [google_chat_ros] add pub/sub library [google_chat_ros][WIP] pub sub mode --- google_chat_ros/chat_card_example.md | 176 ++++++++++++++++++ google_chat_ros/launch/google_chat.launch | 2 +- .../scripts/google_chat_ros_server.py | 54 +++--- google_chat_ros/scripts/google_pubsub.py | 33 ++++ .../src/google_chat_ros/google_chat.py | 26 +++ 5 files changed, 267 insertions(+), 24 deletions(-) create mode 100644 google_chat_ros/chat_card_example.md create mode 100644 google_chat_ros/scripts/google_pubsub.py diff --git a/google_chat_ros/chat_card_example.md b/google_chat_ros/chat_card_example.md new file mode 100644 index 000000000..48e0c5d0f --- /dev/null +++ b/google_chat_ros/chat_card_example.md @@ -0,0 +1,176 @@ + + +## Upload img + +## Text Message +```yaml +goal: + text: 'Hello!' + space: 'AAAAyiXag-0' +``` + +## Card Message +### Card Message with image +```yaml +goal: + cards: + - + sections: + - + widgets: + - + image: + localpath: '/tmp/tweet_image_server.png' + buttons: + - + text_button_name: 'Manage my supervisor HERE!' + text_button_on_click: + open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' + space: 'AAAAyiXag-0' +``` + +```yaml +goal: + cards: + - + sections: + - + widgets: + - + image: + image_url: 'https://media-cdn.tripadvisor.com/media/photo-s/11/fb/90/e4/dsc-7314-largejpg.jpg' + space: 'AAAAyiXag-0' +``` + +### Card Message with header +```yaml +goal: + cards: + - + header: + title: 'What do you want to eat?' + subtitle: 'Executed by Yoshiki Obinata' + image_localpath: '/tmp/tweet_image_server.png' + space: 'AAAAyiXag-0' +``` + +### Text Paragraph +```yaml +goal: + cards: + - + sections: + - + widgets: + - + text_paragraph: 'Write a lot of code, paper.' + space: 'AAAAyiXag-0' +``` +```yaml +goal: + text: 'Hey ,' + cards: + - + sections: + - + widgets: + - + text_paragraph: 'Write a lot of code, paper. ' + space: 'AAAAyiXag-0' +``` + +### KeyValue +```yaml +goal: + text: 'Something FATAL errors have happened in my computer, please fix ASAP' + cards: + - + sections: + - + widgets: + - + key_value: + top_label: 'Process ID' + content: '1234' + bottom_label: 'rospy.Exception...' + on_click: + open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' + icon: 'DESCRIPTION' + button: + text_button_name: 'Rerun my supervisor HERE!' + text_button_on_click: + open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' + space: 'AAAAyiXag-0' +``` + +### Order Bot +```yaml +goal: + cards: + - + header: + title: 'Food delivery errand demo' + subtitle: 'Executed by Yoshiki Obinata' + image_url: 'https://inton-shinjukunishiguchi.com/wp-content/uploads/2021/03/uber-eats-logo-1-300x300.png' + sections: + - + widgets: + - + key_value: + top_label: 'Shop' + content: 'Starbucks' + - + key_value: + top_label: 'Status' + content: 'In delivery' + - + header: 'Shop info' + widgets: + - + image: + image_url: 'https://media-cdn.tripadvisor.com/media/photo-s/11/fb/90/e4/dsc-7314-largejpg.jpg' + - + widgets: + - + buttons: + - + text_button_name: 'ORDER INFO' + text_button_on_click: + open_link_url: 'https://webapp.starbucks.co.jp/' + space: 'AAAAyiXag-0' +``` + +### Interactive button +```yaml +goal: + cards: + - + header: + title: 'What do you want to eat?' + subtitle: 'Please choose the food shop!' + sections: + - + widgets: + - + buttons: + - + text_button_name: 'STARBUCKS' + text_button_on_click: + action: + action_method_name: 'vote_starbucks' + parameters: + - + key: 'shop' + value: 'starbucks' + - + text_button_name: 'SUBWAY' + text_button_on_click: + action: + action_method_name: 'vote_subway' + parameters: + - + key: 'shop' + value: 'subway' + + space: 'AAAAyiXag-0' +``` diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index c73272e4d..ade8d8f52 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -1,5 +1,5 @@ - + diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_server.py index e504013e5..50f55405a 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_server.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import gdown import json import os @@ -14,6 +14,7 @@ from gdrive_ros.srv import * from google_chat_ros.google_chat import GoogleChatRESTClient from google_chat_ros.google_chat import GoogleChatHTTPSServer +from google_chat_ros.google_chat import GoogleChatPubSubClient from google_chat_ros.msg import * import rospy @@ -23,11 +24,12 @@ class GoogleChatROS(object): Send request to Google Chat REST API via ROS """ def __init__(self): - recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'dialogflow', 'url', 'none' + recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'dialogflow', 'url', 'pubsub', 'none' self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service') - # For REST, sending message - rest_keyfile = rospy.get_param('~google_cloud_credentials_json') - self._client = GoogleChatRESTClient(rest_keyfile) + google_credentials = rospy.get_param('~google_cloud_credentials_json') + + # For sending message + self._client = GoogleChatRESTClient(google_credentials) rospy.loginfo("Starting Google Chat REST service...") try: self._client.build_service() # Start google chat authentication and service @@ -42,8 +44,8 @@ def __init__(self): rospy.logwarn("Failed to start Google Chat REST service") rospy.logerr(e) - # For POST, recieving message - if recieving_chat_mode in ("url", "dialogflow"): + # For recieving message + if recieving_chat_mode in ("url", "dialogflow", "pubsub"): # rosparams self.upload_data_timeout = rospy.get_param('~upload_data_timeout') self.download_data = rospy.get_param('~download_data') @@ -54,31 +56,37 @@ def __init__(self): self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) self._card_activity_pub = rospy.Publisher("~card_activity", CardEvent, queue_size=1) - try: - if recieving_chat_mode == "url": - rospy.loginfo("Expected to get Google Chat Bot URL request") - rospy.loginfo("Starting Google Chat HTTPS server...") - self.host = rospy.get_param('~host') - self.port = int(rospy.get_param('~port')) - self.ssl_certfile = rospy.get_param('~ssl_certfile') - self.ssl_keyfile = rospy.get_param('~ssl_keyfile') + if recieving_chat_mode == "url": + rospy.loginfo("Expected to get Google Chat Bot URL request") + rospy.loginfo("Starting Google Chat HTTPS server...") + self.host = rospy.get_param('~host') + self.port = int(rospy.get_param('~port')) + self.ssl_certfile = rospy.get_param('~ssl_certfile') + self.ssl_keyfile = rospy.get_param('~ssl_keyfile') + try: self._server = GoogleChatHTTPSServer( self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb, user_agent='Google-Dynamite') rospy.on_shutdown(self.killhttpd) # shutdown https server self._server.run() - elif recieving_chat_mode == "dialogflow": - rospy.loginfo("Expected to get OriginalDetectIntentRequest.msg from dialogflow webhook ros node.") - self._sub = rospy.Subscriber("dialogflow_original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) - - except ConnectionError as e: - rospy.logwarn("The error occurred while starting HTTPS server") - rospy.logerr(e) + except ConnectionError as e: + rospy.logwarn("The error occurred while starting HTTPS server") + rospy.logerr(e) + elif recieving_chat_mode == "dialogflow": + rospy.loginfo("Expected to get OriginalDetectIntentRequest.msg from dialogflow webhook ros node.") + self._sub = rospy.Subscriber("dialogflow_original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) + elif recieving_chat_mode == "pubsub": + rospy.loginfo("Expected to use Google Cloud Pub Sub service") + self.project_id = rospy.get_param("~project_id") + self.subscription_id = rospy.get_param("~subscription_id") + self._pubsub_client = GoogleChatPubSubClient( + self.project_id, self.subscription_id, self.event_cb, google_credentials) + self._pubsub_client.run() elif recieving_chat_mode == "none": rospy.logwarn("You cannot recieve Google Chat event because HTTPS server is not running.") else: - rospy.logerr("Please choose recieving_mode param from dialogflow, https, none.") + rospy.logerr("Please choose recieving_mode param from dialogflow, https, pubsub, none.") def killhttpd(self): self._server.kill() diff --git a/google_chat_ros/scripts/google_pubsub.py b/google_chat_ros/scripts/google_pubsub.py new file mode 100644 index 000000000..ac7d7b062 --- /dev/null +++ b/google_chat_ros/scripts/google_pubsub.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# TODO this is a example code, should be removed + +from concurrent.futures import TimeoutError +from google.cloud import pubsub_v1 + +project_id = "fetch-kiedno" +subscription_id = "chat-sub" +timeout = 60. + +subscriber = pubsub_v1.SubscriberClient() +# The `subscription_path` method creates a fully qualified identifier +# in the form `projects/{project_id}/subscriptions/{subscription_id}` +subscription_path = subscriber.subscription_path(project_id, subscription_id) + +def callback(message: pubsub_v1.subscriber.message.Message) -> None: + print(f"Received {message}.") + print(message.data) + message.ack() + +streaming_pull_future = subscriber.subscribe(subscription_path, callback=callback) +print(f"Listening for messages on {subscription_path}..\n") + +# Wrap subscriber in a 'with' block to automatically call close() when done. +with subscriber: + try: + # When `timeout` is not set, result() will block indefinitely, + # unless an exception is encountered first. + streaming_pull_future.result(timeout=timeout) + except TimeoutError: + streaming_pull_future.cancel() # Trigger the shutdown. + streaming_pull_future.result() # Block until the shutdown is complete. diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 3a06a298d..fb0c67a99 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -1,6 +1,9 @@ from __future__ import print_function from apiclient.discovery import build +from concurrent.futures import TimeoutError +from google.cloud import pubsub_v1 +from google.oauth2 import service_account from httplib2 import Http import http.server as s import json @@ -127,3 +130,26 @@ def _decode_dict(self, data): value = self._decode_dict(value) rv[key] = value return rv + +class GoogleChatPubSubClient(): + def __init__(self, project_id, subscription_id, callback, keyfile): + self._callback = callback + auth_scopes = "https://www.googleapis.com/auth/pubsub" + self.__credentials = ServiceAccountCredentials.from_json_keyfile_name(keyfile, auth_scopes) + self._sub = pubsub_v1.SubscriberClient(credentials=self.__credentials) + sub_path = self._sub.subscription_path(project_id, subscription_id) + self._streaming_pull_future = self._sub.subscribe(sub_path, callback=self._pubsub_cb) + + def _pubsub_cb(self, message): + rospy.logdebug("Recieved {message}") + rospy.logdebug(message.data) + self._callback(message.data) + message.ack() + + def run(self): + with self._sub: + try: + self._streaming_pull_future.result() + except KeyboardInterrupt: + self._streaming_pull_future.cancel() + self._streaming_pull_future.result() From d42bee5926e8417e43e9d539cc542e523b42ebf1 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 11:14:42 +0900 Subject: [PATCH 22/56] [google_chat_ros] update README for current google_chat_ros packege TODO: add image --- google_chat_ros/README.md | 308 ++++++++++++++++++++++++++++++++------ 1 file changed, 262 insertions(+), 46 deletions(-) diff --git a/google_chat_ros/README.md b/google_chat_ros/README.md index 9f07ac9d8..0572f25b6 100644 --- a/google_chat_ros/README.md +++ b/google_chat_ros/README.md @@ -1,17 +1,49 @@ -# The package for using Google chat services with ROS +# Google Chat ROS +The ROS wrapper for Google Chat API +1. [Installation Guide](#install) +1. [Sending the message](#send) +1. [Recieving the message](#recieve) +1. [Handling the event](#event) +1. [Optional functions](#optional) +1. [Helper nodes](#helper) -## What is this? -Use Google Chat API with ROS. -![Screenshot from 2021-11-01 15-53-27](https://user-images.githubusercontent.com/27789460/139635911-66232c88-d3b9-4d7d-940e-966fbac9d800.png) -System components -![GoogleChatROS_system](https://user-images.githubusercontent.com/27789460/139635648-4ddbf9da-90e9-4b87-b958-ca996a8ffc4f.png) - -## How to use? -### 1. Create a service account and private key + +## 1. Installation Guide +### 1.1 Get the API KEY +At first, you should have the permission to access the Google Chat API. See [Google Official Document](https://developers.google.com/chat/how-tos/service-accounts#step_1_create_service_account_and_private_key). Please ensure to get JSON credetial file and save it. DO NOT LOST IT! -For JSK members, all keys are available at [Google Drive](https://drive.google.com/drive/folders/1Enbbta5QuZ-hrUWdTjVDEjDJc3j7Abxo?usp=sharing). If you make new API keys, please upload them here. +For JSK members, all keys are available at [Google Drive](https://drive.google.com/drive/folders/1Enbbta5QuZ-hrUWdTjVDEjDJc3j7Abxo?usp=sharing). If you make new API keys, please upload them here. + +### 1.2 Select the way how to recieve Google Chat event +The way you recieve Google Chat event from API server depends on your system. If your system has static IP and is allowed to recieve https request with specific port, please see [HTTPS mode](#https). If not, please see [Pub/Sub mode](#pubsub). + + +#### HTTPS mode +When you send the message, the node uses Google REST API. +When you recieve the message, Google Chat API sends https request to your machine and the node handles it. + +You have to prepare SSL certificate. Self-signed one is not available because of Google security issue. Please use the service like Let's Encrypt. In Google Cloud console, please choose `App URL` as connection settings and fill the URL in the App URL form. + + +#### Pub/Sub mode +When you send the message, the node uses Google REST API. +When you recieve the message, the node uses Google Pub/Sub API's subscription. The node has already established its connection to Google Pub/Sub API when you launch it. + +The way how to set up in Google Cloud console shows below. +1. Authorize the existing Google Chat API project to access Google Cloud Pub/Sub service +In IAM settings in the console, please add the role `Pub/Sub Admin` to service account. + +1. Create Pub/Sub topic and subscriber +In Pub/Sub settings in the console, please add the topic and subscriptions. +In the figure, we set the topic name `chat`, the subscription name `chat-sub` as an example. + +Note that if you set the topic name `chat`, the full name of it becomes `projects//topics/chat`. Please confirm the subsciptions subscribes the full name not short one. +1. Set Google Chat API Conncection settings +Please choose `Cloud Pub/Sub` as connection settings and fill the full topic name in the Topic Name form. + -### 2. Build ROS workspace +### 1.3 Install/Build the ROS node +If you want to build from the source ```bash source /opt/ros/${ROS_DISTRO}/setup.bash mkdir -p ~/catkin_ws/src && cd ~/catkin_ws/src @@ -20,48 +52,232 @@ rosdep install --ignore-src --from-paths . -y -r cd .. catkin build ``` - -### 3. Use google chat ros -#### 3.1 Run the server -Execute +### 1.4 Launch the node +#### HTTPS mode +You have to set rosparams `recieving_mode=https`, `google_cloud_credentials_json`, `host`, `port`, `ssl_certfile`, `ssl_keyfile`. +#### Pub/Sub mode +You have to set rosparams `recieving_mode=pubsub`, `google_cloud_credentials_json`, `project_id`, `subscription_id`. `subscription_id` would be `chat-sub` if you follow [Pub/Sub mode](#pubsub) example. +##### Example ```bash -roslaunch google_chat_ros google_chat.launch keyfile:=${PATH_TO_keyfile.json} +roslaunch google_chat_ros google_chat.launch recieving_mode:=pubsub google_cloud_credentials_json:=/path/to/-XXXXXXXX.json project_id:= subscription_id:=chat-sub ``` -and run the action server. + +## 2. Sending the message +### 2.1 Understanding Google Chat Room +When you see Google Chat UI with browsers or smartphone's apps, you may see `space`, `thread`. If you send new message, you must specify the space or thread you want to send the message to. You can get the space name from chat room's URL. If it is `https://mail.google.com/chat/u/0/#chat/space/XXXXXXXXXXX`, `XXXXXXXXXXX` becomes the space name. -#### 2.2 Run the client -First, you have to identify your chat room. You can get it from chat room's URL. If it is `https://mail.google.com/chat/u/0/#chat/space/XXXXXXXXXXX`, `XXXXXXXXXXX` becomes the space name. -##### terminal example -```bash -rostopic pub /google_chat_ros/send/goal google_chat_ros/GoogleChatRESTActionGoal "header: - seq: 0 - stamp: - secs: 0 - nsecs: 0 - frame_id: '' -goal_id: - stamp: - secs: 0 - nsecs: 0 - id: '' -goal: - space: 'YOUR_SPACE' - message_type: 'text' - content: 'Hello world from ROS!'" +### 2.2 Message format +There are 2 types of messages, text and card. The card basically follows [the original json structure](https://developers.google.com/chat/api/guides/message-formats/events#event_fields). As the node covers all the units in here with ros action msgs, it may be complicated for you if you want to use all of them. So in Examples sections, we'll show you simple ones. + +### 2.3 Sending the message by actionlib +All you have to do is send Actionlib goal to `~send/goal`. + +### 2.4 Examples +Showing the message examples with `rostopic pub -1` command on `bash`. +#### Sending a text message +``` bash +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + text: 'Hello!' + space: 'spaces/'" ``` + +#### Sending a message with KeyValue card +``` bash +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + text: 'Something FATAL errors have happened in my computer, please fix ASAP' + cards: + - + sections: + - + widgets: + - + key_value: + top_label: 'Process ID' + content: '1234' + bottom_label: 'rospy' + icon: 'DESCRIPTION' + space: 'spaces/'" +``` + + +#### Sending an Interactive button +``` bash +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + cards: + - + header: + title: 'What do you want to eat?' + subtitle: 'Please choose the food shop!' + sections: + - + widgets: + - + buttons: + - + text_button_name: 'STARBUCKS' + text_button_on_click: + action: + action_method_name: 'vote_starbucks' + parameters: + - + key: 'shop' + value: 'starbucks' + - + text_button_name: 'SUBWAY' + text_button_on_click: + action: + action_method_name: 'vote_subway' + parameters: + - + key: 'shop' + value: 'subway' + + space: 'spaces/'" +``` + +#### Sending a message with a image +See [Here](#image). + + +## 3. Recieving the messages +### 3.1 ROS Topic +When the bot was mentioned, the node publishes `~message_activity` topic. + +### 3.2 Examples + +#### Recieving a text message +```yaml +event_time: "2022-04-28T06:25:26.884623Z" +space: + name: "spaces/" + display_name: '' + room: False + dm: True +message: + name: "spaces//messages/" + sender: + name: "users/" + display_name: "Yoshiki Obinata" + avatar_url: "" + avatar: [] + email: "" + bot: False + human: True + create_time: "2022-04-28T06:25:26.884623Z" + text: "Hello!" + thread_name: "spaces//threads/" + annotations: [] + argument_text: "Hello!" + attachments: [] +user: + name: "users/" + display_name: "Yoshiki Obinata" + avatar_url: "" + avatar: [] + email: "" + bot: False + human: True +``` + +#### Recieving a message with a image or gdrive file and download it + + + +## 4. Handling the interactive event +If you've already sent the interactive card like [Interactive card example](#interactive), you can receive the activity of buttons. Suppose someone pressed the button `STARBUCKS`, the node publishes a `~card_activity` topic like + +``` yaml +event_time: "2022-05-02T00:23:47.855023Z" +space: + name: "spaces/" + display_name: "robotroom_with_thread" + room: True + dm: False +message: + name: "spaces//messages/Go__sDfIdec.Go__sDfIdec" + sender: + name: "users/100406614699672138585" + display_name: "Fetch1075" + avatar_url: "https://lh4.googleusercontent.com/proxy/hWEAWt6fmHsFAzeiEoV5FMOx5-jmU3OnzQxCtrr9unyt73NNwv0lh7InFzOh-0yO3jOPgtColHBywnZnJvl4SVqqqrYkyT1uf18k_hDIVYrAv87AY7lM0hp5KtQ1m9br-aPFE98QwNnSTYc2LQ" + avatar: [] + email: '' + bot: True + human: False + create_time: "2022-05-02T00:23:47.855023Z" + text: '' + thread_name: "spaces//threads/Go__sDfIdec" + annotations: [] + argument_text: '' + attachments: [] +user: + name: "users/103866924487978823908" + display_name: "Yoshiki Obinata" + avatar_url: "https://lh3.googleusercontent.com/a-/AOh14GgexXiq8ImuKMgOq6QG-4geIzz5IC1-xa0Caead=k" + avatar: [] + email: "" + bot: False + human: True +action: + action_method_name: "vote_starbucks" + parameters: + - + key: "shop" + value: "starbucks" +``` +After the node which handles the chat event subscribed the topic, it can respond with text message like + +``` bash +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + cards: + - + sections: + - + widgets: + - + key_value: + top_label: 'The shop accepted!' + content: 'You choose STARBUCKS!!' + icon: 'DESCRIPTION' + space: 'spaces/' + thread_name: 'spaces//threads/'" +``` +The important point is that the client node has to remember the `thread_name` which the card event was occured at and send response to it. + +## 5. Optional functions + +### 5.1 Sending a message with a image +To send a image, you have to use `card` type message. If you want to add the image uploaded to a storage server available for everyone, you just add its URI like +``` yaml +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + cards: + - + sections: + - + widgets: + - + image: + image_url: 'https://media-cdn.tripadvisor.com/media/photo-s/11/fb/90/e4/dsc-7314-largejpg.jpg' + space: 'spaces/'" +``` +If you want to attach image saved at your host, you have to launch (gdrive_ros)[https://github.com/jsk-ros-pkg/jsk_3rdparty/tree/master/gdrive_ros] at first and set `~gdrive_upload_service` param with `gdrive_ros/Upload` service name. Then publish topic like +``` yaml +rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: + cards: + - + sections: + - + widgets: + - + image: + localpath: '/home/user/Pictures/image.png' + space: 'spaces/' +``` +### 5.2 Recieving a message with images or gdrive file +You have to set rosparam `~download_data` True, `~download_directory`. If the node recieved the message with image or google drive file, it automatically downloads to `~donwload_directory` path. + ##### roseus example ```lisp (load "package://google_chat_ros/scripts/google-chat.l") (send-google-chat-message "YOUR_SPACE" "text" "Hello world from eus!") ``` -## Google Chat Message types -You can set Google Chat message type by setting `message_type` in ros message. -### text -Send simple text message. -### card -Send Google Chat Card message. -See [here](https://developers.google.com/chat/api/guides/message-formats/cards) for details. - -## Sending Images -You have to get image's permanent link and attach its url to card type message. To get it, please consider using jsk_3rdparty/gdrive_ros. From 7002c62aa0c97c92b24370ad78eca4869285a387 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 11:17:53 +0900 Subject: [PATCH 23/56] [google_chat_ros] support GCP Pub/Sub, drop Python2 support for parsing json, add response from Google Chat API server to sendmessage action result --- google_chat_ros/action/SendMessage.action | 3 + google_chat_ros/launch/google_chat.launch | 16 ++++- ..._ros_server.py => google_chat_ros_node.py} | 49 ++++++++------- .../src/google_chat_ros/google_chat.py | 60 ++++++------------- 4 files changed, 65 insertions(+), 63 deletions(-) rename google_chat_ros/scripts/{google_chat_ros_server.py => google_chat_ros_node.py} (94%) diff --git a/google_chat_ros/action/SendMessage.action b/google_chat_ros/action/SendMessage.action index baa0ccdca..4b1d2bb35 100644 --- a/google_chat_ros/action/SendMessage.action +++ b/google_chat_ros/action/SendMessage.action @@ -1,10 +1,13 @@ # Define the goal string text google_chat_ros/Card[] cards +bool update_message # Not creating new message, but rewrite existing message string thread_name string space --- # Define the result +google_chat_ros/Message message_result +google_chat_ros/Card[] cards_result bool done --- # Define a feedback message diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index ade8d8f52..7cd6adf23 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -10,13 +10,21 @@ + + + + + + + + - recieving_mode: $(arg recieving_mode) @@ -27,6 +35,12 @@ download_directory: $(arg download_directory) download_avatar: $(arg download_avatar) google_cloud_credentials_json: $(arg google_cloud_credentials_json) + host: $(arg host) + port: $(arg port) + ssl_certfile: $(arg ssl_certfile) + ssl_keyfile: $(arg ssl_keyfile) + project_id: $(arg project_id) + subscription_id: $(arg subscription_id) diff --git a/google_chat_ros/scripts/google_chat_ros_server.py b/google_chat_ros/scripts/google_chat_ros_node.py similarity index 94% rename from google_chat_ros/scripts/google_chat_ros_server.py rename to google_chat_ros/scripts/google_chat_ros_node.py index 50f55405a..5c5e06d5e 100755 --- a/google_chat_ros/scripts/google_chat_ros_server.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -66,7 +66,7 @@ def __init__(self): try: self._server = GoogleChatHTTPSServer( self.host, self.port, self.ssl_certfile, self.ssl_keyfile, callback=self.event_cb, user_agent='Google-Dynamite') - rospy.on_shutdown(self.killhttpd) # shutdown https server + rospy.on_shutdown(self.killhttpd) # shutdown https server TODO is this okay in try ? self._server.run() except ConnectionError as e: rospy.logwarn("The error occurred while starting HTTPS server") @@ -78,12 +78,13 @@ def __init__(self): rospy.loginfo("Expected to use Google Cloud Pub Sub service") self.project_id = rospy.get_param("~project_id") self.subscription_id = rospy.get_param("~subscription_id") + rospy.on_shutdown(self.killpubsub) self._pubsub_client = GoogleChatPubSubClient( self.project_id, self.subscription_id, self.event_cb, google_credentials) self._pubsub_client.run() elif recieving_chat_mode == "none": - rospy.logwarn("You cannot recieve Google Chat event because HTTPS server is not running.") + rospy.logwarn("You cannot recieve Google Chat event because HTTPS server or Google Cloud Pub/Sub is not running.") else: rospy.logerr("Please choose recieving_mode param from dialogflow, https, pubsub, none.") @@ -91,6 +92,9 @@ def __init__(self): def killhttpd(self): self._server.kill() + def killpubsub(self): + self._pubsub_client.kill() + def rest_cb(self, goal): """Get ROS SendMessageAction Goal and send request to Google Chat API. :param goal: ROS SendMessageAction Goal @@ -108,6 +112,8 @@ def rest_cb(self, goal): # Card json_body['cards'] = [] if goal.cards: + if goal.update_message: + json_body['actionResponse'] = {"type": "UPDATE_MESSAGE"} for card in goal.cards: card_body = {} # card/header @@ -138,16 +144,16 @@ def rest_cb(self, goal): json_body['cards'].append(card_body) try: - # establish the service - # TODO debug - rospy.loginfo("Send json") - rospy.loginfo(str(json_body)) + rospy.logdebug("Send json") + rospy.logdebug(str(json_body)) self._client.build_service() - feedback.status = str( - self._client.message_request( - space=goal.space, - json_body=json_body - )) + res = self._client.message_create( + space=goal.space, + json_body=json_body + ) + result.message_result = self._make_message_msg({'message': res}) + # TODO add the result of what card was sent + # result.cards_result = except Exception as e: rospy.logerr(str(e)) feedback.status = str(e) @@ -158,13 +164,13 @@ def rest_cb(self, goal): result.done = success self._as.set_succeeded(result) - def event_cb(self, event): + def event_cb(self, event: dict, publish_topic=True): """Parse Google Chat API json content and publish as a ROS Message. See https://developers.google.com/chat/api/reference/rest to check what contents are included in the json. :param event: A google Chat API POST request json content. See https://developers.google.com/chat/api/guides/message-formats/events#event_fields for details. - :rtype: None + :rtype: ros message """ rospy.logdebug("GOOGLE CHAT ORIGINAL JSON EVENT") rospy.logdebug(json.dumps(event, indent=2)) @@ -188,8 +194,9 @@ def event_cb(self, event): msg.user = user msg.added = True if event['type'] == "ADDED_TO_SPACE" else False msg.removed = True if event['type'] == "REMOVED_FROM_SPACE" else False - self._space_activity_pub.publish(msg) - return + if publish_topic: + self._space_activity_pub.publish(msg) + return msg elif event['type'] == 'MESSAGE': msg = MessageEvent() @@ -197,8 +204,9 @@ def event_cb(self, event): msg.space = space msg.user = user msg.message = self._make_message_msg(event) - self._message_activity_pub.publish(msg) - return + if publish_topic: + self._message_activity_pub.publish(msg) + return msg elif event['type'] == 'CARD_CLICKED': msg = CardEvent() @@ -217,8 +225,9 @@ def event_cb(self, event): action_parameter.value = param.get('value') parameters.append(action_parameter) msg.action.parameters = parameters - self._card_activity_pub.publish(msg) - return + if publish_topic: + self._card_activity_pub.publish(msg) + return msg else: rospy.logerr("Got unknown event type.") @@ -464,6 +473,6 @@ def _download_content(self, uri=None, drive_id=None, filename=''): return path if __name__ == '__main__': - rospy.init_node('google_chat', disable_signals=True) + rospy.init_node('google_chat') node = GoogleChatROS() rospy.spin() diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index fb0c67a99..326c08f4e 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -1,9 +1,8 @@ -from __future__ import print_function - from apiclient.discovery import build +import base64 from concurrent.futures import TimeoutError from google.cloud import pubsub_v1 -from google.oauth2 import service_account +from google.oauth2.service_account import Credentials from httplib2 import Http import http.server as s import json @@ -27,16 +26,18 @@ def build_service(self): self.__credentials = ServiceAccountCredentials.from_json_keyfile_name(self.keyfile, self._auth_scopes) self._chat = build('chat', 'v1', http=self.__credentials.authorize(Http())) - def message_request(self, space, json_body): - parent = 'spaces/' + space + def message_create(self, space, json_body): + if not space.startswith('spaces/'): + raise RuntimeError("Space name must begin with spaces/") # returns same 403 error both authenticate error and not connected error - return self._chat.spaces().messages().create(parent=parent, body=json_body).execute() + return self._chat.spaces().messages().create(parent=space, body=json_body).execute() def list_members(self, space): """Show member list in the space. """ - parent = 'spaces/' + space - return self._chat.spaces().members().list(parent=parent).execute() + if not space.startswith('spaces/'): + raise RuntimeError("Space name must begin with spaces/") + return self._chat.spaces().members().list(parent=space).execute() class GoogleChatHTTPSServer(): """The server for getting https request from Google Chat @@ -94,7 +95,7 @@ def do_POST(self): def _parse_json(self): content_len = int(self.headers.get("content-length")) request_body = self.rfile.read(content_len).decode('utf-8') - self.json_content = json.loads(request_body, object_hook=self._decode_dict) # json.loads returns unicode by default + self.json_content = json.loads(request_body) # json.loads returns unicode by default def _response(self): self.send_response(200) @@ -104,46 +105,19 @@ def _bad_request(self): self.send_response(400) self.end_headers() - ### helper functions - def _decode_list(self, data): - rv = [] - for item in data: - if isinstance(item, unicode): - item = item.encode('utf-8') - elif isinstance(item, list): - item = self._decode_list(item) - elif isinstance(item, dict): - item = self._decode_dict(item) - rv.append(item) - return rv - - def _decode_dict(self, data): - rv = {} - for key, value in data.iteritems(): - if isinstance(key, unicode): - key = key.encode('utf-8') - if isinstance(value, unicode): - value = value.encode('utf-8') - elif isinstance(value, list): - value = self._decode_list(value) - elif isinstance(value, dict): - value = self._decode_dict(value) - rv[key] = value - return rv - class GoogleChatPubSubClient(): def __init__(self, project_id, subscription_id, callback, keyfile): self._callback = callback - auth_scopes = "https://www.googleapis.com/auth/pubsub" - self.__credentials = ServiceAccountCredentials.from_json_keyfile_name(keyfile, auth_scopes) + self.__credentials = Credentials.from_service_account_file(keyfile) self._sub = pubsub_v1.SubscriberClient(credentials=self.__credentials) sub_path = self._sub.subscription_path(project_id, subscription_id) self._streaming_pull_future = self._sub.subscribe(sub_path, callback=self._pubsub_cb) def _pubsub_cb(self, message): - rospy.logdebug("Recieved {message}") - rospy.logdebug(message.data) - self._callback(message.data) + rospy.loginfo("Recieved {message}") + rospy.loginfo(message.data) + json_content = json.loads(message.data) + self._callback(json_content) message.ack() def run(self): @@ -152,4 +126,6 @@ def run(self): self._streaming_pull_future.result() except KeyboardInterrupt: self._streaming_pull_future.cancel() - self._streaming_pull_future.result() + + def kill(self): + self._streaming_pull_future.cancel() From 704c08b0670b174ad2c3c70eaea4b2b2f29f7e6f Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 14:15:06 +0900 Subject: [PATCH 24/56] [google_chat_ros] remove unnecessary doc, change logging level of PubSub handler --- google_chat_ros/chat_card_example.md | 176 ------------------ .../src/google_chat_ros/google_chat.py | 4 +- 2 files changed, 2 insertions(+), 178 deletions(-) delete mode 100644 google_chat_ros/chat_card_example.md diff --git a/google_chat_ros/chat_card_example.md b/google_chat_ros/chat_card_example.md deleted file mode 100644 index 48e0c5d0f..000000000 --- a/google_chat_ros/chat_card_example.md +++ /dev/null @@ -1,176 +0,0 @@ - - -## Upload img - -## Text Message -```yaml -goal: - text: 'Hello!' - space: 'AAAAyiXag-0' -``` - -## Card Message -### Card Message with image -```yaml -goal: - cards: - - - sections: - - - widgets: - - - image: - localpath: '/tmp/tweet_image_server.png' - buttons: - - - text_button_name: 'Manage my supervisor HERE!' - text_button_on_click: - open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' - space: 'AAAAyiXag-0' -``` - -```yaml -goal: - cards: - - - sections: - - - widgets: - - - image: - image_url: 'https://media-cdn.tripadvisor.com/media/photo-s/11/fb/90/e4/dsc-7314-largejpg.jpg' - space: 'AAAAyiXag-0' -``` - -### Card Message with header -```yaml -goal: - cards: - - - header: - title: 'What do you want to eat?' - subtitle: 'Executed by Yoshiki Obinata' - image_localpath: '/tmp/tweet_image_server.png' - space: 'AAAAyiXag-0' -``` - -### Text Paragraph -```yaml -goal: - cards: - - - sections: - - - widgets: - - - text_paragraph: 'Write a lot of code, paper.' - space: 'AAAAyiXag-0' -``` -```yaml -goal: - text: 'Hey ,' - cards: - - - sections: - - - widgets: - - - text_paragraph: 'Write a lot of code, paper. ' - space: 'AAAAyiXag-0' -``` - -### KeyValue -```yaml -goal: - text: 'Something FATAL errors have happened in my computer, please fix ASAP' - cards: - - - sections: - - - widgets: - - - key_value: - top_label: 'Process ID' - content: '1234' - bottom_label: 'rospy.Exception...' - on_click: - open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' - icon: 'DESCRIPTION' - button: - text_button_name: 'Rerun my supervisor HERE!' - text_button_on_click: - open_link_url: 'http://supervisor.fetch1075.jsk.imi.i.u-tokyo.ac.jp' - space: 'AAAAyiXag-0' -``` - -### Order Bot -```yaml -goal: - cards: - - - header: - title: 'Food delivery errand demo' - subtitle: 'Executed by Yoshiki Obinata' - image_url: 'https://inton-shinjukunishiguchi.com/wp-content/uploads/2021/03/uber-eats-logo-1-300x300.png' - sections: - - - widgets: - - - key_value: - top_label: 'Shop' - content: 'Starbucks' - - - key_value: - top_label: 'Status' - content: 'In delivery' - - - header: 'Shop info' - widgets: - - - image: - image_url: 'https://media-cdn.tripadvisor.com/media/photo-s/11/fb/90/e4/dsc-7314-largejpg.jpg' - - - widgets: - - - buttons: - - - text_button_name: 'ORDER INFO' - text_button_on_click: - open_link_url: 'https://webapp.starbucks.co.jp/' - space: 'AAAAyiXag-0' -``` - -### Interactive button -```yaml -goal: - cards: - - - header: - title: 'What do you want to eat?' - subtitle: 'Please choose the food shop!' - sections: - - - widgets: - - - buttons: - - - text_button_name: 'STARBUCKS' - text_button_on_click: - action: - action_method_name: 'vote_starbucks' - parameters: - - - key: 'shop' - value: 'starbucks' - - - text_button_name: 'SUBWAY' - text_button_on_click: - action: - action_method_name: 'vote_subway' - parameters: - - - key: 'shop' - value: 'subway' - - space: 'AAAAyiXag-0' -``` diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 326c08f4e..470a000b0 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -114,8 +114,8 @@ def __init__(self, project_id, subscription_id, callback, keyfile): self._streaming_pull_future = self._sub.subscribe(sub_path, callback=self._pubsub_cb) def _pubsub_cb(self, message): - rospy.loginfo("Recieved {message}") - rospy.loginfo(message.data) + rospy.logdebug("Recieved {message}") + rospy.logdebug(message.data) json_content = json.loads(message.data) self._callback(json_content) message.ack() From 90c2fb4d517b932bd82742c9e75176102fc5e8b2 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata <27789460+mqcmd196@users.noreply.github.com> Date: Tue, 3 May 2022 15:06:24 +0900 Subject: [PATCH 25/56] [google_chat_ros]README, add figures, fix typo --- google_chat_ros/README.md | 56 ++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/google_chat_ros/README.md b/google_chat_ros/README.md index 0572f25b6..d4c4c2a0e 100644 --- a/google_chat_ros/README.md +++ b/google_chat_ros/README.md @@ -21,26 +21,41 @@ The way you recieve Google Chat event from API server depends on your system. If #### HTTPS mode When you send the message, the node uses Google REST API. When you recieve the message, Google Chat API sends https request to your machine and the node handles it. - + +![google_chat_https_system](https://user-images.githubusercontent.com/27789460/166410618-6ae286bd-86d8-47e8-87c9-bae0c66493ed.png) + You have to prepare SSL certificate. Self-signed one is not available because of Google security issue. Please use the service like Let's Encrypt. In Google Cloud console, please choose `App URL` as connection settings and fill the URL in the App URL form. +![google_chat_https](https://user-images.githubusercontent.com/27789460/166408349-09520454-4c55-4ca7-bbdc-1d3f27e243b9.png) + #### Pub/Sub mode When you send the message, the node uses Google REST API. When you recieve the message, the node uses Google Pub/Sub API's subscription. The node has already established its connection to Google Pub/Sub API when you launch it. - + +![google_chat_pubsub_system](https://user-images.githubusercontent.com/27789460/166410714-03f16096-eea4-4eeb-9487-5ab3df309332.png) + The way how to set up in Google Cloud console shows below. -1. Authorize the existing Google Chat API project to access Google Cloud Pub/Sub service +##### 1. Authorize the existing Google Chat API project to access Google Cloud Pub/Sub service In IAM settings in the console, please add the role `Pub/Sub Admin` to service account. - -1. Create Pub/Sub topic and subscriber + +![pubsub_admin_mosaic](https://user-images.githubusercontent.com/27789460/166408915-832a279f-da9e-463b-86e4-8a18ad5e4f5a.png) + +##### 2. Create Pub/Sub topic and subscriber In Pub/Sub settings in the console, please add the topic and subscriptions. In the figure, we set the topic name `chat`, the subscription name `chat-sub` as an example. - + +![pubsub_topic_mosaic](https://user-images.githubusercontent.com/27789460/166409434-8f7fa329-1ae1-4cc4-aba2-82f43c2de16f.png) + +![pubsub_subscription](https://user-images.githubusercontent.com/27789460/166409454-cc59ec43-f59e-4b63-a1b8-fadefcdd46ce.png) + Note that if you set the topic name `chat`, the full name of it becomes `projects//topics/chat`. Please confirm the subsciptions subscribes the full name not short one. -1. Set Google Chat API Conncection settings + +##### 3. Set Google Chat API Connection settings Please choose `Cloud Pub/Sub` as connection settings and fill the full topic name in the Topic Name form. - + +![google_chat_pubsub](https://user-images.githubusercontent.com/27789460/166408478-b662b73c-35a8-43e8-aaaa-93efdd48e486.png) + ### 1.3 Install/Build the ROS node If you want to build from the source @@ -80,6 +95,8 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal text: 'Hello!' space: 'spaces/'" ``` +![google_chat_text](https://user-images.githubusercontent.com/27789460/166410345-4ba29050-a83d-42c7-9dfe-0babc0486001.png) + #### Sending a message with KeyValue card ``` bash @@ -98,6 +115,8 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal icon: 'DESCRIPTION' space: 'spaces/'" ``` +![google_chat_keyvalue](https://user-images.githubusercontent.com/27789460/166410374-f94a9da7-45fb-4915-929e-3181891d7293.png) + #### Sending an Interactive button @@ -134,8 +153,10 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal space: 'spaces/'" ``` +![google_chat_interactive_button](https://user-images.githubusercontent.com/27789460/166410386-395daab4-158c-4f47-b0c3-324b2f258ffd.png) + -#### Sending a message with a image +#### Sending a message with an image See [Here](#image). @@ -179,7 +200,7 @@ user: human: True ``` -#### Recieving a message with a image or gdrive file and download it +#### Recieving a message with an image or gdrive file and download it @@ -241,12 +262,16 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal space: 'spaces/' thread_name: 'spaces//threads/'" ``` +![google_chat_interact](https://user-images.githubusercontent.com/27789460/166410418-c2bdc2e5-9916-4b50-a705-f838d86681aa.png) + + The important point is that the client node has to remember the `thread_name` which the card event was occured at and send response to it. + ## 5. Optional functions -### 5.1 Sending a message with a image -To send a image, you have to use `card` type message. If you want to add the image uploaded to a storage server available for everyone, you just add its URI like +### 5.1 Sending a message with an image +To send an image, you have to use `card` type message. If you want to add the image uploaded to a storage server available for everyone, you just add its URI like ``` yaml rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal "goal: cards: @@ -274,10 +299,3 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal ``` ### 5.2 Recieving a message with images or gdrive file You have to set rosparam `~download_data` True, `~download_directory`. If the node recieved the message with image or google drive file, it automatically downloads to `~donwload_directory` path. - -##### roseus example -```lisp -(load "package://google_chat_ros/scripts/google-chat.l") -(send-google-chat-message "YOUR_SPACE" "text" "Hello world from eus!") -``` - From f23437910187177bc2a6ea317929ffe8beff1408 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 15:11:32 +0900 Subject: [PATCH 26/56] [google_chat_ros]remove unnecessary files --- google_chat_ros/scripts/google_pubsub.py | 33 ------------------------ 1 file changed, 33 deletions(-) delete mode 100644 google_chat_ros/scripts/google_pubsub.py diff --git a/google_chat_ros/scripts/google_pubsub.py b/google_chat_ros/scripts/google_pubsub.py deleted file mode 100644 index ac7d7b062..000000000 --- a/google_chat_ros/scripts/google_pubsub.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# TODO this is a example code, should be removed - -from concurrent.futures import TimeoutError -from google.cloud import pubsub_v1 - -project_id = "fetch-kiedno" -subscription_id = "chat-sub" -timeout = 60. - -subscriber = pubsub_v1.SubscriberClient() -# The `subscription_path` method creates a fully qualified identifier -# in the form `projects/{project_id}/subscriptions/{subscription_id}` -subscription_path = subscriber.subscription_path(project_id, subscription_id) - -def callback(message: pubsub_v1.subscriber.message.Message) -> None: - print(f"Received {message}.") - print(message.data) - message.ack() - -streaming_pull_future = subscriber.subscribe(subscription_path, callback=callback) -print(f"Listening for messages on {subscription_path}..\n") - -# Wrap subscriber in a 'with' block to automatically call close() when done. -with subscriber: - try: - # When `timeout` is not set, result() will block indefinitely, - # unless an exception is encountered first. - streaming_pull_future.result(timeout=timeout) - except TimeoutError: - streaming_pull_future.cancel() # Trigger the shutdown. - streaming_pull_future.result() # Block until the shutdown is complete. From 89b1178f3cddf55394cc1ab11ac7c67c23915aca Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 17:13:47 +0900 Subject: [PATCH 27/56] [google_chat_ros] Drop python2.x support, remove unnecessary msgs --- google_chat_ros/CMakeLists.txt | 2 -- google_chat_ros/package.xml | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index d595ad152..9b054628f 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -5,7 +5,6 @@ find_package( catkin REQUIRED COMPONENTS rospy actionlib_msgs - sensor_msgs std_msgs message_generation ) @@ -23,7 +22,6 @@ add_action_files( generate_messages( DEPENDENCIES - sensor_msgs std_msgs actionlib_msgs ) diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index 4af341528..09652395c 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -10,25 +10,24 @@ BSD catkin - python-setuptools - python3-setuptools + python3-setuptools message_generation - python-googleapi - python3-googleapi message_runtime - python-httplib2 - python-oauth2client - python-flask - python-gdown rospy - sensor_msgs std_msgs - dialogflow_task_executive - dialogflow_webhook_ros gdrive_ros + python3-gdown-pip + python3-googleapi + python3-google-auth-pip + python3-google-auth-httplib2-pip + python3-google-auth-oauthlib-pip + python3-google-cloud-pubsub-pip + python3-httplib2 + python3-oauth2client + From e22cd8e98f62ed7cba2ac1a8950f234ccbbe7f03 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 3 May 2022 17:28:25 +0900 Subject: [PATCH 28/56] [google_chat_ros] add rostest for python importing --- google_chat_ros/CMakeLists.txt | 11 ++++++++++ google_chat_ros/test/import.test | 5 +++++ google_chat_ros/test/test_import.py | 31 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 google_chat_ros/test/import.test create mode 100755 google_chat_ros/test/test_import.py diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 9b054628f..9cc559b3a 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -7,6 +7,7 @@ find_package( actionlib_msgs std_msgs message_generation + rostest ) catkin_python_setup() @@ -30,6 +31,16 @@ catkin_package() include_directories() +# import test +file(GLOB TEST_SCRIPT_FILES test/*.py) +if(CATKIN_ENABLE_TESTING) + catkin_install_python( + PROGRAMS ${TEST_SCRIPT_FILES} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} + ) + add_rostest(test/import.test) +endif() + # install # euslisp file(GLOB EUSLISP_SCRIPTS scripts/*.l) diff --git a/google_chat_ros/test/import.test b/google_chat_ros/test/import.test new file mode 100644 index 000000000..32ac4c023 --- /dev/null +++ b/google_chat_ros/test/import.test @@ -0,0 +1,5 @@ + + + diff --git a/google_chat_ros/test/test_import.py b/google_chat_ros/test/test_import.py new file mode 100755 index 000000000..911679d44 --- /dev/null +++ b/google_chat_ros/test/test_import.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import unittest + +PKG = 'google_chat_ros' +NAME = 'test_import' + +class TestBlock(unittest.TestCase): + def __init__(self, *args): + super(TestBlock, self).__init__(*args) + + def test_import(self): + try: + from apiclient.discovery import build + import base64 + from concurrent.futures import TimeoutError + import gdown + from google.cloud import pubsub_v1 + from google.oauth2.service_account import Credentials + from httplib2 import Http + import http.server as s + import json + from oauth2client.service_account import ServiceAccountCredentials + import requests + import socket + import ssl + except Exception as e: + assert False + +if __name__ == "__main__": + import rostest + rostest.rosrun(PKG, NAME, TestBlock) From 32d0610bb622008920b154f881ed5071e1caac13 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 17 May 2022 10:13:16 +0900 Subject: [PATCH 29/56] [google_chat_ros]rename rosparam, function names related to dialogflow --- google_chat_ros/launch/google_chat.launch | 8 ++++---- google_chat_ros/scripts/helper.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 7cd6adf23..72ce564ef 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -21,8 +21,8 @@ - - + + @@ -48,8 +48,8 @@ - to_dialogflow_task_executive: $(arg to_dialogflow_task_executive) - sound_play_jp: $(arg sound_play_jp) + to_dialogflow_client: $(arg to_dialogflow_client) + debug_sound: $(arg debug_sound) diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index e9543134d..1f1b54c5e 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -16,8 +16,8 @@ class GoogleChatROSHelper(object): """ def __init__(self): # Get configuration params - self.to_dialogflow_task_executive = rospy.get_param("~to_dialogflow_task_executive") - self.sound_play_jp = rospy.get_param("~sound_play_jp") + self.to_dialogflow_task_executive = rospy.get_param("~to_dialogflow_client") + self.sound_play_jp = rospy.get_param("~debug_sound") self._message_sub = rospy.Subscriber("google_chat_ros/message_activity", MessageEvent, callback=self._message_cb) self.recent_message_event = None @@ -29,11 +29,11 @@ def send_chat_client(self, goal): client.wait_for_result() return client.get_result() - def dialogflow_task_exec_client(self, query): + def dialogflow_action_client(self, query): """ :rtype: TextResult """ - client = actionlib.SimpleActionClient('dialogflow_client/text_request', TextAction) + client = actionlib.SimpleActionClient('dialogflow_client/text', DialogTextAction) client.wait_for_server() goal = TextGoal() goal.query = query @@ -62,7 +62,7 @@ def _message_cb(self, data): chat_goal = SendMessageGoal() chat_goal.space = space.replace('spaces/', '') chat_goal.thread_name = thread_name - dialogflow_res = self.dialogflow_task_exec_client(text) + dialogflow_res = self.dialogflow_action_client(text) content = "<{}> {}".format(sender_id, dialogflow_res.response.response) chat_goal.text = content self.send_chat_client(chat_goal) From a8d88f874187cc9244846ce5c0c68428a3b70c33 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 17 May 2022 11:14:16 +0900 Subject: [PATCH 30/56] [google_chat_ros] handling shutting down httpd --- google_chat_ros/src/google_chat_ros/google_chat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 470a000b0..7206ac5ce 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -55,6 +55,7 @@ def __init__(self, host, port, certfile, keyfile, callback, user_agent): self._keyfile = keyfile self._callback = callback self.user_agent = user_agent + self.__RUN = True def __handler(self, *args): try: @@ -68,10 +69,12 @@ def __handler(self, *args): def run(self): self._httpd = s.HTTPServer((self._host, self._port), self.__handler) self._httpd.socket = ssl.wrap_socket(self._httpd.socket, certfile=self._certfile, keyfile=self._keyfile) - self._httpd.serve_forever() + while self.__RUN: + self._httpd.handle_request() def kill(self): - self._httpd.shutdown() + self.__RUN = False + self._httpd.server_close() class GoogleChatHTTPSHandler(s.BaseHTTPRequestHandler): """The handler for https request from Google chat API. Mainly used for recieving messages, events. From c68a5d9276b79692275d13bae6f4003e1c428d9a Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 17 May 2022 12:34:41 +0900 Subject: [PATCH 31/56] [google_chat_ros]fix action names, space name in helper --- google_chat_ros/scripts/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index 1f1b54c5e..ef36f68fd 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -33,7 +33,7 @@ def dialogflow_action_client(self, query): """ :rtype: TextResult """ - client = actionlib.SimpleActionClient('dialogflow_client/text', DialogTextAction) + client = actionlib.SimpleActionClient('dialogflow_client/text_action', DialogTextAction) client.wait_for_server() goal = TextGoal() goal.query = query @@ -60,7 +60,7 @@ def _message_cb(self, data): text = data.message.argument_text if self.to_dialogflow_task_executive: chat_goal = SendMessageGoal() - chat_goal.space = space.replace('spaces/', '') + chat_goal.space = space chat_goal.thread_name = thread_name dialogflow_res = self.dialogflow_action_client(text) content = "<{}> {}".format(sender_id, dialogflow_res.response.response) From 16946929da5a3f105abfb38cd6e02caf886c2777 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 18 May 2022 13:02:00 +0900 Subject: [PATCH 32/56] [google_chat_ros] fix euslisp function --- google_chat_ros/scripts/google-chat.l | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/google_chat_ros/scripts/google-chat.l b/google_chat_ros/scripts/google-chat.l index 992dc8253..db62e1675 100755 --- a/google_chat_ros/scripts/google-chat.l +++ b/google_chat_ros/scripts/google-chat.l @@ -3,20 +3,21 @@ (ros::load-ros-manifest "google_chat_ros") (ros::roseus "google_chat_eus_client") -(defun send-google-chat-message (space content &key (message-type "text") (topic-name "google_chat_ros/send") (wait nil)) - (when (boundp 'google_chat_ros::SendMessageAction) - (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) - (ac (instance ros::simple-action-client :init - topic-name google_chat_ros::SendMessageAction))) - (when (send ac :wait-for-server 1) - (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) - (send ac :cancel-goal) - (send ac :wait-for-result :timeout 5)) - (send goal :goal :space space) - (send goal :goal :message_type message-type) - (send goal :goal :content content) - (send ac :send-goal goal) - (if wait - (return-from send-google-chat-message (send ac :wait-for-result :timeout 5)) - (return-from send-google-chat-message t))))) -) +(defun send-google-chat-text (space content + &key (thread-name nil) (topic-name "google_chat_ros/send") (wait t)) + (wait (boundp 'google_chat_ros::SendMessageAction) + (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) + (ac (instance ros::simple-action-client :init + topic-name google_chat_ros::SendMessageAction))) + (when (send ac :wait-for-server 1) + (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) + (send ac :cancel-goal) + (send ac :wait-for-result :timeout 5)) + (send goal :goal :space space) + (when thread-name + (send goal :goal :thread_name thread-name)) + (send goal :goal :text content) + (send ac :send-goal goal) + (if wait + (return-from send-google-chat-text (send ac :wait-for-result :timeout 5)) + (return-from send-google-chat-text t)))))) From ce8fbf4dd6d839144969ffb0fdd7aa94b223adcf Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sat, 21 May 2022 15:29:47 +0900 Subject: [PATCH 33/56] [google_chat_ros] update eus functions --- google_chat_ros/scripts/google-chat.l | 79 ++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/google_chat_ros/scripts/google-chat.l b/google_chat_ros/scripts/google-chat.l index db62e1675..f146737e5 100755 --- a/google_chat_ros/scripts/google-chat.l +++ b/google_chat_ros/scripts/google-chat.l @@ -4,11 +4,11 @@ (ros::roseus "google_chat_eus_client") (defun send-google-chat-text (space content - &key (thread-name nil) (topic-name "google_chat_ros/send") (wait t)) + &key (thread-name nil) (action-goal-name "google_chat_ros/send") (wait t)) (wait (boundp 'google_chat_ros::SendMessageAction) (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) (ac (instance ros::simple-action-client :init - topic-name google_chat_ros::SendMessageAction))) + action-goal-name google_chat_ros::SendMessageAction))) (when (send ac :wait-for-server 1) (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) (send ac :cancel-goal) @@ -21,3 +21,78 @@ (if wait (return-from send-google-chat-text (send ac :wait-for-result :timeout 5)) (return-from send-google-chat-text t)))))) + +(defun send-google-chat-image + (space image-path + &key (image-header "") (thread-name nil) + (action-goal-name "google_chat_ros/send") (wait t)) + (when (boundp 'google_chat_ros::SendMessageAction) + (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) + (ac (instance ros::simple-action-client :init + action-goal-name google_chat_ros::SendMessageAction)) + (card (instance google_chat_ros::Card :init)) + (section (instance google_chat_ros::Section :init)) + (widget (instance google_chat_ros::WidgetMarkup :init)) + (image (instance google_chat_ros::Image :init))) + (when (send ac :wait-for-server 1) + (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) + (send ac :cancel-goal) + (send ac :wait-for-result :timeout 5)) + (send image :localpath image-path) + (send widget :image image) + (send section :widgets (list widget)) + (send section :header image-header) + (send card :sections (list section)) + (send goal :goal :cards (list card)) + (send goal :goal :space space) + (when thread-name + (send goal :goal :thread_name thread-name)) + (send ac :send-goal goal) + (if wait + (return-from send-google-chat-image (send ac :wait-for-result :timeout 5)) + (return-from send-google-chat-image t)))))) + +(defun create-google-chat-button + (button-name button-action-name + &key (button-action-key) (button-action-value)) + (let ((button (instance google_chat_ros::Button :init)) + (text-button-on-click (instance google_chat_ros::OnClick :init)) + (action (instance google_chat_ros::FormAction :init)) + (parameter (instance google_chat_ros::ActionParameter :init))) + (send button :text_button_name button-name) + (send action :action_method_name button-action-name) + (send parameter :key button-action-key) + (send parameter :value button-action-value) + (send action :parameters (list parameter)) + (send text-button-on-click :action action) + (send button :text_button_on_click text-button-on-click) + button)) + +(defun send-google-chat-buttons + ;; buttons should be list + (space buttons + &key (buttons-header "") (thread-name nil) + (action-goal-name "google_chat_ros/send") (wait nil)) + (when (boundp 'google_chat_ros::SendMessageAction) + (let ((goal (instance google_chat_ros::SendMessageActionGoal :init)) + (ac (instance ros::simple-action-client :init + action-goal-name google_chat_ros::SendMessageAction)) + (card (instance google_chat_ros::Card :init)) + (section (instance google_chat_ros::Section :init)) + (widget (instance google_chat_ros::WidgetMarkup :init))) + (when (send ac :wait-for-server 1) + (when (eq (send ac :get-state) actionlib_msgs::GoalStatus::*active*) + (send ac :cancel-goal) + (send ac :wait-for-result :timeout 5)) + (send widget :buttons buttons) + (send section :widgets (list widget)) + (send section :header buttons-header) + (send card :sections (list section)) + (send goal :goal :cards (list card)) + (send goal :goal :space space) + (when thread-name + (send goal :goal :thread_name thread-name)) + (send ac :send-goal goal) + (if wait + (return-from send-google-chat-buttons (send ac :wait-for-result :timeout 5)) + (return-from send-google-chat-buttons t)))))) From 78dc1c79292e1eb79a7bc9cb42534bb7dae134e1 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sat, 21 May 2022 15:13:44 +0900 Subject: [PATCH 34/56] [google_chat_ros] fix pubsub bug, killnode not defined --- google_chat_ros/scripts/google_chat_ros_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py index 5c5e06d5e..4e5dc5afa 100755 --- a/google_chat_ros/scripts/google_chat_ros_node.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -78,9 +78,9 @@ def __init__(self): rospy.loginfo("Expected to use Google Cloud Pub Sub service") self.project_id = rospy.get_param("~project_id") self.subscription_id = rospy.get_param("~subscription_id") - rospy.on_shutdown(self.killpubsub) self._pubsub_client = GoogleChatPubSubClient( self.project_id, self.subscription_id, self.event_cb, google_credentials) + rospy.on_shutdown(self.killpubsub) self._pubsub_client.run() elif recieving_chat_mode == "none": From e4adc5ae04052125c7b07985cd9c3ef72e4c267d Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 8 Jun 2022 19:06:53 +0900 Subject: [PATCH 35/56] [google_chat_ros] drop dialogflow mode --- google_chat_ros/scripts/google_chat_ros_node.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py index 4e5dc5afa..05fa36c6f 100755 --- a/google_chat_ros/scripts/google_chat_ros_node.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -10,7 +10,6 @@ import actionlib import ast from dialogflow_task_executive.msg import DialogResponse -from dialogflow_webhook_ros.msg import OriginalDetectIntentRequest from gdrive_ros.srv import * from google_chat_ros.google_chat import GoogleChatRESTClient from google_chat_ros.google_chat import GoogleChatHTTPSServer @@ -24,7 +23,7 @@ class GoogleChatROS(object): Send request to Google Chat REST API via ROS """ def __init__(self): - recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'dialogflow', 'url', 'pubsub', 'none' + recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'url', 'pubsub', 'none' self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service') google_credentials = rospy.get_param('~google_cloud_credentials_json') @@ -71,9 +70,6 @@ def __init__(self): except ConnectionError as e: rospy.logwarn("The error occurred while starting HTTPS server") rospy.logerr(e) - elif recieving_chat_mode == "dialogflow": - rospy.loginfo("Expected to get OriginalDetectIntentRequest.msg from dialogflow webhook ros node.") - self._sub = rospy.Subscriber("dialogflow_original_application_request", OriginalDetectIntentRequest, self.dialogflow_cb) elif recieving_chat_mode == "pubsub": rospy.loginfo("Expected to use Google Cloud Pub Sub service") self.project_id = rospy.get_param("~project_id") From 735decc33620ea0cc8158e242366b7c0738db334 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 8 Jun 2022 19:20:49 +0900 Subject: [PATCH 36/56] [google_chat_ros] fix action goal instance --- google_chat_ros/scripts/helper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py index ef36f68fd..4b5a33247 100755 --- a/google_chat_ros/scripts/helper.py +++ b/google_chat_ros/scripts/helper.py @@ -31,13 +31,13 @@ def send_chat_client(self, goal): def dialogflow_action_client(self, query): """ - :rtype: TextResult + :rtype: DialogTextActionResult """ client = actionlib.SimpleActionClient('dialogflow_client/text_action', DialogTextAction) client.wait_for_server() - goal = TextGoal() - goal.query = query - client.send_goal(goal) + goal = DialogTextActionGoal() + goal.goal.query = query + client.send_goal(goal.goal) client.wait_for_result() return client.get_result() From f7ff81e0b44c2cd7eebdadb9ef24b7329906db28 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 8 Jun 2022 22:51:12 +0900 Subject: [PATCH 37/56] [google_chat_ros]recieving -> receiving --- google_chat_ros/README.md | 16 ++++++++-------- google_chat_ros/launch/google_chat.launch | 4 ++-- google_chat_ros/msg/MessageEvent.msg | 2 +- google_chat_ros/scripts/google_chat_ros_node.py | 14 +++++++------- .../src/google_chat_ros/google_chat.py | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/google_chat_ros/README.md b/google_chat_ros/README.md index d4c4c2a0e..e911ef41b 100644 --- a/google_chat_ros/README.md +++ b/google_chat_ros/README.md @@ -2,7 +2,7 @@ The ROS wrapper for Google Chat API 1. [Installation Guide](#install) 1. [Sending the message](#send) -1. [Recieving the message](#recieve) +1. [Receiving the message](#recieve) 1. [Handling the event](#event) 1. [Optional functions](#optional) 1. [Helper nodes](#helper) @@ -69,12 +69,12 @@ catkin build ``` ### 1.4 Launch the node #### HTTPS mode -You have to set rosparams `recieving_mode=https`, `google_cloud_credentials_json`, `host`, `port`, `ssl_certfile`, `ssl_keyfile`. +You have to set rosparams `receiving_mode=https`, `google_cloud_credentials_json`, `host`, `port`, `ssl_certfile`, `ssl_keyfile`. #### Pub/Sub mode -You have to set rosparams `recieving_mode=pubsub`, `google_cloud_credentials_json`, `project_id`, `subscription_id`. `subscription_id` would be `chat-sub` if you follow [Pub/Sub mode](#pubsub) example. +You have to set rosparams `receiving_mode=pubsub`, `google_cloud_credentials_json`, `project_id`, `subscription_id`. `subscription_id` would be `chat-sub` if you follow [Pub/Sub mode](#pubsub) example. ##### Example ```bash -roslaunch google_chat_ros google_chat.launch recieving_mode:=pubsub google_cloud_credentials_json:=/path/to/-XXXXXXXX.json project_id:= subscription_id:=chat-sub +roslaunch google_chat_ros google_chat.launch receiving_mode:=pubsub google_cloud_credentials_json:=/path/to/-XXXXXXXX.json project_id:= subscription_id:=chat-sub ``` ## 2. Sending the message @@ -160,13 +160,13 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal See [Here](#image). -## 3. Recieving the messages +## 3. Receiving the messages ### 3.1 ROS Topic When the bot was mentioned, the node publishes `~message_activity` topic. ### 3.2 Examples -#### Recieving a text message +#### Receiving a text message ```yaml event_time: "2022-04-28T06:25:26.884623Z" space: @@ -200,7 +200,7 @@ user: human: True ``` -#### Recieving a message with an image or gdrive file and download it +#### Receiving a message with an image or gdrive file and download it @@ -297,5 +297,5 @@ rostopic pub -1 /google_chat_ros/send/goal google_chat_ros/SendMessageActionGoal localpath: '/home/user/Pictures/image.png' space: 'spaces/' ``` -### 5.2 Recieving a message with images or gdrive file +### 5.2 Receiving a message with images or gdrive file You have to set rosparam `~download_data` True, `~download_directory`. If the node recieved the message with image or google drive file, it automatically downloads to `~donwload_directory` path. diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 72ce564ef..39c589796 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -1,5 +1,5 @@ - + @@ -27,7 +27,7 @@ - recieving_mode: $(arg recieving_mode) + receiving_mode: $(arg receiving_mode) gdrive_upload_service: $(arg gdrive_upload_service) upload_data_timeout: $(arg upload_data_timeout) download_data_timeout: $(arg download_data_timeout) diff --git a/google_chat_ros/msg/MessageEvent.msg b/google_chat_ros/msg/MessageEvent.msg index 12d9153f0..b50f7454f 100644 --- a/google_chat_ros/msg/MessageEvent.msg +++ b/google_chat_ros/msg/MessageEvent.msg @@ -1,4 +1,4 @@ -# ROS message for recieving Google Chat message +# ROS message for receiving Google Chat message string event_time google_chat_ros/Space space google_chat_ros/Message message diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py index 05fa36c6f..8f98eafd6 100755 --- a/google_chat_ros/scripts/google_chat_ros_node.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -23,7 +23,7 @@ class GoogleChatROS(object): Send request to Google Chat REST API via ROS """ def __init__(self): - recieving_chat_mode = rospy.get_param('~recieving_mode') # select from 'url', 'pubsub', 'none' + receiving_chat_mode = rospy.get_param('~receiving_mode') # select from 'url', 'pubsub', 'none' self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service') google_credentials = rospy.get_param('~google_cloud_credentials_json') @@ -43,8 +43,8 @@ def __init__(self): rospy.logwarn("Failed to start Google Chat REST service") rospy.logerr(e) - # For recieving message - if recieving_chat_mode in ("url", "dialogflow", "pubsub"): + # For receiving message + if receiving_chat_mode in ("url", "dialogflow", "pubsub"): # rosparams self.upload_data_timeout = rospy.get_param('~upload_data_timeout') self.download_data = rospy.get_param('~download_data') @@ -55,7 +55,7 @@ def __init__(self): self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) self._card_activity_pub = rospy.Publisher("~card_activity", CardEvent, queue_size=1) - if recieving_chat_mode == "url": + if receiving_chat_mode == "url": rospy.loginfo("Expected to get Google Chat Bot URL request") rospy.loginfo("Starting Google Chat HTTPS server...") self.host = rospy.get_param('~host') @@ -70,7 +70,7 @@ def __init__(self): except ConnectionError as e: rospy.logwarn("The error occurred while starting HTTPS server") rospy.logerr(e) - elif recieving_chat_mode == "pubsub": + elif receiving_chat_mode == "pubsub": rospy.loginfo("Expected to use Google Cloud Pub Sub service") self.project_id = rospy.get_param("~project_id") self.subscription_id = rospy.get_param("~subscription_id") @@ -79,11 +79,11 @@ def __init__(self): rospy.on_shutdown(self.killpubsub) self._pubsub_client.run() - elif recieving_chat_mode == "none": + elif receiving_chat_mode == "none": rospy.logwarn("You cannot recieve Google Chat event because HTTPS server or Google Cloud Pub/Sub is not running.") else: - rospy.logerr("Please choose recieving_mode param from dialogflow, https, pubsub, none.") + rospy.logerr("Please choose receiving_mode param from dialogflow, https, pubsub, none.") def killhttpd(self): self._server.kill() diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 7206ac5ce..5ce613f80 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -77,7 +77,7 @@ def kill(self): self._httpd.server_close() class GoogleChatHTTPSHandler(s.BaseHTTPRequestHandler): - """The handler for https request from Google chat API. Mainly used for recieving messages, events. + """The handler for https request from Google chat API. Mainly used for receiving messages, events. """ def __init__(self, callback, user_agent, *args): self._callback = callback From 83dcf0a6fc27fa3753235c38adf2cc63383ef9ec Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 15 Jun 2022 23:37:38 +0900 Subject: [PATCH 38/56] [google_chat_ros] use catkin_virtualenv --- google_chat_ros/CMakeLists.txt | 35 +++++++++++++------ google_chat_ros/launch/google_chat.launch | 2 +- google_chat_ros/package.xml | 11 ++---- google_chat_ros/requirements.txt | 8 +++++ .../scripts/google_chat_ros_node.py | 0 google_chat_ros/scripts/helper.py | 0 google_chat_ros/test/test_import.py | 0 7 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 google_chat_ros/requirements.txt mode change 100755 => 100644 google_chat_ros/scripts/google_chat_ros_node.py mode change 100755 => 100644 google_chat_ros/scripts/helper.py mode change 100755 => 100644 google_chat_ros/test/test_import.py diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 9cc559b3a..a9d32ef0e 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -3,42 +3,53 @@ project(google_chat_ros) find_package( catkin REQUIRED COMPONENTS + catkin_virtualenv REQUIRED rospy actionlib_msgs std_msgs message_generation rostest -) + ) catkin_python_setup() add_message_files( DIRECTORY msg -) + ) add_action_files( FILES SendMessage.action -) + ) generate_messages( DEPENDENCIES std_msgs actionlib_msgs -) + ) catkin_package() include_directories() +# generate the virtualenv +catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + USE_SYSTEM_PACKAGES FALSE + EXTRA_PIP_ARGS + -vvv + ) + # import test file(GLOB TEST_SCRIPT_FILES test/*.py) if(CATKIN_ENABLE_TESTING) catkin_install_python( PROGRAMS ${TEST_SCRIPT_FILES} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} - ) - add_rostest(test/import.test) + ) + add_rostest(test/import.test + DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv + ) endif() # install @@ -46,16 +57,20 @@ endif() file(GLOB EUSLISP_SCRIPTS scripts/*.l) install(FILES ${EUSLISP_SCRIPTS} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -) + ) + # python file(GLOB PYTHON_SCRIPTS scripts/*.py) catkin_install_python( PROGRAMS ${PYTHON_SCRIPTS} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -) + ) +# python requirements +install(FILES requirements.txt + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} + ) # launch install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -) - + ) diff --git a/google_chat_ros/launch/google_chat.launch b/google_chat_ros/launch/google_chat.launch index 39c589796..8ef7a56ac 100644 --- a/google_chat_ros/launch/google_chat.launch +++ b/google_chat_ros/launch/google_chat.launch @@ -7,7 +7,7 @@ - + diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index 09652395c..82cbd1fa0 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -13,21 +13,14 @@ python3-setuptools message_generation + catkin_virtualenv message_runtime rospy std_msgs gdrive_ros - python3-gdown-pip - python3-googleapi - python3-google-auth-pip - python3-google-auth-httplib2-pip - python3-google-auth-oauthlib-pip - python3-google-cloud-pubsub-pip - python3-httplib2 - python3-oauth2client - + requirements.txt diff --git a/google_chat_ros/requirements.txt b/google_chat_ros/requirements.txt new file mode 100644 index 000000000..53f43bf6a --- /dev/null +++ b/google_chat_ros/requirements.txt @@ -0,0 +1,8 @@ +gdown==4.4.0 +google-api-python-client==2.49.0 +google-auth==2.6.6 +google-auth-httplib2==0.1.0 +google-auth-oauthlib==0.5.1 +google-cloud-pubsub==2.12.1 +httplib2==0.20.4 +oauth2client==4.1.3 diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py old mode 100755 new mode 100644 diff --git a/google_chat_ros/scripts/helper.py b/google_chat_ros/scripts/helper.py old mode 100755 new mode 100644 diff --git a/google_chat_ros/test/test_import.py b/google_chat_ros/test/test_import.py old mode 100755 new mode 100644 From 883ce76bad2789de7134a4531752ef7225057bbf Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata <27789460+mqcmd196@users.noreply.github.com> Date: Thu, 16 Jun 2022 03:05:48 +0900 Subject: [PATCH 39/56] [google_chat_ros] docs:granting pubslish rights to pub/sub topic --- google_chat_ros/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/google_chat_ros/README.md b/google_chat_ros/README.md index e911ef41b..815bf4116 100644 --- a/google_chat_ros/README.md +++ b/google_chat_ros/README.md @@ -51,7 +51,15 @@ In the figure, we set the topic name `chat`, the subscription name `chat-sub` as Note that if you set the topic name `chat`, the full name of it becomes `projects//topics/chat`. Please confirm the subsciptions subscribes the full name not short one. -##### 3. Set Google Chat API Connection settings +##### 3. Grant publish rigts on your topic +In order for Google Chat to publish messages to your topic, it must have publishing rights to the topic. To grant Google Chat these permissions, assign the Pub/Sub Publisher role to the following service account +``` +chat-api-push@system.gserviceaccount.com +``` +![google_chat_pubsub_permission](https://user-images.githubusercontent.com/27789460/173894738-cc169b21-0873-4def-9179-f686a2ae68ec.png) + + +##### 4. Set Google Chat API Connection settings Please choose `Cloud Pub/Sub` as connection settings and fill the full topic name in the Topic Name form. ![google_chat_pubsub](https://user-images.githubusercontent.com/27789460/166408478-b662b73c-35a8-43e8-aaaa-93efdd48e486.png) From 2e4fe2f6595603c2544eeda1e56919f16828d2e2 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 16 Jun 2022 03:08:01 +0900 Subject: [PATCH 40/56] [google_chat_ros]add all requirements, fix pubsub client for handling invalid type pubsub message --- google_chat_ros/package.xml | 1 + google_chat_ros/requirements.txt | 34 +++++++++++++++++-- .../src/google_chat_ros/google_chat.py | 12 +++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index 82cbd1fa0..a043f4155 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -19,6 +19,7 @@ rospy std_msgs gdrive_ros + dialogflow_task_executive requirements.txt diff --git a/google_chat_ros/requirements.txt b/google_chat_ros/requirements.txt index 53f43bf6a..e11fffb78 100644 --- a/google_chat_ros/requirements.txt +++ b/google_chat_ros/requirements.txt @@ -1,8 +1,36 @@ +beautifulsoup4==4.11.1 +cachetools==4.2.4 +certifi==2022.5.18.1 +charset-normalizer==2.0.12 +filelock==3.4.1 gdown==4.4.0 -google-api-python-client==2.49.0 -google-auth==2.6.6 +google-api-core[grpc]==2.8.2 +google-api-python-client==2.51.0 google-auth-httplib2==0.1.0 -google-auth-oauthlib==0.5.1 +google-auth-oauthlib==0.5.2 +google-auth==2.8.0 google-cloud-pubsub==2.12.1 +googleapis-common-protos[grpc]==1.56.2 +grpc-google-iam-v1==0.12.4 +grpcio-status==1.46.3 +grpcio==1.46.3 httplib2==0.20.4 +idna==3.3 +importlib-resources==5.4.0 oauth2client==4.1.3 +oauthlib==3.2.0 +proto-plus==1.20.6 +protobuf==3.19.4 +pyasn1-modules==0.2.8 +pyasn1==0.4.8 +pyparsing==3.0.9 +pysocks==1.7.1 +requests-oauthlib==1.3.1 +requests[socks]==2.27.1 +rsa==4.8 +six==1.16.0 +soupsieve==2.3.2.post1 +tqdm==4.64.0 +uritemplate==4.1.1 +urllib3==1.26.9 +zipp==3.6.0 diff --git a/google_chat_ros/src/google_chat_ros/google_chat.py b/google_chat_ros/src/google_chat_ros/google_chat.py index 5ce613f80..f704bf67d 100644 --- a/google_chat_ros/src/google_chat_ros/google_chat.py +++ b/google_chat_ros/src/google_chat_ros/google_chat.py @@ -119,9 +119,15 @@ def __init__(self, project_id, subscription_id, callback, keyfile): def _pubsub_cb(self, message): rospy.logdebug("Recieved {message}") rospy.logdebug(message.data) - json_content = json.loads(message.data) - self._callback(json_content) - message.ack() + try: + json_content = json.loads(message.data) + self._callback(json_content) + except Exception as e: + rospy.logerr("Failed to handle the request from Cloud PubSub.") + rospy.logerr("It might be caused because of invalid type message from GCP") + rospy.logerr("{}".str(e)) + finally: + message.ack() def run(self): with self._sub: From c23be9022327bdf1c2feb03c93e3246095b771f5 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Thu, 16 Jun 2022 04:05:00 +0900 Subject: [PATCH 41/56] [dialogflow_task_executive] fix launch args in dialogflow_ros.launch --- dialogflow_task_executive/launch/dialogflow_ros.launch | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dialogflow_task_executive/launch/dialogflow_ros.launch b/dialogflow_task_executive/launch/dialogflow_ros.launch index 4283d6ae7..3dc2a4d63 100644 --- a/dialogflow_task_executive/launch/dialogflow_ros.launch +++ b/dialogflow_task_executive/launch/dialogflow_ros.launch @@ -1,11 +1,13 @@ - + + + Date: Thu, 16 Jun 2022 12:50:17 +0900 Subject: [PATCH 42/56] [dialogflow_task_executive] add always publish result param --- dialogflow_task_executive/launch/dialogflow_ros.launch | 2 ++ .../launch/dialogflow_task_executive.launch | 2 ++ .../node_scripts/dialogflow_client.py | 9 ++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dialogflow_task_executive/launch/dialogflow_ros.launch b/dialogflow_task_executive/launch/dialogflow_ros.launch index 3dc2a4d63..8b604a557 100644 --- a/dialogflow_task_executive/launch/dialogflow_ros.launch +++ b/dialogflow_task_executive/launch/dialogflow_ros.launch @@ -8,6 +8,7 @@ + diff --git a/dialogflow_task_executive/launch/dialogflow_task_executive.launch b/dialogflow_task_executive/launch/dialogflow_task_executive.launch index 6f7e41af5..bf3436cbe 100644 --- a/dialogflow_task_executive/launch/dialogflow_task_executive.launch +++ b/dialogflow_task_executive/launch/dialogflow_task_executive.launch @@ -4,6 +4,7 @@ + @@ -32,6 +33,7 @@ project_id: $(arg project_id) google_cloud_credentials_json: $(arg credential) enable_hotword: $(arg enable_hotword) + always_publish_result: $(arg always_publish_result) diff --git a/dialogflow_task_executive/node_scripts/dialogflow_client.py b/dialogflow_task_executive/node_scripts/dialogflow_client.py index 85d6b39d0..106a08e5e 100644 --- a/dialogflow_task_executive/node_scripts/dialogflow_client.py +++ b/dialogflow_task_executive/node_scripts/dialogflow_client.py @@ -83,6 +83,10 @@ def __init__(self): ) if self.project_id is None: rospy.logerr('project ID is not set') + self.pub_res = rospy.Publisher( + "dialog_response", DialogResponse, queue_size=1) + self.always_publish_result = rospy.get_param( + "~always_publish_result", True) def detect_intent_text(self, data, session): query = df.types.QueryInput( @@ -143,6 +147,8 @@ def cb(self, goal): self._as.publish_feedback(feedback) result.done = success self._as.set_succeeded(result) + if df_result and self.always_publish_result: + self.pub_res.publish(result.response) class DialogflowAudioClient(DialogflowBase): @@ -185,9 +191,6 @@ def __init__(self): else: self.sound_action = None - self.pub_res = rospy.Publisher( - "dialog_response", DialogResponse, queue_size=1) - if self.use_audio: self.audio_config = df.types.InputAudioConfig( audio_encoding=df.enums.AudioEncoding.AUDIO_ENCODING_LINEAR_16, From 160feb65db00e9c2059c662b61fdd2a7bbe4a675 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Mon, 4 Jul 2022 10:56:16 +0900 Subject: [PATCH 43/56] [google_chat_ros] missing test name --- google_chat_ros/CMakeLists.txt | 3 +-- google_chat_ros/test/import.test | 1 + google_chat_ros/test/test_import.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index a9d32ef0e..300d8e27e 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -41,10 +41,9 @@ catkin_generate_virtualenv( ) # import test -file(GLOB TEST_SCRIPT_FILES test/*.py) if(CATKIN_ENABLE_TESTING) catkin_install_python( - PROGRAMS ${TEST_SCRIPT_FILES} + PROGRAMS test/test_import.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} ) add_rostest(test/import.test diff --git a/google_chat_ros/test/import.test b/google_chat_ros/test/import.test index 32ac4c023..4715913fe 100644 --- a/google_chat_ros/test/import.test +++ b/google_chat_ros/test/import.test @@ -1,5 +1,6 @@ diff --git a/google_chat_ros/test/test_import.py b/google_chat_ros/test/test_import.py index 911679d44..4f718ee68 100644 --- a/google_chat_ros/test/test_import.py +++ b/google_chat_ros/test/test_import.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import unittest PKG = 'google_chat_ros' From dadfe4fbe76086c469e0294061f52d9a978c6bc3 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 27 Jul 2022 00:50:36 +0900 Subject: [PATCH 44/56] [dialogflow_task_executive] Enable aarch64 --- dialogflow_task_executive/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialogflow_task_executive/CMakeLists.txt b/dialogflow_task_executive/CMakeLists.txt index 7e922454f..ed65e3c0a 100644 --- a/dialogflow_task_executive/CMakeLists.txt +++ b/dialogflow_task_executive/CMakeLists.txt @@ -3,7 +3,7 @@ project(dialogflow_task_executive) execute_process(COMMAND bash -c "gcc -dumpmachine" OUTPUT_VARIABLE gcc_dump_machine OUTPUT_STRIP_TRAILING_WHITESPACE) message("-- gcc dumpmachine returns ${gcc_dump_machine}") -if(NOT gcc_dump_machine MATCHES "x86_64-.*") +if(NOT (gcc_dump_machine MATCHES "x86_64-.*" OR gcc_dump_machine MATCHES "aarch64-.*")) message(WARNING "pip -i requirements.txt work only with i686 ???") message(WARNING "`pip install grpcio` fails with third_party/boringssl-with-bazel/src/crypto/hrss/asm/poly_rq_mul.S: Assembler messages: From 003e347426b07f43bda2b5660c76cd1d40284cec Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 2 Aug 2022 14:34:17 +0900 Subject: [PATCH 45/56] fix PYTHON_INTERPRETER in catkin_generate_virtualenv with its update --- dialogflow_task_executive/CMakeLists.txt | 7 +++++-- ros_speech_recognition/CMakeLists.txt | 4 +++- sesame_ros/CMakeLists.txt | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dialogflow_task_executive/CMakeLists.txt b/dialogflow_task_executive/CMakeLists.txt index ed65e3c0a..a5ac4581c 100644 --- a/dialogflow_task_executive/CMakeLists.txt +++ b/dialogflow_task_executive/CMakeLists.txt @@ -51,9 +51,12 @@ if("$ENV{ROS_DISTRO}" STREQUAL "indigo") message(WARNING "to prevent upgrading to latest pip, which is not Python2 compatible") file(COPY requirements.txt.indigo DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(RENAME ${CMAKE_CURRENT_BINARY_DIR}/requirements.txt.indigo requirements.txt) - catkin_generate_virtualenv() + catkin_generate_virtualenv(PYTHON_INTERPRETER python2) else() - catkin_generate_virtualenv(INPUT_REQUIREMENTS requirements.in) + catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in + PYTHON_INTERPRETER python2 + ) endif() file(GLOB NODE_SCRIPTS_FILES node_scripts/*.py) diff --git a/ros_speech_recognition/CMakeLists.txt b/ros_speech_recognition/CMakeLists.txt index 086494ebb..388bee8f5 100644 --- a/ros_speech_recognition/CMakeLists.txt +++ b/ros_speech_recognition/CMakeLists.txt @@ -19,7 +19,9 @@ if($ENV{ROS_DISTRO} STREQUAL "noetic") PYTHON_INTERPRETER python3 ) else() - catkin_generate_virtualenv() + catkin_generate_virtualenv( + PYTHON_INTERPRETER python2 + ) endif() file(GLOB SCRIPT_PROGRAMS scripts/*.py) diff --git a/sesame_ros/CMakeLists.txt b/sesame_ros/CMakeLists.txt index d83cf9169..3f9de9cd9 100644 --- a/sesame_ros/CMakeLists.txt +++ b/sesame_ros/CMakeLists.txt @@ -20,7 +20,9 @@ catkin_package( CATKIN_DEPENDS message_runtime ) -catkin_generate_virtualenv() +catkin_generate_virtualenv( + PYTHON_INTERPRETER python2 + ) include_directories() From aa8c63432f4d8335b65daad635bb805fb8afaf6d Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 26 Aug 2022 12:17:54 +0900 Subject: [PATCH 46/56] [google_chat_ros] fix default rosparams --- google_chat_ros/scripts/google_chat_ros_node.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py index 8f98eafd6..2e24e8ac2 100644 --- a/google_chat_ros/scripts/google_chat_ros_node.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -23,9 +23,14 @@ class GoogleChatROS(object): Send request to Google Chat REST API via ROS """ def __init__(self): + # rosparams receiving_chat_mode = rospy.get_param('~receiving_mode') # select from 'url', 'pubsub', 'none' - self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service') google_credentials = rospy.get_param('~google_cloud_credentials_json') + self.gdrive_ros_srv = rospy.get_param('~gdrive_upload_service', "/gdrive_ros/upload") + self.upload_data_timeout = rospy.get_param('~upload_data_timeout', 30) + self.download_data = rospy.get_param('~download_data', True) + self.download_directory = rospy.get_param('~download_directory', "/tmp") + self.download_avatar = rospy.get_param('~download_avatar', False) # For sending message self._client = GoogleChatRESTClient(google_credentials) @@ -45,11 +50,6 @@ def __init__(self): # For receiving message if receiving_chat_mode in ("url", "dialogflow", "pubsub"): - # rosparams - self.upload_data_timeout = rospy.get_param('~upload_data_timeout') - self.download_data = rospy.get_param('~download_data') - self.download_directory = rospy.get_param('~download_directory') - self.download_avatar = rospy.get_param('~download_avatar') # ROS publisher self._message_activity_pub = rospy.Publisher("~message_activity", MessageEvent, queue_size=1) self._space_activity_pub = rospy.Publisher("~space_activity", SpaceEvent, queue_size=1) From dc72d0ca6f7828ff977a526a6e3a4fb0e8f89b35 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 26 Aug 2022 18:37:49 +0900 Subject: [PATCH 47/56] [google_chat_ros] remove type hint --- google_chat_ros/scripts/google_chat_ros_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google_chat_ros/scripts/google_chat_ros_node.py b/google_chat_ros/scripts/google_chat_ros_node.py index 2e24e8ac2..30a83171c 100644 --- a/google_chat_ros/scripts/google_chat_ros_node.py +++ b/google_chat_ros/scripts/google_chat_ros_node.py @@ -160,7 +160,7 @@ def rest_cb(self, goal): result.done = success self._as.set_succeeded(result) - def event_cb(self, event: dict, publish_topic=True): + def event_cb(self, event, publish_topic=True): """Parse Google Chat API json content and publish as a ROS Message. See https://developers.google.com/chat/api/reference/rest to check what contents are included in the json. From d663ce9fe3d6e3563d7a38593240209125624de2 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Sat, 27 Aug 2022 16:21:54 +0900 Subject: [PATCH 48/56] [google_chat_ros] work around test, CHECK_VENV=FALSE, remove unnecessary lines --- google_chat_ros/CMakeLists.txt | 27 +++++++++++++-------------- google_chat_ros/package.xml | 3 +++ google_chat_ros/test/import.test | 1 - google_chat_ros/test/test_import.py | 3 ++- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 300d8e27e..3cf0fa9e8 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -8,7 +8,6 @@ find_package( actionlib_msgs std_msgs message_generation - rostest ) catkin_python_setup() @@ -30,27 +29,15 @@ generate_messages( catkin_package() -include_directories() - # generate the virtualenv catkin_generate_virtualenv( PYTHON_INTERPRETER python3 USE_SYSTEM_PACKAGES FALSE + CHECK_VENV FALSE EXTRA_PIP_ARGS -vvv ) -# import test -if(CATKIN_ENABLE_TESTING) - catkin_install_python( - PROGRAMS test/test_import.py - DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} - ) - add_rostest(test/import.test - DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv - ) -endif() - # install # euslisp file(GLOB EUSLISP_SCRIPTS scripts/*.l) @@ -73,3 +60,15 @@ install(FILES requirements.txt install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} ) + +# import test +if(CATKIN_ENABLE_TESTING) + find_package(catkin REQUIRED COMPONENTS catkin_virtualenv rostest) + catkin_install_python( + PROGRAMS test/test_import.py + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} + ) + add_rostest(test/import.test + DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv + ) +endif() diff --git a/google_chat_ros/package.xml b/google_chat_ros/package.xml index a043f4155..ca7d421ed 100644 --- a/google_chat_ros/package.xml +++ b/google_chat_ros/package.xml @@ -21,6 +21,9 @@ gdrive_ros dialogflow_task_executive + catkin_virtualenv + rostest + requirements.txt diff --git a/google_chat_ros/test/import.test b/google_chat_ros/test/import.test index 4715913fe..32ac4c023 100644 --- a/google_chat_ros/test/import.test +++ b/google_chat_ros/test/import.test @@ -1,6 +1,5 @@ diff --git a/google_chat_ros/test/test_import.py b/google_chat_ros/test/test_import.py index 4f718ee68..89da88378 100644 --- a/google_chat_ros/test/test_import.py +++ b/google_chat_ros/test/test_import.py @@ -1,4 +1,5 @@ import unittest +import sys PKG = 'google_chat_ros' NAME = 'test_import' @@ -27,4 +28,4 @@ def test_import(self): if __name__ == "__main__": import rostest - rostest.rosrun(PKG, NAME, TestBlock) + rostest.rosrun(PKG, NAME, TestBlock, sys.argv) From 843e90516088677fb7e423b5bb65e4d8a1a1b723 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Mon, 29 Aug 2022 10:53:38 +0900 Subject: [PATCH 49/56] [google_chat_ros] work around kinetic, requirements.txt --- .github/workflows/config.yml | 2 +- google_chat_ros/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4133c286e..eb8593140 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -20,7 +20,7 @@ jobs: ROS_PARALLEL_TEST_JOBS: "-j8" CATKIN_PARALLEL_JOBS: "-i" NOT_TEST_INSTALL : true - BEFORE_SCRIPT : "for name in dialogflow_task_executive ros_speech_recognition sesame_ros ffha libsiftfast nlopt julius julius_ros downward assimp_devel; do echo \\$name; find $GITHUB_WORKSPACE -iname \\$name -exec touch {}/CATKIN_IGNORE \\; ; ls -al \\$(find -iname \\$name)/; done" # Skip large packagses / Skip packages that could not build on indigo dialogflow_task_executive/ros_speech_recognition/sesame_ros + BEFORE_SCRIPT : "for name in dialogflow_task_executive ros_speech_recognition sesame_ros ffha libsiftfast nlopt julius julius_ros downward assimp_devel google_chat_ros; do echo \\$name; find $GITHUB_WORKSPACE -iname \\$name -exec touch {}/CATKIN_IGNORE \\; ; ls -al \\$(find -iname \\$name)/; done" # Skip large packagses / Skip packages that could not build on indigo dialogflow_task_executive/ros_speech_recognition/sesame_ros - ROS_DISTRO: kinetic CONTAINER: ubuntu:16.04 ROS_PARALLEL_TEST_JOBS: "-j8" diff --git a/google_chat_ros/requirements.txt b/google_chat_ros/requirements.txt index e11fffb78..8cf4ef3f7 100644 --- a/google_chat_ros/requirements.txt +++ b/google_chat_ros/requirements.txt @@ -1,4 +1,4 @@ -beautifulsoup4==4.11.1 +beautifulsoup4==4.10.0 cachetools==4.2.4 certifi==2022.5.18.1 charset-normalizer==2.0.12 From 136a37b28178b3f6b97bf6db0f6bd6600d2f8a3d Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Wed, 31 Aug 2022 09:38:22 +0900 Subject: [PATCH 50/56] [google_chat_ros] not fix pip package version for python3.5 --- google_chat_ros/.gitignore | 1 + google_chat_ros/CMakeLists.txt | 3 +-- google_chat_ros/requirements.in | 18 +++++++++++++++ google_chat_ros/requirements.txt | 36 ----------------------------- google_chat_ros/test/test_import.py | 1 + 5 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 google_chat_ros/.gitignore create mode 100644 google_chat_ros/requirements.in delete mode 100644 google_chat_ros/requirements.txt diff --git a/google_chat_ros/.gitignore b/google_chat_ros/.gitignore new file mode 100644 index 000000000..4414fc1e2 --- /dev/null +++ b/google_chat_ros/.gitignore @@ -0,0 +1 @@ +requirements.txt diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt index 3cf0fa9e8..5242eb4a2 100644 --- a/google_chat_ros/CMakeLists.txt +++ b/google_chat_ros/CMakeLists.txt @@ -31,11 +31,10 @@ catkin_package() # generate the virtualenv catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in PYTHON_INTERPRETER python3 USE_SYSTEM_PACKAGES FALSE CHECK_VENV FALSE - EXTRA_PIP_ARGS - -vvv ) # install diff --git a/google_chat_ros/requirements.in b/google_chat_ros/requirements.in new file mode 100644 index 000000000..fa8818b71 --- /dev/null +++ b/google_chat_ros/requirements.in @@ -0,0 +1,18 @@ +click<=6.7 +gdown==4.4.0 +google-api-core[grpc]==1.32.0 +google-api-python-client +google-auth-httplib2==0.1.0 +google-auth-oauthlib +google-auth +google-cloud-pubsub +googleapis-common-protos[grpc] +grpc-google-iam-v1 +grpcio-status +grpcio +httplib2 +oauth2client==4.1.3 +oauthlib<=3.2.0 +protobuf<=3.19.4 +requests-oauthlib==1.3.1 +requests diff --git a/google_chat_ros/requirements.txt b/google_chat_ros/requirements.txt deleted file mode 100644 index 8cf4ef3f7..000000000 --- a/google_chat_ros/requirements.txt +++ /dev/null @@ -1,36 +0,0 @@ -beautifulsoup4==4.10.0 -cachetools==4.2.4 -certifi==2022.5.18.1 -charset-normalizer==2.0.12 -filelock==3.4.1 -gdown==4.4.0 -google-api-core[grpc]==2.8.2 -google-api-python-client==2.51.0 -google-auth-httplib2==0.1.0 -google-auth-oauthlib==0.5.2 -google-auth==2.8.0 -google-cloud-pubsub==2.12.1 -googleapis-common-protos[grpc]==1.56.2 -grpc-google-iam-v1==0.12.4 -grpcio-status==1.46.3 -grpcio==1.46.3 -httplib2==0.20.4 -idna==3.3 -importlib-resources==5.4.0 -oauth2client==4.1.3 -oauthlib==3.2.0 -proto-plus==1.20.6 -protobuf==3.19.4 -pyasn1-modules==0.2.8 -pyasn1==0.4.8 -pyparsing==3.0.9 -pysocks==1.7.1 -requests-oauthlib==1.3.1 -requests[socks]==2.27.1 -rsa==4.8 -six==1.16.0 -soupsieve==2.3.2.post1 -tqdm==4.64.0 -uritemplate==4.1.1 -urllib3==1.26.9 -zipp==3.6.0 diff --git a/google_chat_ros/test/test_import.py b/google_chat_ros/test/test_import.py index 89da88378..e6848f805 100644 --- a/google_chat_ros/test/test_import.py +++ b/google_chat_ros/test/test_import.py @@ -24,6 +24,7 @@ def test_import(self): import socket import ssl except Exception as e: + print(str(e)) assert False if __name__ == "__main__": From 9c5013c38bb468dc1c99316a33c5c167a7de433a Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Fri, 21 Oct 2022 14:01:01 +0900 Subject: [PATCH 51/56] [google_chat_ros] working around click LANG env issue in github actions --- .github/workflows/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index c929f79b6..4d814a25b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -80,6 +80,12 @@ jobs: xhost +local:root shell: bash + - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii + run: | + export LC_ALL=C.UTF-8 + export LANG=C.UTF-8 + shell: bash + - name: Run jsk_travis uses: jsk-ros-pkg/jsk_travis@master with: @@ -109,6 +115,11 @@ jobs: cd $GITHUB_WORKSPACE git checkout -qf $GITHUB_SHA || (git fetch -q origin +$GITHUB_REF; git checkout -qf FETCH_HEAD) git submodule update --init .travis + - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii + run: | + export LC_ALL=C.UTF-8 + export LANG=C.UTF-8 + shell: bash - name: Run jsk_travis uses: jsk-ros-pkg/jsk_travis@master with: @@ -138,6 +149,11 @@ jobs: - name: Skip packagse run: | for name in ffha libsiftfast nlopt julius julius_ros downward assimp_devel; do echo $name; find -iname $name -exec touch {}/CATKIN_IGNORE \; ; ls -al $(find -iname $name)/; done + - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii + run: | + export LC_ALL=C.UTF-8 + export LANG=C.UTF-8 + shell: bash - uses: uraimo/run-on-arch-action@v2 name: Run commands with: From 0e7d55298a92621020b33d77715ce28cfdb25778 Mon Sep 17 00:00:00 2001 From: Yoshiki Obinata Date: Tue, 1 Nov 2022 18:37:56 +0900 Subject: [PATCH 52/56] [google_chat_ros] fix package versions --- google_chat_ros/requirements.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/google_chat_ros/requirements.in b/google_chat_ros/requirements.in index fa8818b71..439fb3fc8 100644 --- a/google_chat_ros/requirements.in +++ b/google_chat_ros/requirements.in @@ -1,15 +1,15 @@ click<=6.7 gdown==4.4.0 -google-api-core[grpc]==1.32.0 -google-api-python-client +google-api-core[grpc]==2.8.2 +google-api-python-client==2.51.0 google-auth-httplib2==0.1.0 -google-auth-oauthlib -google-auth -google-cloud-pubsub -googleapis-common-protos[grpc] +google-auth-oauthlib==0.5.2 +google-auth==2.8.0 +google-cloud-pubsub==2.12.1 +googleapis-common-protos[grpc]==1.56.2 grpc-google-iam-v1 -grpcio-status -grpcio +grpcio-status==1.46.3 +grpcio==1.46.3 httplib2 oauth2client==4.1.3 oauthlib<=3.2.0 From 6b2a419acb23654606de719b2eb3f43ca465ea7b Mon Sep 17 00:00:00 2001 From: Kei Okada Date: Wed, 2 Nov 2022 20:41:20 +0900 Subject: [PATCH 53/56] Revert "[google_chat_ros] working around click LANG env issue in github actions" This reverts commit 9c5013c38bb468dc1c99316a33c5c167a7de433a. --- .github/workflows/config.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4d814a25b..c929f79b6 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -80,12 +80,6 @@ jobs: xhost +local:root shell: bash - - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii - run: | - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 - shell: bash - - name: Run jsk_travis uses: jsk-ros-pkg/jsk_travis@master with: @@ -115,11 +109,6 @@ jobs: cd $GITHUB_WORKSPACE git checkout -qf $GITHUB_SHA || (git fetch -q origin +$GITHUB_REF; git checkout -qf FETCH_HEAD) git submodule update --init .travis - - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii - run: | - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 - shell: bash - name: Run jsk_travis uses: jsk-ros-pkg/jsk_travis@master with: @@ -149,11 +138,6 @@ jobs: - name: Skip packagse run: | for name in ffha libsiftfast nlopt julius julius_ros downward assimp_devel; do echo $name; find -iname $name -exec touch {}/CATKIN_IGNORE \; ; ls -al $(find -iname $name)/; done - - name: export LANG environment variable # https://stackoverflow.com/questions/36651680/click-will-abort-further-execution-because-python-3-was-configured-to-use-ascii - run: | - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 - shell: bash - uses: uraimo/run-on-arch-action@v2 name: Run commands with: From dbe9fdd33f941f38344f7a04895580d3a542a4e7 Mon Sep 17 00:00:00 2001 From: Kei Okada Date: Tue, 8 Nov 2022 20:44:49 +0900 Subject: [PATCH 54/56] pass LANG/LC_A to cmake as --make-args --- .github/workflows/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index c929f79b6..f03ed2b4b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -83,7 +83,8 @@ jobs: - name: Run jsk_travis uses: jsk-ros-pkg/jsk_travis@master with: - ROS_PARALLEL_JOBS : ${{ matrix.ROS_PARALLEL_JOBS }} + # darty hack, jsk_travis runs catkin build --make-args $ROS_PARALLEL_JOBS -- + ROS_PARALLEL_JOBS : ${{ matrix.ROS_PARALLEL_JOBS }} LANG=C.UTF-8 LC_ALL=C.UTF-8 CATKIN_PARALLEL_JOBS : ${{ matrix.CATKIN_PARALLEL_JOBS }} ROS_PARALLEL_TEST_JOBS : ${{ matrix.ROS_PARALLEL_TEST_JOBS }} CATKIN_PARALLEL_TEST_JOBS : ${{ matrix.CATKIN_PARALLEL_TEST_JOBS }} @@ -94,6 +95,7 @@ jobs: BEFORE_SCRIPT : ${{ matrix.BEFORE_SCRIPT }} EXTRA_DEB : ${{ matrix.EXTRA_DEB }} + kinetic_i386: runs-on: ubuntu-latest name: kinetic_i386 @@ -114,6 +116,7 @@ jobs: with: EXTRA_DEB : "python-lxml" ROS_DISTRO : kinetic + ROS_PARALLEL_JOBS : "LANG=C.UTF-8 LC_ALL=C.UTF-8" ROS_PARALLEL_TEST_JOBS : "-j8" CATKIN_PARALLEL_JOBS: "-i" ROSDEP_ADDITIONAL_OPTIONS : "-n -q -r --ignore-src --skip-keys=python-google-cloud-texttospeech-pip --skip-keys=python-dialogflow-pip" # Skip installation of grpcio by pip because it causes error @@ -156,5 +159,6 @@ jobs: export USE_TRAVIS=true export USE_DOCKER=false export NOT_TEST_INSTALL=true + export ROS_PARALLEL_JOBS="--make-args LANG=C.UTF-8 LC_ALL=C.UTF-8" set +o nounset source .travis/travis.sh From 091d3c8cdfe33a8e0c423e293a6e300ce7908d45 Mon Sep 17 00:00:00 2001 From: Kei Okada Date: Tue, 8 Nov 2022 23:49:11 +0900 Subject: [PATCH 55/56] kinetic fails with 2022-11-08T13:11:12.5532139Z [google_chat_ros:make] File "/github/home/ros/ws_jsk_3rdparty/build/google_chat_ros/venv/bin/pip-compile", line 7, in 2022-11-08T13:11:12.5532848Z [google_chat_ros:make] from piptools.scripts.compile import cli 2022-11-08T13:11:12.5533901Z [google_chat_ros:make] File "/github/home/ros/ws_jsk_3rdparty/build/google_chat_ros/venv/lib/python3.5/site-packages/piptools/__init__.py", line 3, in 2022-11-08T13:11:12.5534577Z [google_chat_ros:make] from piptools.click import secho 2022-11-08T13:11:12.5535391Z [google_chat_ros:make] File "/github/home/ros/ws_jsk_3rdparty/build/google_chat_ros/venv/lib/python3.5/site-packages/piptools/click.py", line 3, in 2022-11-08T13:11:12.5536000Z [google_chat_ros:make] import click 2022-11-08T13:11:12.5536768Z [google_chat_ros:make] File "/github/home/ros/ws_jsk_3rdparty/build/google_chat_ros/venv/lib/python3.5/site-packages/click/__init__.py", line 7, in 2022-11-08T13:11:12.5537421Z [google_chat_ros:make] from .core import Argument as Argument 2022-11-08T13:11:12.5538281Z [google_chat_ros:make] File "/github/home/ros/ws_jsk_3rdparty/build/google_chat_ros/venv/lib/python3.5/site-packages/click/core.py", line 83 2022-11-08T13:11:12.5538949Z [google_chat_ros:make] f"{hint}. Command {base_command.name!r} is set to chain and" 2022-11-08T13:11:12.5539501Z [google_chat_ros:make] ^ 2022-11-08T13:11:12.5540023Z [google_chat_ros:make] SyntaxError: invalid syntax we need to downgrade piptools/click or use newer python3 --- .github/workflows/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f03ed2b4b..fcf1027d1 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -26,6 +26,7 @@ jobs: ROS_PARALLEL_TEST_JOBS: "-j8" CATKIN_PARALLEL_JOBS: "-i" EXTRA_DEB : "python-lxml" + BEFORE_SCRIPT : "sudo add-apt-repository -y ppa:jblgf0/python; sudo apt-get update; sudo apt-get install -y python3.6 python3.6-venv libpython3.6-dev; sudo ln -sf python3.6 /usr/bin/python3; ls -al /usr/bin/python3; python3 --version; python3 -v -m venv /tmp/v" - ROS_DISTRO: melodic CONTAINER: ubuntu:18.04 ROS_PARALLEL_TEST_JOBS: "-j8" @@ -121,7 +122,8 @@ jobs: CATKIN_PARALLEL_JOBS: "-i" ROSDEP_ADDITIONAL_OPTIONS : "-n -q -r --ignore-src --skip-keys=python-google-cloud-texttospeech-pip --skip-keys=python-dialogflow-pip" # Skip installation of grpcio by pip because it causes error # https://github.com/jsk-ros-pkg/jsk_3rdparty/pull/237 : (Note that pip==21.0.1 is incompatible with python 2.x) - BEFORE_SCRIPT : "sudo pip install virtualenv==15.1.0" + BEFORE_SCRIPT : "sudo pip install virtualenv==15.1.0; sudo add-apt-repository -y ppa:jblgf0/python; sudo apt-get update; sudo apt-get install -y python3.6 python3.6-venv libpython3.6-dev; sudo ln -sf python3.6 /usr/bin/python3" + arm: runs-on: ubuntu-latest @@ -155,7 +157,7 @@ jobs: export ROS_PARALLEL_TEST_JOBS="-j2" export CATKIN_PARALLEL_JOBS="-i" export ROSDEP_ADDITIONAL_OPTIONS="-n -q -r --ignore-src --skip-keys=python-google-cloud-texttospeech-pip --skip-keys=python-dialogflow-pip" # Skip installation of grpcio by pip because it causes error - export BEFORE_SCRIPT="sudo pip install virtualenv==15.1.0" + export BEFORE_SCRIPT="sudo pip install virtualenv==15.1.0; sudo add-apt-repository -y ppa:jblgf0/python; sudo apt-get update; sudo apt-get install -y python3.6 python3.6-venv libpython3.6-dev; sudo ln -sf python3.6 /usr/bin/python3" export USE_TRAVIS=true export USE_DOCKER=false export NOT_TEST_INSTALL=true From 2a6dfc9110746e6808e038b4ec019a0d1ed79332 Mon Sep 17 00:00:00 2001 From: Kei Okada Date: Wed, 9 Nov 2022 21:12:36 +0900 Subject: [PATCH 56/56] google_chat_ros skip arm/kinetic --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index fcf1027d1..78c5b115b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -142,7 +142,7 @@ jobs: submodules: 'true' - name: Skip packagse run: | - for name in ffha libsiftfast nlopt julius julius_ros downward assimp_devel; do echo $name; find -iname $name -exec touch {}/CATKIN_IGNORE \; ; ls -al $(find -iname $name)/; done + for name in ffha libsiftfast nlopt julius julius_ros downward assimp_devel google_chat_ros; do echo $name; find -iname $name -exec touch {}/CATKIN_IGNORE \; ; ls -al $(find -iname $name)/; done - uses: uraimo/run-on-arch-action@v2 name: Run commands with: @@ -157,7 +157,7 @@ jobs: export ROS_PARALLEL_TEST_JOBS="-j2" export CATKIN_PARALLEL_JOBS="-i" export ROSDEP_ADDITIONAL_OPTIONS="-n -q -r --ignore-src --skip-keys=python-google-cloud-texttospeech-pip --skip-keys=python-dialogflow-pip" # Skip installation of grpcio by pip because it causes error - export BEFORE_SCRIPT="sudo pip install virtualenv==15.1.0; sudo add-apt-repository -y ppa:jblgf0/python; sudo apt-get update; sudo apt-get install -y python3.6 python3.6-venv libpython3.6-dev; sudo ln -sf python3.6 /usr/bin/python3" + export BEFORE_SCRIPT="sudo pip install virtualenv==15.1.0" export USE_TRAVIS=true export USE_DOCKER=false export NOT_TEST_INSTALL=true