Repository: sopel-irc/sopel

Issue:
Unhandled exception in `translate` plugin
<!-- Before reporting a bug, please search both open *and closed* issues to
see if it has already been reported. If you can, try to reproduce the problem
on an unmodified copy of the `master` branch first, as sometimes bugs are found
and fixed without a report. If the problem is unreported and persists in
`master`, please help us fix it quickly by filling out as much of this
information as you can. Thanks! -->

### Description
Sopel's translate command shows "Unexpected error
          (HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max
          retries exceeded with url:
          /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=..."

### Reproduction steps
With Sopel v7.1.1 (Python v3.9.5) running, do a translate command like:
```
...
10:06AM <URL> [version] Sopel v7.1.1 | Python: 3.9.5
[10:06am] <Ant> hm
10:07AM <URL> Unexpected error
          (HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max
          retries exceeded with url:
          /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=bonne+marche
          (Caused by
          ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection
          object at 0x7f13019115e0>, 'Connection to translate.googleapis.com
          timed out. (connect timeout=40)'))) from Ant at 2021-07-09
          17:07:18.333177. Message was: .translate bonne marche
10:07AM <URL> Unexpected error
          (HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max
          retries exceeded with url:
          /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=hormiga (Caused
          by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection
          object at 0x7f13018930d0>, 'Connection to translate.googleapis.com
          timed out. (connect timeout=40)'))) from Ant at 2021-07-09
          17:07:30.771072. Message was: .translate hormiga
...
```

### Expected behavior
It should give me a non-error result of translation like:
```
...
[08:12am] <Ant> .translate hormiga
08:12AM <URL> [translate] "ant" (es to en, translate.google.com)
...
```

### Logs
```
[2021-07-09 10:17:09,685] sopel.bot            ERROR    - Unexpected error (HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max retries exceeded with url: /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=hormiga (Caused by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection object at 0x7f379c6ddd00>, 'Connection to translate.googleapis.com timed out. (connect timeout=40)'))) from Ant at 2021-07-09 17:17:09.685340. Message was: .translate hormiga
Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/urllib3/connection.py", line 156, in _new_conn
    conn = connection.create_connection(
  File "/usr/lib/python3.9/site-packages/urllib3/util/connection.py", line 84, in create_connection
    raise err
  File "/usr/lib/python3.9/site-packages/urllib3/util/connection.py", line 74, in create_connection
    sock.connect(sa)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/urllib3/connectionpool.py", line 665, in urlopen
    httplib_response = self._make_request(
  File "/usr/lib/python3.9/site-packages/urllib3/connectionpool.py", line 376, in _make_request
    self._validate_conn(conn)
  File "/usr/lib/python3.9/site-packages/urllib3/connectionpool.py", line 994, in _validate_conn
    conn.connect()
  File "/usr/lib/python3.9/site-packages/urllib3/connection.py", line 300, in connect
    conn = self._new_conn()
  File "/usr/lib/python3.9/site-packages/urllib3/connection.py", line 161, in _new_conn
    raise ConnectTimeoutError(
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.VerifiedHTTPSConnection object at 0x7f379c6ddd00>, 'Connection to translate.googleapis.com timed out. (connect timeout=40)')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/requests/adapters.py", line 439, in send
    resp = conn.urlopen(
  File "/usr/lib/python3.9/site-packages/urllib3/connectionpool.py", line 719, in urlopen
    retries = retries.increment(
  File "/usr/lib/python3.9/site-packages/urllib3/util/retry.py", line 436, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max retries exceeded with url: /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=hormiga (Caused by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection object at 0x7f379c6ddd00>, 'Connection to translate.googleapis.com timed out. (connect timeout=40)'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ant/.local/lib/python3.9/site-packages/sopel/bot.py", line 757, in call_rule
    rule.execute(sopel, trigger)
  File "/home/ant/.local/lib/python3.9/site-packages/sopel/plugins/rules.py", line 1057, in execute
    exit_code = self._handler(bot, trigger)
  File "/home/ant/.local/lib/python3.9/site-packages/sopel/modules/translate.py", line 174, in tr2
    msg, src = translate(phrase, src, dest)
  File "/home/ant/.local/lib/python3.9/site-packages/sopel/modules/translate.py", line 59, in translate
    result = requests.get(url, params=query, timeout=40, headers=headers).text
  File "/usr/lib/python3.9/site-packages/requests/api.py", line 76, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/lib/python3.9/site-packages/requests/api.py", line 61, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/lib/python3.9/site-packages/requests/sessions.py", line 530, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python3.9/site-packages/requests/sessions.py", line 643, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python3.9/site-packages/requests/adapters.py", line 504, in send
    raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='translate.googleapis.com', port=443): Max retries exceeded with url: /translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=hormiga (Caused by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection object at 0x7f379c6ddd00>, 'Connection to translate.googleapis.com timed out. (connect timeout=40)'))
```

### Environment
- Sopel `.version`: [7.1.1]
- Sopel installed via: [pip]
- Python version: [3.9.5]
- Operating system: [Fedora v33]
- IRCd `/version`: [ircd-ratbox-3.0.8(20121231_3-27427)]
- Relevant plugins: [defaults from sopel's packages via pip install]

### Notes
I do not have su(do)/root access so it is done locally in my local Linux account. :)


Discussion:
* Bug on our part: not properly handling connection error; a user-friendly error message should be replied
* Not a bug on our part: I think your server can't connect to or is blocked by the google translate API
Thank you for the quick answer. I wonder why sometimes it works and sometimes it doesn't.
> sometimes it doesn't.

The timeout error is clearly a network error between your server and the google translate API. Nothing we (Sopel) can do about it. You'll have to ask your hosting service for that.
OK.

Resolving patch:
diff --git a/sopel/modules/translate.py b/sopel/modules/translate.py
--- a/sopel/modules/translate.py
+++ b/sopel/modules/translate.py
@@ -9,6 +9,7 @@
 from __future__ import generator_stop
 
 import json
+import logging
 import random
 
 import requests
@@ -17,6 +18,7 @@
 from sopel.tools import web
 
 
+LOGGER = logging.getLogger(__name__)
 PLUGIN_OUTPUT_PREFIX = '[translate] '
 
 
@@ -64,6 +66,9 @@ def translate(text, in_lang='auto', out_lang='en'):
     try:
         data = json.loads(result)
     except ValueError:
+        LOGGER.error(
+            'Error parsing JSON response from translate API (%s to %s: "%s")',
+            in_lang, out_lang, text)
         return None, None
 
     if raw:
@@ -100,7 +105,21 @@ def tr(bot, trigger):
         bot.reply('Language guessing failed, so try suggesting one!')
         return
 
-    msg, in_lang = translate(phrase, in_lang, out_lang)
+    try:
+        msg, in_lang = translate(phrase, in_lang, out_lang)
+    except requests.Timeout:
+        bot.reply("Translation service unavailable (timeout).")
+        LOGGER.error(
+            'Translate API error (%s to %s: "%s"): timeout.',
+            in_lang, out_lang, phrase)
+        return
+    except requests.RequestException as http_error:
+        bot.reply("Translation request failed.")
+        LOGGER.exception(
+            'Translate API error (%s to %s: "%s"): %s.',
+            in_lang, out_lang, phrase, http_error)
+        return
+
     if not in_lang:
         bot.reply("Translation failed, probably because of a rate-limit.")
         return
@@ -164,7 +183,21 @@ def langcode(p):
         bot.reply('Language guessing failed, so try suggesting one!')
         return
 
-    msg, src = translate(phrase, src, dest)
+    try:
+        msg, src = translate(phrase, src, dest)
+    except requests.Timeout:
+        bot.reply("Translation service unavailable (timeout).")
+        LOGGER.error(
+            'Translate API error (%s to %s: "%s"): timeout.',
+            src, dest, phrase)
+        return
+    except requests.RequestException as http_error:
+        bot.reply("Translation request failed.")
+        LOGGER.exception(
+            'Translate API error (%s to %s: "%s"): %s.',
+            src, dest, phrase, http_error)
+        return
+
     if not src:
         return bot.say("Translation failed, probably because of a rate-limit.")
 


Test patch: