interactive bots: Create googletranslate bot.
This commit is contained in:
parent
7a963916f2
commit
1b16b54780
26
zulip_bots/zulip_bots/bots/googletranslate/doc.md
Normal file
26
zulip_bots/zulip_bots/bots/googletranslate/doc.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Google Translate bot
|
||||||
|
|
||||||
|
The Google Translate bot uses Google Translate to translate
|
||||||
|
any text sent to it.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
This bot requires a google cloud API key. Create one
|
||||||
|
[here](https://support.google.com/cloud/answer/6158862?hl=en)
|
||||||
|
|
||||||
|
You should add this key to `googletranslate.conf`.
|
||||||
|
|
||||||
|
To run this bot, use:
|
||||||
|
`zulip-run-bots googletranslate -c <zuliprc file>
|
||||||
|
--bot-config-file <path to googletranslate.conf>`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To use this bot, @-mention it like this:
|
||||||
|
|
||||||
|
`@-mention "<text>" <target language> <source language(Optional)>`
|
||||||
|
|
||||||
|
`text` must be in quotation marks, and `source language`
|
||||||
|
is optional.
|
||||||
|
|
||||||
|
If `source language` is not given, it will automatically detect your language.
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2",
|
||||||
|
"params": {
|
||||||
|
"q": "hello",
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "de",
|
||||||
|
"source": "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"error": {
|
||||||
|
"code": 403,
|
||||||
|
"message": "Invalid API Key.",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"message": "Invalid API Key",
|
||||||
|
"domain": "usageLimits",
|
||||||
|
"reason": "accessNotConfigured",
|
||||||
|
"extendedHelp": "https://console.developers.google.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "PERMISSION_DENIED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 403,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2",
|
||||||
|
"params": {
|
||||||
|
"q": "Hello",
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "de",
|
||||||
|
"source": "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2/languages",
|
||||||
|
"params": {
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"error": {
|
||||||
|
"code": 403,
|
||||||
|
"message": "Invalid API Key.",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"message": "Invalid API Key",
|
||||||
|
"domain": "usageLimits",
|
||||||
|
"reason": "accessNotConfigured",
|
||||||
|
"extendedHelp": "https://console.developers.google.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "PERMISSION_DENIED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 403,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2/languages",
|
||||||
|
"params": {
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"data": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"name": "English"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "fr",
|
||||||
|
"name": "French"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "de",
|
||||||
|
"name": "German"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2",
|
||||||
|
"params": {
|
||||||
|
"q": "hello",
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "de"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"data": {
|
||||||
|
"translations": [
|
||||||
|
{
|
||||||
|
"translatedText": "Hallo",
|
||||||
|
"detectedSourceLanguage": "en"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"api_url": "https://translation.googleapis.com/language/translate/v2",
|
||||||
|
"params": {
|
||||||
|
"q": "this has \"quotation\" marks in",
|
||||||
|
"key": "abcdefg",
|
||||||
|
"target": "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"data": {
|
||||||
|
"translations": [
|
||||||
|
{
|
||||||
|
"translatedText": "this has \"quotation\" marks in",
|
||||||
|
"detectedSourceLanguage": "en"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response-headers": {
|
||||||
|
"status": 200,
|
||||||
|
"content-type": "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
[googletranslate]
|
||||||
|
key=your_api_key_here
|
103
zulip_bots/zulip_bots/bots/googletranslate/googletranslate.py
Normal file
103
zulip_bots/zulip_bots/bots/googletranslate/googletranslate.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# To use this plugin, you need to set up the Google Cloud API key for this bot in
|
||||||
|
# googletranslate.conf in this (zulip_bots/bots/googletranslate/) directory.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class GoogleTranslateHandler(object):
|
||||||
|
'''
|
||||||
|
This bot will translate any messages sent to it using google translate.
|
||||||
|
Before using it, make sure you set up google api keys, and enable google
|
||||||
|
cloud translate from the google cloud console.
|
||||||
|
'''
|
||||||
|
def usage(self):
|
||||||
|
return '''
|
||||||
|
This plugin allows users translate messages
|
||||||
|
Users should @-mention the bot with the format
|
||||||
|
@-mention "<text_to_translate>" <target-language> <source-language(optional)>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def initialize(self, bot_handler):
|
||||||
|
self.config_info = bot_handler.get_config_info('googletranslate')
|
||||||
|
self.supported_languages = get_supported_languages(self.config_info['key'])
|
||||||
|
|
||||||
|
def handle_message(self, message, bot_handler):
|
||||||
|
bot_response = get_translate_bot_response(message['content'],
|
||||||
|
self.config_info,
|
||||||
|
message['sender_full_name'],
|
||||||
|
self.supported_languages)
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
|
||||||
|
api_url = 'https://translation.googleapis.com/language/translate/v2'
|
||||||
|
|
||||||
|
help_text = '''
|
||||||
|
Google translate bot
|
||||||
|
Please format your message like:
|
||||||
|
`@-mention "<text_to_translate>" <target-language> <source-language(optional)>`
|
||||||
|
Visit [here](https://cloud.google.com/translate/docs/languages) for all languages
|
||||||
|
'''
|
||||||
|
|
||||||
|
language_not_found_text = '{} language not found. Visit [here](https://cloud.google.com/translate/docs/languages) for all languages'
|
||||||
|
|
||||||
|
def get_supported_languages(key):
|
||||||
|
parameters = {'key': key, 'target': 'en'}
|
||||||
|
response = requests.get(api_url + '/languages', params = parameters)
|
||||||
|
if response.status_code == requests.codes.ok:
|
||||||
|
languages = response.json()['data']['languages']
|
||||||
|
return {lang['name'].lower(): lang['language'].lower() for lang in languages}
|
||||||
|
raise TranslateError(response.json()['error']['message'])
|
||||||
|
|
||||||
|
class TranslateError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def translate(text_to_translate, key, dest, src):
|
||||||
|
parameters = {'q': text_to_translate, 'target': dest, 'key': key}
|
||||||
|
if src != '':
|
||||||
|
parameters.update({'source': src})
|
||||||
|
response = requests.post(api_url, params=parameters)
|
||||||
|
if response.status_code == requests.codes.ok:
|
||||||
|
return response.json()['data']['translations'][0]['translatedText']
|
||||||
|
raise TranslateError(response.json()['error']['message'])
|
||||||
|
|
||||||
|
def get_code_for_language(language, all_languages):
|
||||||
|
if language.lower() not in all_languages.values():
|
||||||
|
if language.lower() not in all_languages.keys():
|
||||||
|
return ''
|
||||||
|
language = all_languages[language.lower()]
|
||||||
|
return language
|
||||||
|
|
||||||
|
def get_translate_bot_response(message_content, config_file, author, all_languages):
|
||||||
|
message_content = message_content.strip()
|
||||||
|
if message_content == 'help' or message_content is None or not message_content.startswith('"'):
|
||||||
|
return help_text
|
||||||
|
split_text = message_content.rsplit('" ', 1)
|
||||||
|
if len(split_text) == 1:
|
||||||
|
return help_text
|
||||||
|
split_text += split_text.pop(1).split(' ')
|
||||||
|
if len(split_text) == 2:
|
||||||
|
# There is no source language
|
||||||
|
split_text.append("")
|
||||||
|
if len(split_text) != 3:
|
||||||
|
return help_text
|
||||||
|
(text_to_translate, target_language, source_language) = split_text
|
||||||
|
text_to_translate = text_to_translate[1:]
|
||||||
|
target_language = get_code_for_language(target_language, all_languages)
|
||||||
|
if target_language == '':
|
||||||
|
return language_not_found_text.format("Target")
|
||||||
|
if source_language != '':
|
||||||
|
source_language = get_code_for_language(source_language, all_languages)
|
||||||
|
if source_language == '':
|
||||||
|
return language_not_found_text.format("Source")
|
||||||
|
try:
|
||||||
|
translated_text = translate(text_to_translate, config_file['key'], target_language, source_language)
|
||||||
|
except requests.exceptions.ConnectionError as conn_err:
|
||||||
|
return "Could not connect to Google Translate. {}.".format(conn_err)
|
||||||
|
except TranslateError as tr_err:
|
||||||
|
return "Translate Error. {}.".format(tr_err)
|
||||||
|
except Exception as err:
|
||||||
|
return "Error. {}.".format(err)
|
||||||
|
return "{} (from {})".format(translated_text, author)
|
||||||
|
|
||||||
|
handler_class = GoogleTranslateHandler
|
|
@ -0,0 +1,135 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
from requests.exceptions import HTTPError, ConnectionError
|
||||||
|
|
||||||
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
from zulip_bots.bots.googletranslate.googletranslate import TranslateError
|
||||||
|
|
||||||
|
help_text = '''
|
||||||
|
Google translate bot
|
||||||
|
Please format your message like:
|
||||||
|
`@-mention "<text_to_translate>" <target-language> <source-language(optional)>`
|
||||||
|
Visit [here](https://cloud.google.com/translate/docs/languages) for all languages
|
||||||
|
'''
|
||||||
|
|
||||||
|
class TestGoogleTranslateBot(BotTestCase):
|
||||||
|
bot_name = "googletranslate"
|
||||||
|
|
||||||
|
def test_normal(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_normal'):
|
||||||
|
with self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello" de', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Hallo (from tester)'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_source_language_not_found(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello" german foo', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Source language not found. Visit [here](https://cloud.google.com/translate/docs/languages) for all languages'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_target_language_not_found(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello" bar english', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Target language not found. Visit [here](https://cloud.google.com/translate/docs/languages) for all languages'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_403(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_403'):
|
||||||
|
with self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello" german english', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Translate Error. Invalid API Key..'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_help_empty(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': help_text},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_help_command(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': 'help', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': help_text},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_help_no_langs(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello"', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': help_text},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_quotation_in_text(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_quotation'):
|
||||||
|
with self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"this has "quotation" marks in" english', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'this has "quotation" marks in (from tester)'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_exception(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
patch('zulip_bots.bots.googletranslate.googletranslate.translate', side_effect=Exception):
|
||||||
|
with self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assertRaises(Exception)
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"hello" de', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Error. .'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_language_403(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
self.mock_http_conversation('test_language_403'), \
|
||||||
|
self.assertRaises(TranslateError):
|
||||||
|
self.initialize_bot()
|
||||||
|
|
||||||
|
def test_connection_error(self):
|
||||||
|
with self.mock_config_info({'key': 'abcdefg'}), \
|
||||||
|
patch('requests.post', side_effect=ConnectionError()), \
|
||||||
|
patch('logging.warning'):
|
||||||
|
with self.mock_http_conversation('test_languages'):
|
||||||
|
self.initialize_bot()
|
||||||
|
self.assert_bot_response(
|
||||||
|
message = {'content': '"test" en', 'sender_full_name': 'tester'},
|
||||||
|
response = {'content': 'Could not connect to Google Translate. .'},
|
||||||
|
expected_method = 'send_reply'
|
||||||
|
)
|
Loading…
Reference in a new issue