Repository: Flexget/Flexget

Issue:
Unhandled exception in notify/discord
### Expected behaviour:

Graceful crash with correct logs

### Actual behaviour:

Excteption thrown from within `except`-block

```
File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notifiers/discord.py", line 180, in notify
if e.response.status_code == 429:
AttributeError: 'NoneType' object has no attribute 'status_code'
```

[Permalink to the affected line](https://github.com/Flexget/Flexget/blob/fc3d35ce0d10b22b4f22517cdbbcdb720b224fe0/flexget/components/notify/notifiers/discord.py#L180)

### Steps to reproduce:
Unclear as of now, will update this issue if I find out how to reproduce it

#### Config:
```yaml
notify:
  entries:
    message: Downloaded {{ tvdb_series_name }} - S{{ tvdb_season|pad(2) }}E{{ tvdb_episode|pad(2) }}
    title: '{{ title }}'
    via:
      - discord:
          avatar_url: https://avatars2.githubusercontent.com/u/17483320?s=400&v=4
          username: Flexget
          web_hook_url: <URL>
rss: https://nyaa.si/?page=rss&q=%5BASW%5D+jujutsu+kaisen&c=0_0&f=0
series:
  - Jujutsu Kaisen:
      begin: 25
      ep_regexp: \s-\s\d+
template:
  - torrent
  - discord
thetvdb_lookup: true
transmission:
  content_filename: '[ASW] Jujutsu Kaisen - S{{ tvdb_season|pad(2) }}E{{ tvdb_episode|pad(2) }}'
  host: transmission
  path: /Media/Anime/Jujutsu Kaisen/S{{ tvdb_season|pad(2) }}
  port: 9091
  ratio: 4
```
  
#### Log:
<details>
 <summary>(click to expand)</summary>

```
2023-12-27 05:38:21 INFO     download      JJK S02         Downloading: [ASW] Jujutsu Kaisen - 46 [1080p HEVC x265 10Bit][AAC]
2023-12-27 05:38:22 INFO     transmission  JJK S02         "[ASW] Jujutsu Kaisen - 46 [1080p HEVC x265 10Bit][AAC]" torrent added to transmission
2023-12-28 20:10:01 INFO     download      JJK S02         Downloading: [ASW] Jujutsu Kaisen - 47 [1080p HEVC x265 10Bit][AAC]
2023-12-28 20:10:04 INFO     transmission  JJK S02         "[ASW] Jujutsu Kaisen - 47 [1080p HEVC x265 10Bit][AAC]" torrent added to transmission
2023-12-28 20:10:34 ERROR    notify_entry  JJK S02         'NoneType' object has no attribute 'status_code'
Traceback (most recent call last):

  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 467, in _make_request
    six.raise_from(e, None)
    │   └ <function raise_from at 0x7f337966d580>
    └ <module 'urllib3.packages.six' from '/usr/local/lib/python3.11/site-packages/urllib3/packages/six.py'>
  File "<string>", line 3, in raise_from
  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 462, in _make_request
    httplib_response = conn.getresponse()
                       │    └ <function HTTPConnection.getresponse at 0x7f33797d9c60>
                       └ <urllib3.connection.HTTPSConnection object at 0x7f3371960790>
  File "/usr/local/lib/python3.11/http/client.py", line 1386, in getresponse
    response.begin()
    │        └ <function HTTPResponse.begin at 0x7f3379f9bce0>
    └ <http.client.HTTPResponse object at 0x7f3371440550>
  File "/usr/local/lib/python3.11/http/client.py", line 325, in begin
    version, status, reason = self._read_status()
                              │    └ <function HTTPResponse._read_status at 0x7f3379f9bc40>
                              └ <http.client.HTTPResponse object at 0x7f3371440550>
  File "/usr/local/lib/python3.11/http/client.py", line 286, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
               │    │           └ 65536
               │    └ None
               └ <http.client.HTTPResponse object at 0x7f3371440550>
  File "/usr/local/lib/python3.11/socket.py", line 706, in readinto
    return self._sock.recv_into(b)
           │    │               └ <memory at 0x7f337214ef80>
           │    └ None
           └ <socket.SocketIO object at 0x7f33718a2080>
  File "/usr/local/lib/python3.11/ssl.py", line 1315, in recv_into
    return self.read(nbytes, buffer)
           │    │    │       └ <memory at 0x7f337214ef80>
           │    │    └ 8192
           │    └ <function SSLSocket.read at 0x7f337bc028e0>
           └ <ssl.SSLSocket [closed] fd=-1, family=2, type=1, proto=6>
  File "/usr/local/lib/python3.11/ssl.py", line 1167, in read
    return self._sslobj.read(len, buffer)
           │    │            │    └ <memory at 0x7f337214ef80>
           │    │            └ 8192
           │    └ None
           └ <ssl.SSLSocket [closed] fd=-1, family=2, type=1, proto=6>

TimeoutError: The read operation timed out


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/usr/local/lib/python3.11/site-packages/requests/adapters.py", line 486, in send
    resp = conn.urlopen(
           │    └ <function HTTPConnectionPool.urlopen at 0x7f33796df9c0>
           └ <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f33719757d0>
  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 799, in urlopen
    retries = retries.increment(
              │       └ <function Retry.increment at 0x7f3379686f20>
              └ Retry(total=0, connect=None, read=False, redirect=None, status=None)
  File "/usr/local/lib/python3.11/site-packages/urllib3/util/retry.py", line 550, in increment
    raise six.reraise(type(error), error, _stacktrace)
          │   │            │       │      └ <traceback object at 0x7f3371976980>
          │   │            │       └ ReadTimeoutError("HTTPSConnectionPool(host='discord.com', port=443): Read timed out. (read timeout=30)")
          │   │            └ ReadTimeoutError("HTTPSConnectionPool(host='discord.com', port=443): Read timed out. (read timeout=30)")
          │   └ <function reraise at 0x7f337966d440>
          └ <module 'urllib3.packages.six' from '/usr/local/lib/python3.11/site-packages/urllib3/packages/six.py'>
  File "/usr/local/lib/python3.11/site-packages/urllib3/packages/six.py", line 770, in reraise
    raise value
          └ None
  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
                       │    └ <function HTTPConnectionPool._make_request at 0x7f33796df740>
                       └ <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f33719757d0>
  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 469, in _make_request
    self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
    │    │                         │                  └ 30
    │    │                         └ '/api/webhooks/1189422493270810767/fsKrAwbBR_X2P-OBxmlFfK88LJSEC_Kg4uQiQrRqB7WSOG_sE8CEWJO4opAw-BEULpWr'
    │    └ <function HTTPConnectionPool._raise_timeout at 0x7f33796df6a0>
    └ <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f33719757d0>
  File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 358, in _raise_timeout
    raise ReadTimeoutError(
          └ <class 'urllib3.exceptions.ReadTimeoutError'>

urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='discord.com', port=443): Read timed out. (read timeout=30)


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notifiers/discord.py", line 170, in notify
    req = session.post(config['web_hook_url'], json=web_hook)
          │       │    │                            └ {'content': 'Downloaded Jujutsu Kaisen - S02E23', 'username': 'Flexget', 'avatar_url': 'https://avatars2.githubusercontent.co...
          │       │    └ {'web_hook_url': '<URL>...
          │       └ <function Session.post at 0x7f33794332e0>
          └ <flexget.utils.requests.Session object at 0x7f337569f690>
  File "/usr/local/lib/python3.11/site-packages/requests/sessions.py", line 637, in post
    return self.request("POST", url, data=data, json=json, **kwargs)
           │    │               │         │          │       └ {}
           │    │               │         │          └ {'content': 'Downloaded Jujutsu Kaisen - S02E23', 'username': 'Flexget', 'avatar_url': 'https://avatars2.githubusercontent.co...
           │    │               │         └ None
           │    │               └ '<URL>'
           │    └ <function Session.request at 0x7f3378a3ab60>
           └ <flexget.utils.requests.Session object at 0x7f337569f690>
  File "/usr/local/lib/python3.11/site-packages/flexget/utils/requests.py", line 267, in request
    result = super().request(method, url, *args, **kwargs)
                             │       │     │       └ {'data': None, 'json': {'content': 'Downloaded Jujutsu Kaisen - S02E23', 'username': 'Flexget', 'avatar_url': 'https://avatar...
                             │       │     └ ()
                             │       └ '<URL>'
                             └ 'POST'
  File "/usr/local/lib/python3.11/site-packages/requests/sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           │    │    │       └ {'timeout': 30, 'allow_redirects': True, 'proxies': OrderedDict(), 'stream': False, 'verify': True, 'cert': None}
           │    │    └ <PreparedRequest [POST]>
           │    └ <function Session.send at 0x7f3379433560>
           └ <flexget.utils.requests.Session object at 0x7f337569f690>
  File "/usr/local/lib/python3.11/site-packages/requests/sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        │       │    │          └ {'timeout': 30, 'proxies': OrderedDict(), 'stream': False, 'verify': True, 'cert': None}
        │       │    └ <PreparedRequest [POST]>
        │       └ <function HTTPAdapter.send at 0x7f33794328e0>
        └ <requests.adapters.HTTPAdapter object at 0x7f337569fc10>
  File "/usr/local/lib/python3.11/site-packages/requests/adapters.py", line 532, in send
    raise ReadTimeout(e, request=request)
          │                      └ <PreparedRequest [POST]>
          └ <class 'requests.exceptions.ReadTimeout'>

requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='discord.com', port=443): Read timed out. (read timeout=30)


During handling of the above exception, another exception occurred:


Traceback (most recent call last):

  File "/usr/local/lib/python3.11/threading.py", line 1002, in _bootstrap
    self._bootstrap_inner()
    │    └ <function Thread._bootstrap_inner at 0x7f337c691620>
    └ <Thread(task_queue, started daemon 139858943535928)>
  File "/usr/local/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
    self.run()
    │    └ <function Thread.run at 0x7f337c691300>
    └ <Thread(task_queue, started daemon 139858943535928)>
  File "/usr/local/lib/python3.11/threading.py", line 982, in run
    self._target(*self._args, **self._kwargs)
    │    │        │    │        │    └ {}
    │    │        │    │        └ <Thread(task_queue, started daemon 139858943535928)>
    │    │        │    └ ()
    │    │        └ <Thread(task_queue, started daemon 139858943535928)>
    │    └ <bound method TaskQueue.run of <flexget.task_queue.TaskQueue object at 0x7f3373bc20d0>>
    └ <Thread(task_queue, started daemon 139858943535928)>
  File "/usr/local/lib/python3.11/site-packages/flexget/task_queue.py", line 46, in run
    self.current_task.execute()
    │    │            └ <function Task.execute at 0x7f337883ca40>
    │    └ <flexget.task.Task object at 0x7f33718a5790>
    └ <flexget.task_queue.TaskQueue object at 0x7f3373bc20d0>
  File "/usr/local/lib/python3.11/site-packages/flexget/task.py", line 87, in wrapper
    return func(self, *args, **kw)
           │    │      │       └ {}
           │    │      └ ()
           │    └ <flexget.task.Task object at 0x7f33718a5790>
           └ <function Task.execute at 0x7f337883c9a0>
  File "/usr/local/lib/python3.11/site-packages/flexget/task.py", line 722, in execute
    self._execute()
    │    └ <function Task._execute at 0x7f337883c900>
    └ <flexget.task.Task object at 0x7f33718a5790>
  File "/usr/local/lib/python3.11/site-packages/flexget/task.py", line 691, in _execute
    self.__run_task_phase(phase)
    │                     └ 'output'
    └ <flexget.task.Task object at 0x7f33718a5790>
  File "/usr/local/lib/python3.11/site-packages/flexget/task.py", line 514, in __run_task_phase
    response = self.__run_plugin(plugin, phase, args)
               │                 │       │      └ (<flexget.task.Task object at 0x7f33718a5790>, {'entries': {'message': 'Downloaded {{ tvdb_series_name }} - S{{ tvdb_season|p...
               │                 │       └ 'output'
               │                 └ <PluginInfo(name=notify)>
               └ <flexget.task.Task object at 0x7f33718a5790>
  File "/usr/local/lib/python3.11/site-packages/flexget/task.py", line 547, in __run_plugin
    result = method(*args, **kwargs)
             │       │       └ {}
             │       └ (<flexget.task.Task object at 0x7f33718a5790>, {'entries': {'message': 'Downloaded {{ tvdb_series_name }} - S{{ tvdb_season|p...
             └ <Event(name=plugin.notify.output,func=on_task_output,priority=0)>
  File "/usr/local/lib/python3.11/site-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ (<flexget.task.Task object at 0x7f33718a5790>, {'entries': {'message': 'Downloaded {{ tvdb_series_name }} - S{{ tvdb_season|p...
           │    └ <bound method Notify.on_task_output of <flexget.components.notify.notify.Notify object at 0x7f3373d64910>>
           └ <Event(name=plugin.notify.output,func=on_task_output,priority=0)>
  File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notify.py", line 131, in on_task_output
    self.send_notification(
    │    └ <function Notify.send_notification at 0x7f3375677ba0>
    └ <flexget.components.notify.notify.Notify object at 0x7f3373d64910>
> File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notify.py", line 102, in send_notification
    send_notification(*args, **kwargs)
    │                  │       └ {'template_renderer': <bound method Entry.render of <Entry(title=[ASW] Jujutsu Kaisen - 47 [1080p HEVC x265 10Bit][AAC],state...
    │                  └ ('{{ title }}', 'Downloaded {{ tvdb_series_name }} - S{{ tvdb_season|pad(2) }}E{{ tvdb_episode|pad(2) }}', [{'discord': {'web...
    └ <bound method NotificationFramework.send_notification of <flexget.components.notify.notification_framework.NotificationFramew...
  File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notification_framework.py", line 122, in send_notification
    notifier_plugin.notify(
    │               └ <function DiscordNotifier.notify at 0x7f33756954e0>
    └ <flexget.components.notify.notifiers.discord.DiscordNotifier object at 0x7f3373d64dd0>
  File "/usr/local/lib/python3.11/site-packages/flexget/components/notify/notifiers/discord.py", line 180, in notify
    if e.response.status_code == 429:

AttributeError: 'NoneType' object has no attribute 'status_code'
```
</details>

### Additional information:

- FlexGet version: 3.11.0
- Python version: 3.11.7
- Installation method: Docker
- Using daemon (yes/no): yes?
- OS and version: Synology DSM7

<!---
In config and debug/crash logs, remember to redact any personal or sensitive information such as passwords, API keys, private URLs and so on.

Please verify that the following data is present before submitting your issue:

- Link to a paste service or paste above the relevant config (preferably full config, including templates if present). Please make sure the paste does not expire, if possible.
- Link to a paste service or paste above debug-level logs of the relevant task/s (use `flexget -L debug execute --tasks <Task_name>`).
- FlexGet version (use `flexget -V` to get it).
- Full Python version, for example `2.7.11` (use `python -V` to get it). 
- Installation method (pip, git install, etc).
- Whether or not you're running FlexGet as a daemon.
- OS and version.
- Attach crash log if one was generated, in addition to the debug-level log. It can be found in the directory with your config file.
--->



Discussion:

Resolving patch:
diff --git a/flexget/components/notify/notifiers/discord.py b/flexget/components/notify/notifiers/discord.py
--- a/flexget/components/notify/notifiers/discord.py
+++ b/flexget/components/notify/notifiers/discord.py
@@ -3,6 +3,7 @@
 
 from dateutil.parser import ParserError, isoparse
 from loguru import logger
+from requests.exceptions import ReadTimeout
 
 from flexget import plugin
 from flexget.event import event
@@ -176,6 +177,9 @@ def notify(self, title, message, config):
                 session.add_domain_limiter(
                     TokenBucketLimiter('discord.com', tokens=tokens, rate=tokens_reset)
                 )
+            except ReadTimeout:
+                logger.info('Request timed out')
+                continue
             except RequestException as e:
                 if e.response.status_code == 429:
                     timeout = int(


Test patch: