├── .gitignore
├── LICENSE
├── README.md
├── docker
├── certifi
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── __main__.py
│ ├── cacert.pem
│ ├── core.py
│ ├── core.pyc
│ ├── old_root.pem
│ └── weak.pem
├── chardet
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── big5freq.py
│ ├── big5freq.pyc
│ ├── big5prober.py
│ ├── big5prober.pyc
│ ├── chardistribution.py
│ ├── chardistribution.pyc
│ ├── charsetgroupprober.py
│ ├── charsetgroupprober.pyc
│ ├── charsetprober.py
│ ├── charsetprober.pyc
│ ├── cli
│ │ ├── __init__.py
│ │ └── chardetect.py
│ ├── codingstatemachine.py
│ ├── codingstatemachine.pyc
│ ├── compat.py
│ ├── compat.pyc
│ ├── cp949prober.py
│ ├── cp949prober.pyc
│ ├── enums.py
│ ├── enums.pyc
│ ├── escprober.py
│ ├── escprober.pyc
│ ├── escsm.py
│ ├── escsm.pyc
│ ├── eucjpprober.py
│ ├── eucjpprober.pyc
│ ├── euckrfreq.py
│ ├── euckrfreq.pyc
│ ├── euckrprober.py
│ ├── euckrprober.pyc
│ ├── euctwfreq.py
│ ├── euctwfreq.pyc
│ ├── euctwprober.py
│ ├── euctwprober.pyc
│ ├── gb2312freq.py
│ ├── gb2312freq.pyc
│ ├── gb2312prober.py
│ ├── gb2312prober.pyc
│ ├── hebrewprober.py
│ ├── hebrewprober.pyc
│ ├── jisfreq.py
│ ├── jisfreq.pyc
│ ├── jpcntx.py
│ ├── jpcntx.pyc
│ ├── langbulgarianmodel.py
│ ├── langbulgarianmodel.pyc
│ ├── langcyrillicmodel.py
│ ├── langcyrillicmodel.pyc
│ ├── langgreekmodel.py
│ ├── langgreekmodel.pyc
│ ├── langhebrewmodel.py
│ ├── langhebrewmodel.pyc
│ ├── langhungarianmodel.py
│ ├── langthaimodel.py
│ ├── langthaimodel.pyc
│ ├── langturkishmodel.py
│ ├── langturkishmodel.pyc
│ ├── latin1prober.py
│ ├── latin1prober.pyc
│ ├── mbcharsetprober.py
│ ├── mbcharsetprober.pyc
│ ├── mbcsgroupprober.py
│ ├── mbcsgroupprober.pyc
│ ├── mbcssm.py
│ ├── mbcssm.pyc
│ ├── sbcharsetprober.py
│ ├── sbcharsetprober.pyc
│ ├── sbcsgroupprober.py
│ ├── sbcsgroupprober.pyc
│ ├── sjisprober.py
│ ├── sjisprober.pyc
│ ├── universaldetector.py
│ ├── universaldetector.pyc
│ ├── utf8prober.py
│ ├── utf8prober.pyc
│ ├── version.py
│ └── version.pyc
├── idna
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── codec.py
│ ├── compat.py
│ ├── core.py
│ ├── core.pyc
│ ├── idnadata.py
│ ├── idnadata.pyc
│ ├── intranges.py
│ ├── intranges.pyc
│ ├── package_data.py
│ ├── package_data.pyc
│ └── uts46data.py
├── radarr_discord.py
├── requests
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── __version__.py
│ ├── __version__.pyc
│ ├── _internal_utils.py
│ ├── _internal_utils.pyc
│ ├── adapters.py
│ ├── adapters.pyc
│ ├── api.py
│ ├── api.pyc
│ ├── auth.py
│ ├── auth.pyc
│ ├── certs.py
│ ├── certs.pyc
│ ├── compat.py
│ ├── compat.pyc
│ ├── cookies.py
│ ├── cookies.pyc
│ ├── exceptions.py
│ ├── exceptions.pyc
│ ├── help.py
│ ├── hooks.py
│ ├── hooks.pyc
│ ├── models.py
│ ├── models.pyc
│ ├── packages.py
│ ├── packages.pyc
│ ├── sessions.py
│ ├── sessions.pyc
│ ├── status_codes.py
│ ├── status_codes.pyc
│ ├── structures.py
│ ├── structures.pyc
│ ├── utils.py
│ └── utils.pyc
├── sonarr_discord.py
└── urllib3
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── _collections.py
│ ├── _collections.pyc
│ ├── connection.py
│ ├── connection.pyc
│ ├── connectionpool.py
│ ├── connectionpool.pyc
│ ├── contrib
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── _securetransport
│ │ ├── __init__.py
│ │ ├── bindings.py
│ │ └── low_level.py
│ ├── appengine.py
│ ├── appengine.pyc
│ ├── ntlmpool.py
│ ├── pyopenssl.py
│ ├── pyopenssl.pyc
│ ├── securetransport.py
│ ├── socks.py
│ └── socks.pyc
│ ├── exceptions.py
│ ├── exceptions.pyc
│ ├── fields.py
│ ├── fields.pyc
│ ├── filepost.py
│ ├── filepost.pyc
│ ├── packages
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── backports
│ │ ├── __init__.py
│ │ └── makefile.py
│ ├── ordered_dict.py
│ ├── ordered_dict.pyc
│ ├── six.py
│ ├── six.pyc
│ └── ssl_match_hostname
│ │ ├── __init__.py
│ │ ├── __init__.pyc
│ │ ├── _implementation.py
│ │ └── _implementation.pyc
│ ├── poolmanager.py
│ ├── poolmanager.pyc
│ ├── request.py
│ ├── request.pyc
│ ├── response.py
│ ├── response.pyc
│ └── util
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── connection.py
│ ├── connection.pyc
│ ├── request.py
│ ├── request.pyc
│ ├── response.py
│ ├── response.pyc
│ ├── retry.py
│ ├── retry.pyc
│ ├── selectors.py
│ ├── selectors.pyc
│ ├── ssl_.py
│ ├── ssl_.pyc
│ ├── timeout.py
│ ├── timeout.pyc
│ ├── url.py
│ ├── url.pyc
│ ├── wait.py
│ └── wait.pyc
├── radarr_discord.py
├── requirements.txt
├── script_config.example.py
└── sonarr_discord.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .DS_Store?
3 | ._*
4 | .Spotlight-V100
5 | .Trashes
6 | ehthumbs.db
7 | Thumbs.db
8 | script_config.py
9 | *.log
10 | __pycache__
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Scripts for better notifications from Sonarr and Radarr
2 | Scripts that send better discord notifications from sonarr and radarr
3 |
4 | ## To install
5 | 1. `git clone https://github.com/samwiseg00/better-discord-notifications.git`
6 |
7 | 1. Download python dependencies `pip3 install -r requirements.txt`
8 |
9 | 1. Make a copy of `script_config.example.py` to `script_config.py`.
10 |
11 | 1. On sonarr/radarr add the script under, `settings > connect > Customscript` Run on download and on upgrade. Point to `radarr_discord.py` or `sonarr_discord.py`.
12 |
13 | _Note: Docker folder has been added and includes local copies of the modules required. It should work inside sonarr v3 and radarr v3 containers._
14 |
15 | ### Samples
16 |
17 |
18 |
19 | Project is inspired by https://github.com/Dec64/Better_slack_notifcations
20 |
--------------------------------------------------------------------------------
/docker/certifi/__init__.py:
--------------------------------------------------------------------------------
1 | from .core import where, old_where
2 |
3 | __version__ = "2017.11.05"
4 |
--------------------------------------------------------------------------------
/docker/certifi/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/certifi/__init__.pyc
--------------------------------------------------------------------------------
/docker/certifi/__main__.py:
--------------------------------------------------------------------------------
1 | from certifi import where
2 | print(where())
3 |
--------------------------------------------------------------------------------
/docker/certifi/core.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | certifi.py
6 | ~~~~~~~~~~
7 |
8 | This module returns the installation location of cacert.pem.
9 | """
10 | import os
11 | import warnings
12 |
13 |
14 | class DeprecatedBundleWarning(DeprecationWarning):
15 | """
16 | The weak security bundle is being deprecated. Please bother your service
17 | provider to get them to stop using cross-signed roots.
18 | """
19 |
20 |
21 | def where():
22 | f = os.path.dirname(__file__)
23 |
24 | return os.path.join(f, 'cacert.pem')
25 |
26 |
27 | def old_where():
28 | warnings.warn(
29 | "The weak security bundle is being deprecated. It will be removed in "
30 | "2018.",
31 | DeprecatedBundleWarning
32 | )
33 | f = os.path.dirname(__file__)
34 | return os.path.join(f, 'weak.pem')
35 |
36 | if __name__ == '__main__':
37 | print(where())
38 |
--------------------------------------------------------------------------------
/docker/certifi/core.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/certifi/core.pyc
--------------------------------------------------------------------------------
/docker/chardet/__init__.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # This library is free software; you can redistribute it and/or
3 | # modify it under the terms of the GNU Lesser General Public
4 | # License as published by the Free Software Foundation; either
5 | # version 2.1 of the License, or (at your option) any later version.
6 | #
7 | # This library is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 | # Lesser General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU Lesser General Public
13 | # License along with this library; if not, write to the Free Software
14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
15 | # 02110-1301 USA
16 | ######################### END LICENSE BLOCK #########################
17 |
18 |
19 | from .compat import PY2, PY3
20 | from .universaldetector import UniversalDetector
21 | from .version import __version__, VERSION
22 |
23 |
24 | def detect(byte_str):
25 | """
26 | Detect the encoding of the given byte string.
27 |
28 | :param byte_str: The byte sequence to examine.
29 | :type byte_str: ``bytes`` or ``bytearray``
30 | """
31 | if not isinstance(byte_str, bytearray):
32 | if not isinstance(byte_str, bytes):
33 | raise TypeError('Expected object of type bytes or bytearray, got: '
34 | '{0}'.format(type(byte_str)))
35 | else:
36 | byte_str = bytearray(byte_str)
37 | detector = UniversalDetector()
38 | detector.feed(byte_str)
39 | return detector.close()
40 |
--------------------------------------------------------------------------------
/docker/chardet/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/__init__.pyc
--------------------------------------------------------------------------------
/docker/chardet/big5freq.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/big5freq.pyc
--------------------------------------------------------------------------------
/docker/chardet/big5prober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Communicator client code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .mbcharsetprober import MultiByteCharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .chardistribution import Big5DistributionAnalysis
31 | from .mbcssm import BIG5_SM_MODEL
32 |
33 |
34 | class Big5Prober(MultiByteCharSetProber):
35 | def __init__(self):
36 | super(Big5Prober, self).__init__()
37 | self.coding_sm = CodingStateMachine(BIG5_SM_MODEL)
38 | self.distribution_analyzer = Big5DistributionAnalysis()
39 | self.reset()
40 |
41 | @property
42 | def charset_name(self):
43 | return "Big5"
44 |
45 | @property
46 | def language(self):
47 | return "Chinese"
48 |
--------------------------------------------------------------------------------
/docker/chardet/big5prober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/big5prober.pyc
--------------------------------------------------------------------------------
/docker/chardet/chardistribution.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/chardistribution.pyc
--------------------------------------------------------------------------------
/docker/chardet/charsetgroupprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Communicator client code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .enums import ProbingState
29 | from .charsetprober import CharSetProber
30 |
31 |
32 | class CharSetGroupProber(CharSetProber):
33 | def __init__(self, lang_filter=None):
34 | super(CharSetGroupProber, self).__init__(lang_filter=lang_filter)
35 | self._active_num = 0
36 | self.probers = []
37 | self._best_guess_prober = None
38 |
39 | def reset(self):
40 | super(CharSetGroupProber, self).reset()
41 | self._active_num = 0
42 | for prober in self.probers:
43 | if prober:
44 | prober.reset()
45 | prober.active = True
46 | self._active_num += 1
47 | self._best_guess_prober = None
48 |
49 | @property
50 | def charset_name(self):
51 | if not self._best_guess_prober:
52 | self.get_confidence()
53 | if not self._best_guess_prober:
54 | return None
55 | return self._best_guess_prober.charset_name
56 |
57 | @property
58 | def language(self):
59 | if not self._best_guess_prober:
60 | self.get_confidence()
61 | if not self._best_guess_prober:
62 | return None
63 | return self._best_guess_prober.language
64 |
65 | def feed(self, byte_str):
66 | for prober in self.probers:
67 | if not prober:
68 | continue
69 | if not prober.active:
70 | continue
71 | state = prober.feed(byte_str)
72 | if not state:
73 | continue
74 | if state == ProbingState.FOUND_IT:
75 | self._best_guess_prober = prober
76 | return self.state
77 | elif state == ProbingState.NOT_ME:
78 | prober.active = False
79 | self._active_num -= 1
80 | if self._active_num <= 0:
81 | self._state = ProbingState.NOT_ME
82 | return self.state
83 | return self.state
84 |
85 | def get_confidence(self):
86 | state = self.state
87 | if state == ProbingState.FOUND_IT:
88 | return 0.99
89 | elif state == ProbingState.NOT_ME:
90 | return 0.01
91 | best_conf = 0.0
92 | self._best_guess_prober = None
93 | for prober in self.probers:
94 | if not prober:
95 | continue
96 | if not prober.active:
97 | self.logger.debug('%s not active', prober.charset_name)
98 | continue
99 | conf = prober.get_confidence()
100 | self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf)
101 | if best_conf < conf:
102 | best_conf = conf
103 | self._best_guess_prober = prober
104 | if not self._best_guess_prober:
105 | return 0.0
106 | return best_conf
107 |
--------------------------------------------------------------------------------
/docker/chardet/charsetgroupprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/charsetgroupprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/charsetprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | #
13 | # This library is free software; you can redistribute it and/or
14 | # modify it under the terms of the GNU Lesser General Public
15 | # License as published by the Free Software Foundation; either
16 | # version 2.1 of the License, or (at your option) any later version.
17 | #
18 | # This library is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 | # Lesser General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU Lesser General Public
24 | # License along with this library; if not, write to the Free Software
25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 | # 02110-1301 USA
27 | ######################### END LICENSE BLOCK #########################
28 |
29 | import logging
30 | import re
31 |
32 | from .enums import ProbingState
33 |
34 |
35 | class CharSetProber(object):
36 |
37 | SHORTCUT_THRESHOLD = 0.95
38 |
39 | def __init__(self, lang_filter=None):
40 | self._state = None
41 | self.lang_filter = lang_filter
42 | self.logger = logging.getLogger(__name__)
43 |
44 | def reset(self):
45 | self._state = ProbingState.DETECTING
46 |
47 | @property
48 | def charset_name(self):
49 | return None
50 |
51 | def feed(self, buf):
52 | pass
53 |
54 | @property
55 | def state(self):
56 | return self._state
57 |
58 | def get_confidence(self):
59 | return 0.0
60 |
61 | @staticmethod
62 | def filter_high_byte_only(buf):
63 | buf = re.sub(b'([\x00-\x7F])+', b' ', buf)
64 | return buf
65 |
66 | @staticmethod
67 | def filter_international_words(buf):
68 | """
69 | We define three types of bytes:
70 | alphabet: english alphabets [a-zA-Z]
71 | international: international characters [\x80-\xFF]
72 | marker: everything else [^a-zA-Z\x80-\xFF]
73 |
74 | The input buffer can be thought to contain a series of words delimited
75 | by markers. This function works to filter all words that contain at
76 | least one international character. All contiguous sequences of markers
77 | are replaced by a single space ascii character.
78 |
79 | This filter applies to all scripts which do not use English characters.
80 | """
81 | filtered = bytearray()
82 |
83 | # This regex expression filters out only words that have at-least one
84 | # international character. The word may include one marker character at
85 | # the end.
86 | words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?',
87 | buf)
88 |
89 | for word in words:
90 | filtered.extend(word[:-1])
91 |
92 | # If the last character in the word is a marker, replace it with a
93 | # space as markers shouldn't affect our analysis (they are used
94 | # similarly across all languages and may thus have similar
95 | # frequencies).
96 | last_char = word[-1:]
97 | if not last_char.isalpha() and last_char < b'\x80':
98 | last_char = b' '
99 | filtered.extend(last_char)
100 |
101 | return filtered
102 |
103 | @staticmethod
104 | def filter_with_english_letters(buf):
105 | """
106 | Returns a copy of ``buf`` that retains only the sequences of English
107 | alphabet and high byte characters that are not between <> characters.
108 | Also retains English alphabet and high byte characters immediately
109 | before occurrences of >.
110 |
111 | This filter can be applied to all scripts which contain both English
112 | characters and extended ASCII characters, but is currently only used by
113 | ``Latin1Prober``.
114 | """
115 | filtered = bytearray()
116 | in_tag = False
117 | prev = 0
118 |
119 | for curr in range(len(buf)):
120 | # Slice here to get bytes instead of an int with Python 3
121 | buf_char = buf[curr:curr + 1]
122 | # Check if we're coming out of or entering an HTML tag
123 | if buf_char == b'>':
124 | in_tag = False
125 | elif buf_char == b'<':
126 | in_tag = True
127 |
128 | # If current character is not extended-ASCII and not alphabetic...
129 | if buf_char < b'\x80' and not buf_char.isalpha():
130 | # ...and we're not in a tag
131 | if curr > prev and not in_tag:
132 | # Keep everything after last non-extended-ASCII,
133 | # non-alphabetic character
134 | filtered.extend(buf[prev:curr])
135 | # Output a space to delimit stretch we kept
136 | filtered.extend(b' ')
137 | prev = curr + 1
138 |
139 | # If we're not in a tag...
140 | if not in_tag:
141 | # Keep everything after last non-extended-ASCII, non-alphabetic
142 | # character
143 | filtered.extend(buf[prev:])
144 |
145 | return filtered
146 |
--------------------------------------------------------------------------------
/docker/chardet/charsetprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/charsetprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/cli/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docker/chardet/cli/chardetect.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Script which takes one or more file paths and reports on their detected
4 | encodings
5 |
6 | Example::
7 |
8 | % chardetect somefile someotherfile
9 | somefile: windows-1252 with confidence 0.5
10 | someotherfile: ascii with confidence 1.0
11 |
12 | If no paths are provided, it takes its input from stdin.
13 |
14 | """
15 |
16 | from __future__ import absolute_import, print_function, unicode_literals
17 |
18 | import argparse
19 | import sys
20 |
21 | from chardet import __version__
22 | from chardet.compat import PY2
23 | from chardet.universaldetector import UniversalDetector
24 |
25 |
26 | def description_of(lines, name='stdin'):
27 | """
28 | Return a string describing the probable encoding of a file or
29 | list of strings.
30 |
31 | :param lines: The lines to get the encoding of.
32 | :type lines: Iterable of bytes
33 | :param name: Name of file or collection of lines
34 | :type name: str
35 | """
36 | u = UniversalDetector()
37 | for line in lines:
38 | line = bytearray(line)
39 | u.feed(line)
40 | # shortcut out of the loop to save reading further - particularly useful if we read a BOM.
41 | if u.done:
42 | break
43 | u.close()
44 | result = u.result
45 | if PY2:
46 | name = name.decode(sys.getfilesystemencoding(), 'ignore')
47 | if result['encoding']:
48 | return '{0}: {1} with confidence {2}'.format(name, result['encoding'],
49 | result['confidence'])
50 | else:
51 | return '{0}: no result'.format(name)
52 |
53 |
54 | def main(argv=None):
55 | """
56 | Handles command line arguments and gets things started.
57 |
58 | :param argv: List of arguments, as if specified on the command-line.
59 | If None, ``sys.argv[1:]`` is used instead.
60 | :type argv: list of str
61 | """
62 | # Get command line arguments
63 | parser = argparse.ArgumentParser(
64 | description="Takes one or more file paths and reports their detected \
65 | encodings")
66 | parser.add_argument('input',
67 | help='File whose encoding we would like to determine. \
68 | (default: stdin)',
69 | type=argparse.FileType('rb'), nargs='*',
70 | default=[sys.stdin if PY2 else sys.stdin.buffer])
71 | parser.add_argument('--version', action='version',
72 | version='%(prog)s {0}'.format(__version__))
73 | args = parser.parse_args(argv)
74 |
75 | for f in args.input:
76 | if f.isatty():
77 | print("You are running chardetect interactively. Press " +
78 | "CTRL-D twice at the start of a blank line to signal the " +
79 | "end of your input. If you want help, run chardetect " +
80 | "--help\n", file=sys.stderr)
81 | print(description_of(f, f.name))
82 |
83 |
84 | if __name__ == '__main__':
85 | main()
86 |
--------------------------------------------------------------------------------
/docker/chardet/codingstatemachine.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | import logging
29 |
30 | from .enums import MachineState
31 |
32 |
33 | class CodingStateMachine(object):
34 | """
35 | A state machine to verify a byte sequence for a particular encoding. For
36 | each byte the detector receives, it will feed that byte to every active
37 | state machine available, one byte at a time. The state machine changes its
38 | state based on its previous state and the byte it receives. There are 3
39 | states in a state machine that are of interest to an auto-detector:
40 |
41 | START state: This is the state to start with, or a legal byte sequence
42 | (i.e. a valid code point) for character has been identified.
43 |
44 | ME state: This indicates that the state machine identified a byte sequence
45 | that is specific to the charset it is designed for and that
46 | there is no other possible encoding which can contain this byte
47 | sequence. This will to lead to an immediate positive answer for
48 | the detector.
49 |
50 | ERROR state: This indicates the state machine identified an illegal byte
51 | sequence for that encoding. This will lead to an immediate
52 | negative answer for this encoding. Detector will exclude this
53 | encoding from consideration from here on.
54 | """
55 | def __init__(self, sm):
56 | self._model = sm
57 | self._curr_byte_pos = 0
58 | self._curr_char_len = 0
59 | self._curr_state = None
60 | self.logger = logging.getLogger(__name__)
61 | self.reset()
62 |
63 | def reset(self):
64 | self._curr_state = MachineState.START
65 |
66 | def next_state(self, c):
67 | # for each byte we get its class
68 | # if it is first byte, we also get byte length
69 | byte_class = self._model['class_table'][c]
70 | if self._curr_state == MachineState.START:
71 | self._curr_byte_pos = 0
72 | self._curr_char_len = self._model['char_len_table'][byte_class]
73 | # from byte's class and state_table, we get its next state
74 | curr_state = (self._curr_state * self._model['class_factor']
75 | + byte_class)
76 | self._curr_state = self._model['state_table'][curr_state]
77 | self._curr_byte_pos += 1
78 | return self._curr_state
79 |
80 | def get_current_charlen(self):
81 | return self._curr_char_len
82 |
83 | def get_coding_state_machine(self):
84 | return self._model['name']
85 |
86 | @property
87 | def language(self):
88 | return self._model['language']
89 |
--------------------------------------------------------------------------------
/docker/chardet/codingstatemachine.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/codingstatemachine.pyc
--------------------------------------------------------------------------------
/docker/chardet/compat.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # Contributor(s):
3 | # Dan Blanchard
4 | # Ian Cordasco
5 | #
6 | # This library is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU Lesser General Public
8 | # License as published by the Free Software Foundation; either
9 | # version 2.1 of the License, or (at your option) any later version.
10 | #
11 | # This library is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | # Lesser General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU Lesser General Public
17 | # License along with this library; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 | # 02110-1301 USA
20 | ######################### END LICENSE BLOCK #########################
21 |
22 | import sys
23 |
24 |
25 | if sys.version_info < (3, 0):
26 | PY2 = True
27 | PY3 = False
28 | base_str = (str, unicode)
29 | text_type = unicode
30 | else:
31 | PY2 = False
32 | PY3 = True
33 | base_str = (bytes, str)
34 | text_type = str
35 |
--------------------------------------------------------------------------------
/docker/chardet/compat.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/compat.pyc
--------------------------------------------------------------------------------
/docker/chardet/cp949prober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .chardistribution import EUCKRDistributionAnalysis
29 | from .codingstatemachine import CodingStateMachine
30 | from .mbcharsetprober import MultiByteCharSetProber
31 | from .mbcssm import CP949_SM_MODEL
32 |
33 |
34 | class CP949Prober(MultiByteCharSetProber):
35 | def __init__(self):
36 | super(CP949Prober, self).__init__()
37 | self.coding_sm = CodingStateMachine(CP949_SM_MODEL)
38 | # NOTE: CP949 is a superset of EUC-KR, so the distribution should be
39 | # not different.
40 | self.distribution_analyzer = EUCKRDistributionAnalysis()
41 | self.reset()
42 |
43 | @property
44 | def charset_name(self):
45 | return "CP949"
46 |
47 | @property
48 | def language(self):
49 | return "Korean"
50 |
--------------------------------------------------------------------------------
/docker/chardet/cp949prober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/cp949prober.pyc
--------------------------------------------------------------------------------
/docker/chardet/enums.py:
--------------------------------------------------------------------------------
1 | """
2 | All of the Enums that are used throughout the chardet package.
3 |
4 | :author: Dan Blanchard (dan.blanchard@gmail.com)
5 | """
6 |
7 |
8 | class InputState(object):
9 | """
10 | This enum represents the different states a universal detector can be in.
11 | """
12 | PURE_ASCII = 0
13 | ESC_ASCII = 1
14 | HIGH_BYTE = 2
15 |
16 |
17 | class LanguageFilter(object):
18 | """
19 | This enum represents the different language filters we can apply to a
20 | ``UniversalDetector``.
21 | """
22 | CHINESE_SIMPLIFIED = 0x01
23 | CHINESE_TRADITIONAL = 0x02
24 | JAPANESE = 0x04
25 | KOREAN = 0x08
26 | NON_CJK = 0x10
27 | ALL = 0x1F
28 | CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL
29 | CJK = CHINESE | JAPANESE | KOREAN
30 |
31 |
32 | class ProbingState(object):
33 | """
34 | This enum represents the different states a prober can be in.
35 | """
36 | DETECTING = 0
37 | FOUND_IT = 1
38 | NOT_ME = 2
39 |
40 |
41 | class MachineState(object):
42 | """
43 | This enum represents the different states a state machine can be in.
44 | """
45 | START = 0
46 | ERROR = 1
47 | ITS_ME = 2
48 |
49 |
50 | class SequenceLikelihood(object):
51 | """
52 | This enum represents the likelihood of a character following the previous one.
53 | """
54 | NEGATIVE = 0
55 | UNLIKELY = 1
56 | LIKELY = 2
57 | POSITIVE = 3
58 |
59 | @classmethod
60 | def get_num_categories(cls):
61 | """:returns: The number of likelihood categories in the enum."""
62 | return 4
63 |
64 |
65 | class CharacterCategory(object):
66 | """
67 | This enum represents the different categories language models for
68 | ``SingleByteCharsetProber`` put characters into.
69 |
70 | Anything less than CONTROL is considered a letter.
71 | """
72 | UNDEFINED = 255
73 | LINE_BREAK = 254
74 | SYMBOL = 253
75 | DIGIT = 252
76 | CONTROL = 251
77 |
--------------------------------------------------------------------------------
/docker/chardet/enums.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/enums.pyc
--------------------------------------------------------------------------------
/docker/chardet/escprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .charsetprober import CharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .enums import LanguageFilter, ProbingState, MachineState
31 | from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL,
32 | ISO2022KR_SM_MODEL)
33 |
34 |
35 | class EscCharSetProber(CharSetProber):
36 | """
37 | This CharSetProber uses a "code scheme" approach for detecting encodings,
38 | whereby easily recognizable escape or shift sequences are relied on to
39 | identify these encodings.
40 | """
41 |
42 | def __init__(self, lang_filter=None):
43 | super(EscCharSetProber, self).__init__(lang_filter=lang_filter)
44 | self.coding_sm = []
45 | if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED:
46 | self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL))
47 | self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL))
48 | if self.lang_filter & LanguageFilter.JAPANESE:
49 | self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL))
50 | if self.lang_filter & LanguageFilter.KOREAN:
51 | self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL))
52 | self.active_sm_count = None
53 | self._detected_charset = None
54 | self._detected_language = None
55 | self._state = None
56 | self.reset()
57 |
58 | def reset(self):
59 | super(EscCharSetProber, self).reset()
60 | for coding_sm in self.coding_sm:
61 | if not coding_sm:
62 | continue
63 | coding_sm.active = True
64 | coding_sm.reset()
65 | self.active_sm_count = len(self.coding_sm)
66 | self._detected_charset = None
67 | self._detected_language = None
68 |
69 | @property
70 | def charset_name(self):
71 | return self._detected_charset
72 |
73 | @property
74 | def language(self):
75 | return self._detected_language
76 |
77 | def get_confidence(self):
78 | if self._detected_charset:
79 | return 0.99
80 | else:
81 | return 0.00
82 |
83 | def feed(self, byte_str):
84 | for c in byte_str:
85 | for coding_sm in self.coding_sm:
86 | if not coding_sm or not coding_sm.active:
87 | continue
88 | coding_state = coding_sm.next_state(c)
89 | if coding_state == MachineState.ERROR:
90 | coding_sm.active = False
91 | self.active_sm_count -= 1
92 | if self.active_sm_count <= 0:
93 | self._state = ProbingState.NOT_ME
94 | return self.state
95 | elif coding_state == MachineState.ITS_ME:
96 | self._state = ProbingState.FOUND_IT
97 | self._detected_charset = coding_sm.get_coding_state_machine()
98 | self._detected_language = coding_sm.language
99 | return self.state
100 |
101 | return self.state
102 |
--------------------------------------------------------------------------------
/docker/chardet/escprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/escprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/escsm.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/escsm.pyc
--------------------------------------------------------------------------------
/docker/chardet/eucjpprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .enums import ProbingState, MachineState
29 | from .mbcharsetprober import MultiByteCharSetProber
30 | from .codingstatemachine import CodingStateMachine
31 | from .chardistribution import EUCJPDistributionAnalysis
32 | from .jpcntx import EUCJPContextAnalysis
33 | from .mbcssm import EUCJP_SM_MODEL
34 |
35 |
36 | class EUCJPProber(MultiByteCharSetProber):
37 | def __init__(self):
38 | super(EUCJPProber, self).__init__()
39 | self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL)
40 | self.distribution_analyzer = EUCJPDistributionAnalysis()
41 | self.context_analyzer = EUCJPContextAnalysis()
42 | self.reset()
43 |
44 | def reset(self):
45 | super(EUCJPProber, self).reset()
46 | self.context_analyzer.reset()
47 |
48 | @property
49 | def charset_name(self):
50 | return "EUC-JP"
51 |
52 | @property
53 | def language(self):
54 | return "Japanese"
55 |
56 | def feed(self, byte_str):
57 | for i in range(len(byte_str)):
58 | # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte
59 | coding_state = self.coding_sm.next_state(byte_str[i])
60 | if coding_state == MachineState.ERROR:
61 | self.logger.debug('%s %s prober hit error at byte %s',
62 | self.charset_name, self.language, i)
63 | self._state = ProbingState.NOT_ME
64 | break
65 | elif coding_state == MachineState.ITS_ME:
66 | self._state = ProbingState.FOUND_IT
67 | break
68 | elif coding_state == MachineState.START:
69 | char_len = self.coding_sm.get_current_charlen()
70 | if i == 0:
71 | self._last_char[1] = byte_str[0]
72 | self.context_analyzer.feed(self._last_char, char_len)
73 | self.distribution_analyzer.feed(self._last_char, char_len)
74 | else:
75 | self.context_analyzer.feed(byte_str[i - 1:i + 1],
76 | char_len)
77 | self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
78 | char_len)
79 |
80 | self._last_char[0] = byte_str[-1]
81 |
82 | if self.state == ProbingState.DETECTING:
83 | if (self.context_analyzer.got_enough_data() and
84 | (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
85 | self._state = ProbingState.FOUND_IT
86 |
87 | return self.state
88 |
89 | def get_confidence(self):
90 | context_conf = self.context_analyzer.get_confidence()
91 | distrib_conf = self.distribution_analyzer.get_confidence()
92 | return max(context_conf, distrib_conf)
93 |
--------------------------------------------------------------------------------
/docker/chardet/eucjpprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/eucjpprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/euckrfreq.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/euckrfreq.pyc
--------------------------------------------------------------------------------
/docker/chardet/euckrprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .mbcharsetprober import MultiByteCharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .chardistribution import EUCKRDistributionAnalysis
31 | from .mbcssm import EUCKR_SM_MODEL
32 |
33 |
34 | class EUCKRProber(MultiByteCharSetProber):
35 | def __init__(self):
36 | super(EUCKRProber, self).__init__()
37 | self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL)
38 | self.distribution_analyzer = EUCKRDistributionAnalysis()
39 | self.reset()
40 |
41 | @property
42 | def charset_name(self):
43 | return "EUC-KR"
44 |
45 | @property
46 | def language(self):
47 | return "Korean"
48 |
--------------------------------------------------------------------------------
/docker/chardet/euckrprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/euckrprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/euctwfreq.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/euctwfreq.pyc
--------------------------------------------------------------------------------
/docker/chardet/euctwprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .mbcharsetprober import MultiByteCharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .chardistribution import EUCTWDistributionAnalysis
31 | from .mbcssm import EUCTW_SM_MODEL
32 |
33 | class EUCTWProber(MultiByteCharSetProber):
34 | def __init__(self):
35 | super(EUCTWProber, self).__init__()
36 | self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL)
37 | self.distribution_analyzer = EUCTWDistributionAnalysis()
38 | self.reset()
39 |
40 | @property
41 | def charset_name(self):
42 | return "EUC-TW"
43 |
44 | @property
45 | def language(self):
46 | return "Taiwan"
47 |
--------------------------------------------------------------------------------
/docker/chardet/euctwprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/euctwprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/gb2312freq.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/gb2312freq.pyc
--------------------------------------------------------------------------------
/docker/chardet/gb2312prober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .mbcharsetprober import MultiByteCharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .chardistribution import GB2312DistributionAnalysis
31 | from .mbcssm import GB2312_SM_MODEL
32 |
33 | class GB2312Prober(MultiByteCharSetProber):
34 | def __init__(self):
35 | super(GB2312Prober, self).__init__()
36 | self.coding_sm = CodingStateMachine(GB2312_SM_MODEL)
37 | self.distribution_analyzer = GB2312DistributionAnalysis()
38 | self.reset()
39 |
40 | @property
41 | def charset_name(self):
42 | return "GB2312"
43 |
44 | @property
45 | def language(self):
46 | return "Chinese"
47 |
--------------------------------------------------------------------------------
/docker/chardet/gb2312prober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/gb2312prober.pyc
--------------------------------------------------------------------------------
/docker/chardet/hebrewprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/hebrewprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/jisfreq.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/jisfreq.pyc
--------------------------------------------------------------------------------
/docker/chardet/jpcntx.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/jpcntx.pyc
--------------------------------------------------------------------------------
/docker/chardet/langbulgarianmodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langbulgarianmodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/langcyrillicmodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langcyrillicmodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/langgreekmodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langgreekmodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/langhebrewmodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langhebrewmodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/langthaimodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langthaimodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/langturkishmodel.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/langturkishmodel.pyc
--------------------------------------------------------------------------------
/docker/chardet/latin1prober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | #
13 | # This library is free software; you can redistribute it and/or
14 | # modify it under the terms of the GNU Lesser General Public
15 | # License as published by the Free Software Foundation; either
16 | # version 2.1 of the License, or (at your option) any later version.
17 | #
18 | # This library is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 | # Lesser General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU Lesser General Public
24 | # License along with this library; if not, write to the Free Software
25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 | # 02110-1301 USA
27 | ######################### END LICENSE BLOCK #########################
28 |
29 | from .charsetprober import CharSetProber
30 | from .enums import ProbingState
31 |
32 | FREQ_CAT_NUM = 4
33 |
34 | UDF = 0 # undefined
35 | OTH = 1 # other
36 | ASC = 2 # ascii capital letter
37 | ASS = 3 # ascii small letter
38 | ACV = 4 # accent capital vowel
39 | ACO = 5 # accent capital other
40 | ASV = 6 # accent small vowel
41 | ASO = 7 # accent small other
42 | CLASS_NUM = 8 # total classes
43 |
44 | Latin1_CharToClass = (
45 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07
46 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F
47 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17
48 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F
49 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27
50 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F
51 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37
52 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F
53 | OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47
54 | ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F
55 | ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57
56 | ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F
57 | OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67
58 | ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F
59 | ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77
60 | ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F
61 | OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87
62 | OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F
63 | UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97
64 | OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F
65 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7
66 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF
67 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7
68 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF
69 | ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7
70 | ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF
71 | ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7
72 | ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF
73 | ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7
74 | ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF
75 | ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7
76 | ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF
77 | )
78 |
79 | # 0 : illegal
80 | # 1 : very unlikely
81 | # 2 : normal
82 | # 3 : very likely
83 | Latin1ClassModel = (
84 | # UDF OTH ASC ASS ACV ACO ASV ASO
85 | 0, 0, 0, 0, 0, 0, 0, 0, # UDF
86 | 0, 3, 3, 3, 3, 3, 3, 3, # OTH
87 | 0, 3, 3, 3, 3, 3, 3, 3, # ASC
88 | 0, 3, 3, 3, 1, 1, 3, 3, # ASS
89 | 0, 3, 3, 3, 1, 2, 1, 2, # ACV
90 | 0, 3, 3, 3, 3, 3, 3, 3, # ACO
91 | 0, 3, 1, 3, 1, 1, 1, 3, # ASV
92 | 0, 3, 1, 3, 1, 1, 3, 3, # ASO
93 | )
94 |
95 |
96 | class Latin1Prober(CharSetProber):
97 | def __init__(self):
98 | super(Latin1Prober, self).__init__()
99 | self._last_char_class = None
100 | self._freq_counter = None
101 | self.reset()
102 |
103 | def reset(self):
104 | self._last_char_class = OTH
105 | self._freq_counter = [0] * FREQ_CAT_NUM
106 | CharSetProber.reset(self)
107 |
108 | @property
109 | def charset_name(self):
110 | return "ISO-8859-1"
111 |
112 | @property
113 | def language(self):
114 | return ""
115 |
116 | def feed(self, byte_str):
117 | byte_str = self.filter_with_english_letters(byte_str)
118 | for c in byte_str:
119 | char_class = Latin1_CharToClass[c]
120 | freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM)
121 | + char_class]
122 | if freq == 0:
123 | self._state = ProbingState.NOT_ME
124 | break
125 | self._freq_counter[freq] += 1
126 | self._last_char_class = char_class
127 |
128 | return self.state
129 |
130 | def get_confidence(self):
131 | if self.state == ProbingState.NOT_ME:
132 | return 0.01
133 |
134 | total = sum(self._freq_counter)
135 | if total < 0.01:
136 | confidence = 0.0
137 | else:
138 | confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0)
139 | / total)
140 | if confidence < 0.0:
141 | confidence = 0.0
142 | # lower the confidence of latin1 so that other more accurate
143 | # detector can take priority.
144 | confidence = confidence * 0.73
145 | return confidence
146 |
--------------------------------------------------------------------------------
/docker/chardet/latin1prober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/latin1prober.pyc
--------------------------------------------------------------------------------
/docker/chardet/mbcharsetprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | # Proofpoint, Inc.
13 | #
14 | # This library is free software; you can redistribute it and/or
15 | # modify it under the terms of the GNU Lesser General Public
16 | # License as published by the Free Software Foundation; either
17 | # version 2.1 of the License, or (at your option) any later version.
18 | #
19 | # This library is distributed in the hope that it will be useful,
20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 | # Lesser General Public License for more details.
23 | #
24 | # You should have received a copy of the GNU Lesser General Public
25 | # License along with this library; if not, write to the Free Software
26 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
27 | # 02110-1301 USA
28 | ######################### END LICENSE BLOCK #########################
29 |
30 | from .charsetprober import CharSetProber
31 | from .enums import ProbingState, MachineState
32 |
33 |
34 | class MultiByteCharSetProber(CharSetProber):
35 | """
36 | MultiByteCharSetProber
37 | """
38 |
39 | def __init__(self, lang_filter=None):
40 | super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter)
41 | self.distribution_analyzer = None
42 | self.coding_sm = None
43 | self._last_char = [0, 0]
44 |
45 | def reset(self):
46 | super(MultiByteCharSetProber, self).reset()
47 | if self.coding_sm:
48 | self.coding_sm.reset()
49 | if self.distribution_analyzer:
50 | self.distribution_analyzer.reset()
51 | self._last_char = [0, 0]
52 |
53 | @property
54 | def charset_name(self):
55 | raise NotImplementedError
56 |
57 | @property
58 | def language(self):
59 | raise NotImplementedError
60 |
61 | def feed(self, byte_str):
62 | for i in range(len(byte_str)):
63 | coding_state = self.coding_sm.next_state(byte_str[i])
64 | if coding_state == MachineState.ERROR:
65 | self.logger.debug('%s %s prober hit error at byte %s',
66 | self.charset_name, self.language, i)
67 | self._state = ProbingState.NOT_ME
68 | break
69 | elif coding_state == MachineState.ITS_ME:
70 | self._state = ProbingState.FOUND_IT
71 | break
72 | elif coding_state == MachineState.START:
73 | char_len = self.coding_sm.get_current_charlen()
74 | if i == 0:
75 | self._last_char[1] = byte_str[0]
76 | self.distribution_analyzer.feed(self._last_char, char_len)
77 | else:
78 | self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
79 | char_len)
80 |
81 | self._last_char[0] = byte_str[-1]
82 |
83 | if self.state == ProbingState.DETECTING:
84 | if (self.distribution_analyzer.got_enough_data() and
85 | (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
86 | self._state = ProbingState.FOUND_IT
87 |
88 | return self.state
89 |
90 | def get_confidence(self):
91 | return self.distribution_analyzer.get_confidence()
92 |
--------------------------------------------------------------------------------
/docker/chardet/mbcharsetprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/mbcharsetprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/mbcsgroupprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | # Proofpoint, Inc.
13 | #
14 | # This library is free software; you can redistribute it and/or
15 | # modify it under the terms of the GNU Lesser General Public
16 | # License as published by the Free Software Foundation; either
17 | # version 2.1 of the License, or (at your option) any later version.
18 | #
19 | # This library is distributed in the hope that it will be useful,
20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 | # Lesser General Public License for more details.
23 | #
24 | # You should have received a copy of the GNU Lesser General Public
25 | # License along with this library; if not, write to the Free Software
26 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
27 | # 02110-1301 USA
28 | ######################### END LICENSE BLOCK #########################
29 |
30 | from .charsetgroupprober import CharSetGroupProber
31 | from .utf8prober import UTF8Prober
32 | from .sjisprober import SJISProber
33 | from .eucjpprober import EUCJPProber
34 | from .gb2312prober import GB2312Prober
35 | from .euckrprober import EUCKRProber
36 | from .cp949prober import CP949Prober
37 | from .big5prober import Big5Prober
38 | from .euctwprober import EUCTWProber
39 |
40 |
41 | class MBCSGroupProber(CharSetGroupProber):
42 | def __init__(self, lang_filter=None):
43 | super(MBCSGroupProber, self).__init__(lang_filter=lang_filter)
44 | self.probers = [
45 | UTF8Prober(),
46 | SJISProber(),
47 | EUCJPProber(),
48 | GB2312Prober(),
49 | EUCKRProber(),
50 | CP949Prober(),
51 | Big5Prober(),
52 | EUCTWProber()
53 | ]
54 | self.reset()
55 |
--------------------------------------------------------------------------------
/docker/chardet/mbcsgroupprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/mbcsgroupprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/mbcssm.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/mbcssm.pyc
--------------------------------------------------------------------------------
/docker/chardet/sbcharsetprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | #
13 | # This library is free software; you can redistribute it and/or
14 | # modify it under the terms of the GNU Lesser General Public
15 | # License as published by the Free Software Foundation; either
16 | # version 2.1 of the License, or (at your option) any later version.
17 | #
18 | # This library is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 | # Lesser General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU Lesser General Public
24 | # License along with this library; if not, write to the Free Software
25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 | # 02110-1301 USA
27 | ######################### END LICENSE BLOCK #########################
28 |
29 | from .charsetprober import CharSetProber
30 | from .enums import CharacterCategory, ProbingState, SequenceLikelihood
31 |
32 |
33 | class SingleByteCharSetProber(CharSetProber):
34 | SAMPLE_SIZE = 64
35 | SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2
36 | POSITIVE_SHORTCUT_THRESHOLD = 0.95
37 | NEGATIVE_SHORTCUT_THRESHOLD = 0.05
38 |
39 | def __init__(self, model, reversed=False, name_prober=None):
40 | super(SingleByteCharSetProber, self).__init__()
41 | self._model = model
42 | # TRUE if we need to reverse every pair in the model lookup
43 | self._reversed = reversed
44 | # Optional auxiliary prober for name decision
45 | self._name_prober = name_prober
46 | self._last_order = None
47 | self._seq_counters = None
48 | self._total_seqs = None
49 | self._total_char = None
50 | self._freq_char = None
51 | self.reset()
52 |
53 | def reset(self):
54 | super(SingleByteCharSetProber, self).reset()
55 | # char order of last character
56 | self._last_order = 255
57 | self._seq_counters = [0] * SequenceLikelihood.get_num_categories()
58 | self._total_seqs = 0
59 | self._total_char = 0
60 | # characters that fall in our sampling range
61 | self._freq_char = 0
62 |
63 | @property
64 | def charset_name(self):
65 | if self._name_prober:
66 | return self._name_prober.charset_name
67 | else:
68 | return self._model['charset_name']
69 |
70 | @property
71 | def language(self):
72 | if self._name_prober:
73 | return self._name_prober.language
74 | else:
75 | return self._model.get('language')
76 |
77 | def feed(self, byte_str):
78 | if not self._model['keep_english_letter']:
79 | byte_str = self.filter_international_words(byte_str)
80 | if not byte_str:
81 | return self.state
82 | char_to_order_map = self._model['char_to_order_map']
83 | for i, c in enumerate(byte_str):
84 | # XXX: Order is in range 1-64, so one would think we want 0-63 here,
85 | # but that leads to 27 more test failures than before.
86 | order = char_to_order_map[c]
87 | # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but
88 | # CharacterCategory.SYMBOL is actually 253, so we use CONTROL
89 | # to make it closer to the original intent. The only difference
90 | # is whether or not we count digits and control characters for
91 | # _total_char purposes.
92 | if order < CharacterCategory.CONTROL:
93 | self._total_char += 1
94 | if order < self.SAMPLE_SIZE:
95 | self._freq_char += 1
96 | if self._last_order < self.SAMPLE_SIZE:
97 | self._total_seqs += 1
98 | if not self._reversed:
99 | i = (self._last_order * self.SAMPLE_SIZE) + order
100 | model = self._model['precedence_matrix'][i]
101 | else: # reverse the order of the letters in the lookup
102 | i = (order * self.SAMPLE_SIZE) + self._last_order
103 | model = self._model['precedence_matrix'][i]
104 | self._seq_counters[model] += 1
105 | self._last_order = order
106 |
107 | charset_name = self._model['charset_name']
108 | if self.state == ProbingState.DETECTING:
109 | if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD:
110 | confidence = self.get_confidence()
111 | if confidence > self.POSITIVE_SHORTCUT_THRESHOLD:
112 | self.logger.debug('%s confidence = %s, we have a winner',
113 | charset_name, confidence)
114 | self._state = ProbingState.FOUND_IT
115 | elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD:
116 | self.logger.debug('%s confidence = %s, below negative '
117 | 'shortcut threshhold %s', charset_name,
118 | confidence,
119 | self.NEGATIVE_SHORTCUT_THRESHOLD)
120 | self._state = ProbingState.NOT_ME
121 |
122 | return self.state
123 |
124 | def get_confidence(self):
125 | r = 0.01
126 | if self._total_seqs > 0:
127 | r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) /
128 | self._total_seqs / self._model['typical_positive_ratio'])
129 | r = r * self._freq_char / self._total_char
130 | if r >= 1.0:
131 | r = 0.99
132 | return r
133 |
--------------------------------------------------------------------------------
/docker/chardet/sbcharsetprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/sbcharsetprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/sbcsgroupprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is Mozilla Universal charset detector code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 2001
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | # Shy Shalom - original C code
12 | #
13 | # This library is free software; you can redistribute it and/or
14 | # modify it under the terms of the GNU Lesser General Public
15 | # License as published by the Free Software Foundation; either
16 | # version 2.1 of the License, or (at your option) any later version.
17 | #
18 | # This library is distributed in the hope that it will be useful,
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 | # Lesser General Public License for more details.
22 | #
23 | # You should have received a copy of the GNU Lesser General Public
24 | # License along with this library; if not, write to the Free Software
25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 | # 02110-1301 USA
27 | ######################### END LICENSE BLOCK #########################
28 |
29 | from .charsetgroupprober import CharSetGroupProber
30 | from .sbcharsetprober import SingleByteCharSetProber
31 | from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel,
32 | Latin5CyrillicModel, MacCyrillicModel,
33 | Ibm866Model, Ibm855Model)
34 | from .langgreekmodel import Latin7GreekModel, Win1253GreekModel
35 | from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel
36 | # from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel
37 | from .langthaimodel import TIS620ThaiModel
38 | from .langhebrewmodel import Win1255HebrewModel
39 | from .hebrewprober import HebrewProber
40 | from .langturkishmodel import Latin5TurkishModel
41 |
42 |
43 | class SBCSGroupProber(CharSetGroupProber):
44 | def __init__(self):
45 | super(SBCSGroupProber, self).__init__()
46 | self.probers = [
47 | SingleByteCharSetProber(Win1251CyrillicModel),
48 | SingleByteCharSetProber(Koi8rModel),
49 | SingleByteCharSetProber(Latin5CyrillicModel),
50 | SingleByteCharSetProber(MacCyrillicModel),
51 | SingleByteCharSetProber(Ibm866Model),
52 | SingleByteCharSetProber(Ibm855Model),
53 | SingleByteCharSetProber(Latin7GreekModel),
54 | SingleByteCharSetProber(Win1253GreekModel),
55 | SingleByteCharSetProber(Latin5BulgarianModel),
56 | SingleByteCharSetProber(Win1251BulgarianModel),
57 | # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250)
58 | # after we retrain model.
59 | # SingleByteCharSetProber(Latin2HungarianModel),
60 | # SingleByteCharSetProber(Win1250HungarianModel),
61 | SingleByteCharSetProber(TIS620ThaiModel),
62 | SingleByteCharSetProber(Latin5TurkishModel),
63 | ]
64 | hebrew_prober = HebrewProber()
65 | logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel,
66 | False, hebrew_prober)
67 | visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True,
68 | hebrew_prober)
69 | hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober)
70 | self.probers.extend([hebrew_prober, logical_hebrew_prober,
71 | visual_hebrew_prober])
72 |
73 | self.reset()
74 |
--------------------------------------------------------------------------------
/docker/chardet/sbcsgroupprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/sbcsgroupprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/sjisprober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .mbcharsetprober import MultiByteCharSetProber
29 | from .codingstatemachine import CodingStateMachine
30 | from .chardistribution import SJISDistributionAnalysis
31 | from .jpcntx import SJISContextAnalysis
32 | from .mbcssm import SJIS_SM_MODEL
33 | from .enums import ProbingState, MachineState
34 |
35 |
36 | class SJISProber(MultiByteCharSetProber):
37 | def __init__(self):
38 | super(SJISProber, self).__init__()
39 | self.coding_sm = CodingStateMachine(SJIS_SM_MODEL)
40 | self.distribution_analyzer = SJISDistributionAnalysis()
41 | self.context_analyzer = SJISContextAnalysis()
42 | self.reset()
43 |
44 | def reset(self):
45 | super(SJISProber, self).reset()
46 | self.context_analyzer.reset()
47 |
48 | @property
49 | def charset_name(self):
50 | return self.context_analyzer.charset_name
51 |
52 | @property
53 | def language(self):
54 | return "Japanese"
55 |
56 | def feed(self, byte_str):
57 | for i in range(len(byte_str)):
58 | coding_state = self.coding_sm.next_state(byte_str[i])
59 | if coding_state == MachineState.ERROR:
60 | self.logger.debug('%s %s prober hit error at byte %s',
61 | self.charset_name, self.language, i)
62 | self._state = ProbingState.NOT_ME
63 | break
64 | elif coding_state == MachineState.ITS_ME:
65 | self._state = ProbingState.FOUND_IT
66 | break
67 | elif coding_state == MachineState.START:
68 | char_len = self.coding_sm.get_current_charlen()
69 | if i == 0:
70 | self._last_char[1] = byte_str[0]
71 | self.context_analyzer.feed(self._last_char[2 - char_len:],
72 | char_len)
73 | self.distribution_analyzer.feed(self._last_char, char_len)
74 | else:
75 | self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3
76 | - char_len], char_len)
77 | self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
78 | char_len)
79 |
80 | self._last_char[0] = byte_str[-1]
81 |
82 | if self.state == ProbingState.DETECTING:
83 | if (self.context_analyzer.got_enough_data() and
84 | (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
85 | self._state = ProbingState.FOUND_IT
86 |
87 | return self.state
88 |
89 | def get_confidence(self):
90 | context_conf = self.context_analyzer.get_confidence()
91 | distrib_conf = self.distribution_analyzer.get_confidence()
92 | return max(context_conf, distrib_conf)
93 |
--------------------------------------------------------------------------------
/docker/chardet/sjisprober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/sjisprober.pyc
--------------------------------------------------------------------------------
/docker/chardet/universaldetector.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/universaldetector.pyc
--------------------------------------------------------------------------------
/docker/chardet/utf8prober.py:
--------------------------------------------------------------------------------
1 | ######################## BEGIN LICENSE BLOCK ########################
2 | # The Original Code is mozilla.org code.
3 | #
4 | # The Initial Developer of the Original Code is
5 | # Netscape Communications Corporation.
6 | # Portions created by the Initial Developer are Copyright (C) 1998
7 | # the Initial Developer. All Rights Reserved.
8 | #
9 | # Contributor(s):
10 | # Mark Pilgrim - port to Python
11 | #
12 | # This library is free software; you can redistribute it and/or
13 | # modify it under the terms of the GNU Lesser General Public
14 | # License as published by the Free Software Foundation; either
15 | # version 2.1 of the License, or (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 | # Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public
23 | # License along with this library; if not, write to the Free Software
24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 | # 02110-1301 USA
26 | ######################### END LICENSE BLOCK #########################
27 |
28 | from .charsetprober import CharSetProber
29 | from .enums import ProbingState, MachineState
30 | from .codingstatemachine import CodingStateMachine
31 | from .mbcssm import UTF8_SM_MODEL
32 |
33 |
34 |
35 | class UTF8Prober(CharSetProber):
36 | ONE_CHAR_PROB = 0.5
37 |
38 | def __init__(self):
39 | super(UTF8Prober, self).__init__()
40 | self.coding_sm = CodingStateMachine(UTF8_SM_MODEL)
41 | self._num_mb_chars = None
42 | self.reset()
43 |
44 | def reset(self):
45 | super(UTF8Prober, self).reset()
46 | self.coding_sm.reset()
47 | self._num_mb_chars = 0
48 |
49 | @property
50 | def charset_name(self):
51 | return "utf-8"
52 |
53 | @property
54 | def language(self):
55 | return ""
56 |
57 | def feed(self, byte_str):
58 | for c in byte_str:
59 | coding_state = self.coding_sm.next_state(c)
60 | if coding_state == MachineState.ERROR:
61 | self._state = ProbingState.NOT_ME
62 | break
63 | elif coding_state == MachineState.ITS_ME:
64 | self._state = ProbingState.FOUND_IT
65 | break
66 | elif coding_state == MachineState.START:
67 | if self.coding_sm.get_current_charlen() >= 2:
68 | self._num_mb_chars += 1
69 |
70 | if self.state == ProbingState.DETECTING:
71 | if self.get_confidence() > self.SHORTCUT_THRESHOLD:
72 | self._state = ProbingState.FOUND_IT
73 |
74 | return self.state
75 |
76 | def get_confidence(self):
77 | unlike = 0.99
78 | if self._num_mb_chars < 6:
79 | unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars
80 | return 1.0 - unlike
81 | else:
82 | return unlike
83 |
--------------------------------------------------------------------------------
/docker/chardet/utf8prober.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/utf8prober.pyc
--------------------------------------------------------------------------------
/docker/chardet/version.py:
--------------------------------------------------------------------------------
1 | """
2 | This module exists only to simplify retrieving the version number of chardet
3 | from within setup.py and from chardet subpackages.
4 |
5 | :author: Dan Blanchard (dan.blanchard@gmail.com)
6 | """
7 |
8 | __version__ = "3.0.4"
9 | VERSION = __version__.split('.')
10 |
--------------------------------------------------------------------------------
/docker/chardet/version.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/chardet/version.pyc
--------------------------------------------------------------------------------
/docker/idna/__init__.py:
--------------------------------------------------------------------------------
1 | from .package_data import __version__
2 | from .core import *
3 |
--------------------------------------------------------------------------------
/docker/idna/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/idna/__init__.pyc
--------------------------------------------------------------------------------
/docker/idna/codec.py:
--------------------------------------------------------------------------------
1 | from .core import encode, decode, alabel, ulabel, IDNAError
2 | import codecs
3 | import re
4 |
5 | _unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]')
6 |
7 | class Codec(codecs.Codec):
8 |
9 | def encode(self, data, errors='strict'):
10 |
11 | if errors != 'strict':
12 | raise IDNAError("Unsupported error handling \"{0}\"".format(errors))
13 |
14 | if not data:
15 | return "", 0
16 |
17 | return encode(data), len(data)
18 |
19 | def decode(self, data, errors='strict'):
20 |
21 | if errors != 'strict':
22 | raise IDNAError("Unsupported error handling \"{0}\"".format(errors))
23 |
24 | if not data:
25 | return u"", 0
26 |
27 | return decode(data), len(data)
28 |
29 | class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
30 | def _buffer_encode(self, data, errors, final):
31 | if errors != 'strict':
32 | raise IDNAError("Unsupported error handling \"{0}\"".format(errors))
33 |
34 | if not data:
35 | return ("", 0)
36 |
37 | labels = _unicode_dots_re.split(data)
38 | trailing_dot = u''
39 | if labels:
40 | if not labels[-1]:
41 | trailing_dot = '.'
42 | del labels[-1]
43 | elif not final:
44 | # Keep potentially unfinished label until the next call
45 | del labels[-1]
46 | if labels:
47 | trailing_dot = '.'
48 |
49 | result = []
50 | size = 0
51 | for label in labels:
52 | result.append(alabel(label))
53 | if size:
54 | size += 1
55 | size += len(label)
56 |
57 | # Join with U+002E
58 | result = ".".join(result) + trailing_dot
59 | size += len(trailing_dot)
60 | return (result, size)
61 |
62 | class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
63 | def _buffer_decode(self, data, errors, final):
64 | if errors != 'strict':
65 | raise IDNAError("Unsupported error handling \"{0}\"".format(errors))
66 |
67 | if not data:
68 | return (u"", 0)
69 |
70 | # IDNA allows decoding to operate on Unicode strings, too.
71 | if isinstance(data, unicode):
72 | labels = _unicode_dots_re.split(data)
73 | else:
74 | # Must be ASCII string
75 | data = str(data)
76 | unicode(data, "ascii")
77 | labels = data.split(".")
78 |
79 | trailing_dot = u''
80 | if labels:
81 | if not labels[-1]:
82 | trailing_dot = u'.'
83 | del labels[-1]
84 | elif not final:
85 | # Keep potentially unfinished label until the next call
86 | del labels[-1]
87 | if labels:
88 | trailing_dot = u'.'
89 |
90 | result = []
91 | size = 0
92 | for label in labels:
93 | result.append(ulabel(label))
94 | if size:
95 | size += 1
96 | size += len(label)
97 |
98 | result = u".".join(result) + trailing_dot
99 | size += len(trailing_dot)
100 | return (result, size)
101 |
102 |
103 | class StreamWriter(Codec, codecs.StreamWriter):
104 | pass
105 |
106 | class StreamReader(Codec, codecs.StreamReader):
107 | pass
108 |
109 | def getregentry():
110 | return codecs.CodecInfo(
111 | name='idna',
112 | encode=Codec().encode,
113 | decode=Codec().decode,
114 | incrementalencoder=IncrementalEncoder,
115 | incrementaldecoder=IncrementalDecoder,
116 | streamwriter=StreamWriter,
117 | streamreader=StreamReader,
118 | )
119 |
--------------------------------------------------------------------------------
/docker/idna/compat.py:
--------------------------------------------------------------------------------
1 | from .core import *
2 | from .codec import *
3 |
4 | def ToASCII(label):
5 | return encode(label)
6 |
7 | def ToUnicode(label):
8 | return decode(label)
9 |
10 | def nameprep(s):
11 | raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol")
12 |
13 |
--------------------------------------------------------------------------------
/docker/idna/core.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/idna/core.pyc
--------------------------------------------------------------------------------
/docker/idna/idnadata.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/idna/idnadata.pyc
--------------------------------------------------------------------------------
/docker/idna/intranges.py:
--------------------------------------------------------------------------------
1 | """
2 | Given a list of integers, made up of (hopefully) a small number of long runs
3 | of consecutive integers, compute a representation of the form
4 | ((start1, end1), (start2, end2) ...). Then answer the question "was x present
5 | in the original list?" in time O(log(# runs)).
6 | """
7 |
8 | import bisect
9 |
10 | def intranges_from_list(list_):
11 | """Represent a list of integers as a sequence of ranges:
12 | ((start_0, end_0), (start_1, end_1), ...), such that the original
13 | integers are exactly those x such that start_i <= x < end_i for some i.
14 |
15 | Ranges are encoded as single integers (start << 32 | end), not as tuples.
16 | """
17 |
18 | sorted_list = sorted(list_)
19 | ranges = []
20 | last_write = -1
21 | for i in range(len(sorted_list)):
22 | if i+1 < len(sorted_list):
23 | if sorted_list[i] == sorted_list[i+1]-1:
24 | continue
25 | current_range = sorted_list[last_write+1:i+1]
26 | ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
27 | last_write = i
28 |
29 | return tuple(ranges)
30 |
31 | def _encode_range(start, end):
32 | return (start << 32) | end
33 |
34 | def _decode_range(r):
35 | return (r >> 32), (r & ((1 << 32) - 1))
36 |
37 |
38 | def intranges_contain(int_, ranges):
39 | """Determine if `int_` falls into one of the ranges in `ranges`."""
40 | tuple_ = _encode_range(int_, 0)
41 | pos = bisect.bisect_left(ranges, tuple_)
42 | # we could be immediately ahead of a tuple (start, end)
43 | # with start < int_ <= end
44 | if pos > 0:
45 | left, right = _decode_range(ranges[pos-1])
46 | if left <= int_ < right:
47 | return True
48 | # or we could be immediately behind a tuple (int_, end)
49 | if pos < len(ranges):
50 | left, _ = _decode_range(ranges[pos])
51 | if left == int_:
52 | return True
53 | return False
54 |
--------------------------------------------------------------------------------
/docker/idna/intranges.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/idna/intranges.pyc
--------------------------------------------------------------------------------
/docker/idna/package_data.py:
--------------------------------------------------------------------------------
1 | __version__ = '2.6'
2 |
3 |
--------------------------------------------------------------------------------
/docker/idna/package_data.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/idna/package_data.pyc
--------------------------------------------------------------------------------
/docker/radarr_discord.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import logging
4 | import sys
5 | import json
6 | import re
7 | import requests
8 | import script_config
9 | from datetime import datetime
10 |
11 | discord_headers = {'content-type': 'application/json'}
12 |
13 | # Set up the log file
14 | log_filename = os.path.join(os.path.dirname(sys.argv[0]), 'radarr_notification.log')
15 | logging.basicConfig(
16 | filename=log_filename,
17 | level=logging.INFO,
18 | format='[%(asctime)s] %(levelname)s - %(message)s'
19 | )
20 | log = logging.getLogger('Radarr')
21 |
22 | def get_profile_name(profile_id):
23 | try:
24 | get_profiles = requests.get('{}api/profile/{}?apikey={}'.format(script_config.radarr_url, profile_id, script_config.radarr_key)).json()
25 | except Exception as ex:
26 | log.error('Could not pull profile from radarr! ({})'.format(ex))
27 |
28 | try:
29 | profile_name = get_profiles['name']
30 |
31 | except Exception as ex:
32 | log.error('Could not find profile ID in radarr! ({})'.format(ex))
33 | profile_name = 'Unknown'
34 |
35 | return profile_name
36 |
37 | def get_imdb_rating(imdb_id):
38 | get_imdb = requests.get('https://imdb-api.com/API/Ratings/{}/{}'.format(script_config.radarr_imdbapi_key, imdb_id)).json()
39 | imdb_rating = get_imdb['imDb']
40 | if not imdb_rating:
41 | imdb_rating = "Unknown"
42 | return imdb_rating
43 |
44 | def utc_now_iso():
45 | utcnow = datetime.utcnow()
46 | return utcnow.isoformat()
47 |
48 | # Get Event Type
49 | eventtype = os.environ.get('radarr_eventtype')
50 |
51 | if eventtype == 'test':
52 | TEST_MODE = True
53 | else:
54 | TEST_MODE = False
55 |
56 | #Get ENV variables
57 | movie_id = os.environ.get('radarr_movie_id')
58 | if not movie_id:
59 | movie_id = 10
60 |
61 | media_title = os.environ.get('radarr_movie_title')
62 | if not media_title:
63 | media_title = 'The Lego Movie'
64 |
65 | imdb_id = os.environ.get('radarr_movie_imdbid')
66 | if not imdb_id:
67 | imdb_id = 'tt1490017'
68 |
69 | quality = os.environ.get('radarr_moviefile_quality')
70 | if not quality:
71 | quality = 'Bluray-2160p'
72 |
73 | scene_name = os.environ.get('radarr_moviefile_scenename')
74 |
75 | imdb_rating = get_imdb_rating(imdb_id)
76 |
77 | imdb_url = 'https://www.imdb.com/title/' + imdb_id
78 |
79 | #Get Radarr data
80 | radarr_api_url = '{}api/movie/{}?apikey={}'.format(script_config.radarr_url, movie_id, script_config.radarr_key)
81 |
82 | radarr = requests.get(radarr_api_url)
83 |
84 | radarr_data = radarr.json()
85 |
86 | if not TEST_MODE:
87 | year = radarr_data['year']
88 |
89 | if TEST_MODE:
90 | scene_name = 'A.Movie.2020.TrueHD.Atmos.AC3.MULTISUBS.UHD.4k.BluRay.x264.HQ-TUSAHD'
91 | year = '2014'
92 |
93 | #Get Trailer Link from Radarr
94 | try:
95 | trailer_link = 'https://www.youtube.com/watch?v={}'.format(radarr_data['youTubeTrailerId'])
96 | except:
97 | trailer_link = 'None'
98 |
99 | title_slug = re.sub(r'[?|$|.|!|:|/]', r'', media_title).replace(' ', '-')
100 |
101 | #Get data from TMDB
102 | moviedb_api_url = 'https://api.themoviedb.org/3/find/{}?api_key={}&external_source=imdb_id'.format(imdb_id, script_config.moviedb_key)
103 |
104 | moviedb_api = requests.get(moviedb_api_url)
105 |
106 | moviedb_api_data = moviedb_api.json()
107 |
108 | radarr_id = moviedb_api_data['movie_results'][0]['id']
109 |
110 | try:
111 | overview = moviedb_api_data['movie_results'][0]['overview']
112 | except:
113 | overview = 'None'
114 |
115 | try:
116 | physical_release = radarr_data['physicalRelease']
117 | physical_release= datetime.strptime(physical_release,"%Y-%m-%dT%H:%M:%SZ").strftime("%B %d, %Y")
118 | except:
119 | physical_release = 'None'
120 |
121 | try:
122 | release = moviedb_api_data['movie_results'][0]['release_date']
123 | release = datetime.strptime(release, "%Y-%m-%d").strftime("%B %d, %Y")
124 | except:
125 | release = 'None'
126 |
127 | try:
128 | genres = json.dumps(radarr_data['genres'])
129 | genres = re.sub(r'[?|$|.|!|:|/|\]|\[|\"]', r'', genres)
130 | except:
131 | genres = 'None'
132 |
133 | quality_profile = get_profile_name(radarr_data['qualityProfileId'])
134 |
135 | #Get Poster from TMDB
136 | poster_path = moviedb_api_data['movie_results'][0]['poster_path']
137 |
138 | try:
139 | poster_path = 'https://image.tmdb.org/t/p/w500' + poster_path
140 | except TypeError:
141 | #Send a generic poster if there is not one for this movie
142 | poster_path = 'https://i.imgur.com/GoqfZJe.jpg'
143 |
144 | # Format the message
145 | message = {
146 | 'username': script_config.radarr_discord_user,
147 | 'content': 'New movie downloaded - {} ({}) IMDb: {}'.format(media_title, year, imdb_rating),
148 | 'embeds': [
149 | {
150 | 'author': {
151 | 'name': 'Movies',
152 | 'url': script_config.radarr_url,
153 | 'icon_url': script_config.radarr_icon
154 | },
155 | 'title': '{} ({})'.format(media_title, year),
156 | 'color': 3394662,
157 | 'url': '{}movie/{}-{}'.format(script_config.radarr_url, title_slug.lower(), radarr_id),
158 | 'image': {
159 | 'url': poster_path
160 | },
161 | 'fields': [
162 |
163 | ]
164 | },
165 | {
166 | 'title': 'Overview',
167 | 'color': 3381708,
168 | 'description': overview,
169 | "fields": [
170 | {
171 | "name": 'Quality',
172 | "value": quality,
173 | "inline": True
174 | },
175 | {
176 | "name": 'Quality Profile',
177 | "value": quality_profile,
178 | "inline": True
179 | },
180 | {
181 | "name": 'Release Date',
182 | "value": release,
183 | "inline": True
184 | },
185 | {
186 | "name": 'Physical Release Date',
187 | "value": physical_release,
188 | "inline": True
189 | },
190 | {
191 | "name": 'IMDb Rating',
192 | "value": "[{}]({})".format(imdb_rating, imdb_url),
193 | "inline": True
194 | },
195 | {
196 | "name": 'Genres',
197 | "value": genres,
198 | "inline": True
199 | },
200 | {
201 | "inline": False,
202 | "name": "Trailer",
203 | "value": trailer_link
204 | }
205 | ],
206 | 'footer': {
207 | 'text': '{}'.format(scene_name)
208 | },
209 | 'timestamp': utc_now_iso()
210 |
211 | },
212 |
213 | ]
214 | }
215 |
216 | # Log json
217 | log.info(json.dumps(message, sort_keys=True, indent=4, separators=(',', ': ')))
218 |
219 | # Send notification
220 | sender = requests.post(script_config.radarr_discord_url, headers=discord_headers, json=message)
221 |
222 | # Log response from discord
223 | log.info(sender.content)
224 |
--------------------------------------------------------------------------------
/docker/requests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # __
4 | # /__) _ _ _ _ _/ _
5 | # / ( (- (/ (/ (- _) / _)
6 | # /
7 |
8 | """
9 | Requests HTTP Library
10 | ~~~~~~~~~~~~~~~~~~~~~
11 |
12 | Requests is an HTTP library, written in Python, for human beings. Basic GET
13 | usage:
14 |
15 | >>> import requests
16 | >>> r = requests.get('https://www.python.org')
17 | >>> r.status_code
18 | 200
19 | >>> 'Python is a programming language' in r.content
20 | True
21 |
22 | ... or POST:
23 |
24 | >>> payload = dict(key1='value1', key2='value2')
25 | >>> r = requests.post('http://httpbin.org/post', data=payload)
26 | >>> print(r.text)
27 | {
28 | ...
29 | "form": {
30 | "key2": "value2",
31 | "key1": "value1"
32 | },
33 | ...
34 | }
35 |
36 | The other HTTP methods are supported - see `requests.api`. Full documentation
37 | is at .
38 |
39 | :copyright: (c) 2017 by Kenneth Reitz.
40 | :license: Apache 2.0, see LICENSE for more details.
41 | """
42 |
43 | import urllib3
44 | import chardet
45 | import warnings
46 | from .exceptions import RequestsDependencyWarning
47 |
48 |
49 | def check_compatibility(urllib3_version, chardet_version):
50 | urllib3_version = urllib3_version.split('.')
51 | assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.
52 |
53 | # Sometimes, urllib3 only reports its version as 16.1.
54 | if len(urllib3_version) == 2:
55 | urllib3_version.append('0')
56 |
57 | # Check urllib3 for compatibility.
58 | major, minor, patch = urllib3_version # noqa: F811
59 | major, minor, patch = int(major), int(minor), int(patch)
60 | # urllib3 >= 1.21.1, <= 1.22
61 | assert major == 1
62 | assert minor >= 21
63 | assert minor <= 22
64 |
65 | # Check chardet for compatibility.
66 | major, minor, patch = chardet_version.split('.')[:3]
67 | major, minor, patch = int(major), int(minor), int(patch)
68 | # chardet >= 3.0.2, < 3.1.0
69 | assert major == 3
70 | assert minor < 1
71 | assert patch >= 2
72 |
73 |
74 | # Check imported dependencies for compatibility.
75 | try:
76 | check_compatibility(urllib3.__version__, chardet.__version__)
77 | except (AssertionError, ValueError):
78 | warnings.warn("urllib3 ({0}) or chardet ({1}) doesn't match a supported "
79 | "version!".format(urllib3.__version__, chardet.__version__),
80 | RequestsDependencyWarning)
81 |
82 | # Attempt to enable urllib3's SNI support, if possible
83 | try:
84 | from urllib3.contrib import pyopenssl
85 | pyopenssl.inject_into_urllib3()
86 | except ImportError:
87 | pass
88 |
89 | # urllib3's DependencyWarnings should be silenced.
90 | from urllib3.exceptions import DependencyWarning
91 | warnings.simplefilter('ignore', DependencyWarning)
92 |
93 | from .__version__ import __title__, __description__, __url__, __version__
94 | from .__version__ import __build__, __author__, __author_email__, __license__
95 | from .__version__ import __copyright__, __cake__
96 |
97 | from . import utils
98 | from . import packages
99 | from .models import Request, Response, PreparedRequest
100 | from .api import request, get, head, post, patch, put, delete, options
101 | from .sessions import session, Session
102 | from .status_codes import codes
103 | from .exceptions import (
104 | RequestException, Timeout, URLRequired,
105 | TooManyRedirects, HTTPError, ConnectionError,
106 | FileModeWarning, ConnectTimeout, ReadTimeout
107 | )
108 |
109 | # Set default logging handler to avoid "No handler found" warnings.
110 | import logging
111 | try: # Python 2.7+
112 | from logging import NullHandler
113 | except ImportError:
114 | class NullHandler(logging.Handler):
115 | def emit(self, record):
116 | pass
117 |
118 | logging.getLogger(__name__).addHandler(NullHandler())
119 |
120 | # FileModeWarnings go off per the default.
121 | warnings.simplefilter('default', FileModeWarning, append=True)
122 |
--------------------------------------------------------------------------------
/docker/requests/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/__init__.pyc
--------------------------------------------------------------------------------
/docker/requests/__version__.py:
--------------------------------------------------------------------------------
1 | # .-. .-. .-. . . .-. .-. .-. .-.
2 | # |( |- |.| | | |- `-. | `-.
3 | # ' ' `-' `-`.`-' `-' `-' ' `-'
4 |
5 | __title__ = 'requests'
6 | __description__ = 'Python HTTP for Humans.'
7 | __url__ = 'http://python-requests.org'
8 | __version__ = '2.18.4'
9 | __build__ = 0x021804
10 | __author__ = 'Kenneth Reitz'
11 | __author_email__ = 'me@kennethreitz.org'
12 | __license__ = 'Apache 2.0'
13 | __copyright__ = 'Copyright 2017 Kenneth Reitz'
14 | __cake__ = u'\u2728 \U0001f370 \u2728'
15 |
--------------------------------------------------------------------------------
/docker/requests/__version__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/__version__.pyc
--------------------------------------------------------------------------------
/docker/requests/_internal_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests._internal_utils
5 | ~~~~~~~~~~~~~~
6 |
7 | Provides utility functions that are consumed internally by Requests
8 | which depend on extremely few external helpers (such as compat)
9 | """
10 |
11 | from .compat import is_py2, builtin_str, str
12 |
13 |
14 | def to_native_string(string, encoding='ascii'):
15 | """Given a string object, regardless of type, returns a representation of
16 | that string in the native string type, encoding and decoding where
17 | necessary. This assumes ASCII unless told otherwise.
18 | """
19 | if isinstance(string, builtin_str):
20 | out = string
21 | else:
22 | if is_py2:
23 | out = string.encode(encoding)
24 | else:
25 | out = string.decode(encoding)
26 |
27 | return out
28 |
29 |
30 | def unicode_is_ascii(u_string):
31 | """Determine if unicode string only contains ASCII characters.
32 |
33 | :param str u_string: unicode string to check. Must be unicode
34 | and not Python 2 `str`.
35 | :rtype: bool
36 | """
37 | assert isinstance(u_string, str)
38 | try:
39 | u_string.encode('ascii')
40 | return True
41 | except UnicodeEncodeError:
42 | return False
43 |
--------------------------------------------------------------------------------
/docker/requests/_internal_utils.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/_internal_utils.pyc
--------------------------------------------------------------------------------
/docker/requests/adapters.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/adapters.pyc
--------------------------------------------------------------------------------
/docker/requests/api.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests.api
5 | ~~~~~~~~~~~~
6 |
7 | This module implements the Requests API.
8 |
9 | :copyright: (c) 2012 by Kenneth Reitz.
10 | :license: Apache2, see LICENSE for more details.
11 | """
12 |
13 | from . import sessions
14 |
15 |
16 | def request(method, url, **kwargs):
17 | """Constructs and sends a :class:`Request `.
18 |
19 | :param method: method for the new :class:`Request` object.
20 | :param url: URL for the new :class:`Request` object.
21 | :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
22 | :param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
23 | :param json: (optional) json data to send in the body of the :class:`Request`.
24 | :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
25 | :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
26 | :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
27 | ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
28 | or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
29 | defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
30 | to add for the file.
31 | :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
32 | :param timeout: (optional) How many seconds to wait for the server to send data
33 | before giving up, as a float, or a :ref:`(connect timeout, read
34 | timeout) ` tuple.
35 | :type timeout: float or tuple
36 | :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
37 | :type allow_redirects: bool
38 | :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
39 | :param verify: (optional) Either a boolean, in which case it controls whether we verify
40 | the server's TLS certificate, or a string, in which case it must be a path
41 | to a CA bundle to use. Defaults to ``True``.
42 | :param stream: (optional) if ``False``, the response content will be immediately downloaded.
43 | :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
44 | :return: :class:`Response ` object
45 | :rtype: requests.Response
46 |
47 | Usage::
48 |
49 | >>> import requests
50 | >>> req = requests.request('GET', 'http://httpbin.org/get')
51 |
52 | """
53 |
54 | # By using the 'with' statement we are sure the session is closed, thus we
55 | # avoid leaving sockets open which can trigger a ResourceWarning in some
56 | # cases, and look like a memory leak in others.
57 | with sessions.Session() as session:
58 | return session.request(method=method, url=url, **kwargs)
59 |
60 |
61 | def get(url, params=None, **kwargs):
62 | r"""Sends a GET request.
63 |
64 | :param url: URL for the new :class:`Request` object.
65 | :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
66 | :param \*\*kwargs: Optional arguments that ``request`` takes.
67 | :return: :class:`Response ` object
68 | :rtype: requests.Response
69 | """
70 |
71 | kwargs.setdefault('allow_redirects', True)
72 | return request('get', url, params=params, **kwargs)
73 |
74 |
75 | def options(url, **kwargs):
76 | r"""Sends an OPTIONS request.
77 |
78 | :param url: URL for the new :class:`Request` object.
79 | :param \*\*kwargs: Optional arguments that ``request`` takes.
80 | :return: :class:`Response ` object
81 | :rtype: requests.Response
82 | """
83 |
84 | kwargs.setdefault('allow_redirects', True)
85 | return request('options', url, **kwargs)
86 |
87 |
88 | def head(url, **kwargs):
89 | r"""Sends a HEAD request.
90 |
91 | :param url: URL for the new :class:`Request` object.
92 | :param \*\*kwargs: Optional arguments that ``request`` takes.
93 | :return: :class:`Response ` object
94 | :rtype: requests.Response
95 | """
96 |
97 | kwargs.setdefault('allow_redirects', False)
98 | return request('head', url, **kwargs)
99 |
100 |
101 | def post(url, data=None, json=None, **kwargs):
102 | r"""Sends a POST request.
103 |
104 | :param url: URL for the new :class:`Request` object.
105 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
106 | :param json: (optional) json data to send in the body of the :class:`Request`.
107 | :param \*\*kwargs: Optional arguments that ``request`` takes.
108 | :return: :class:`Response ` object
109 | :rtype: requests.Response
110 | """
111 |
112 | return request('post', url, data=data, json=json, **kwargs)
113 |
114 |
115 | def put(url, data=None, **kwargs):
116 | r"""Sends a PUT request.
117 |
118 | :param url: URL for the new :class:`Request` object.
119 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
120 | :param json: (optional) json data to send in the body of the :class:`Request`.
121 | :param \*\*kwargs: Optional arguments that ``request`` takes.
122 | :return: :class:`Response ` object
123 | :rtype: requests.Response
124 | """
125 |
126 | return request('put', url, data=data, **kwargs)
127 |
128 |
129 | def patch(url, data=None, **kwargs):
130 | r"""Sends a PATCH request.
131 |
132 | :param url: URL for the new :class:`Request` object.
133 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
134 | :param json: (optional) json data to send in the body of the :class:`Request`.
135 | :param \*\*kwargs: Optional arguments that ``request`` takes.
136 | :return: :class:`Response ` object
137 | :rtype: requests.Response
138 | """
139 |
140 | return request('patch', url, data=data, **kwargs)
141 |
142 |
143 | def delete(url, **kwargs):
144 | r"""Sends a DELETE request.
145 |
146 | :param url: URL for the new :class:`Request` object.
147 | :param \*\*kwargs: Optional arguments that ``request`` takes.
148 | :return: :class:`Response ` object
149 | :rtype: requests.Response
150 | """
151 |
152 | return request('delete', url, **kwargs)
153 |
--------------------------------------------------------------------------------
/docker/requests/api.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/api.pyc
--------------------------------------------------------------------------------
/docker/requests/auth.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/auth.pyc
--------------------------------------------------------------------------------
/docker/requests/certs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | requests.certs
6 | ~~~~~~~~~~~~~~
7 |
8 | This module returns the preferred default CA certificate bundle. There is
9 | only one — the one from the certifi package.
10 |
11 | If you are packaging Requests, e.g., for a Linux distribution or a managed
12 | environment, you can change the definition of where() to return a separately
13 | packaged CA bundle.
14 | """
15 | from certifi import where
16 |
17 | if __name__ == '__main__':
18 | print(where())
19 |
--------------------------------------------------------------------------------
/docker/requests/certs.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/certs.pyc
--------------------------------------------------------------------------------
/docker/requests/compat.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests.compat
5 | ~~~~~~~~~~~~~~~
6 |
7 | This module handles import compatibility issues between Python 2 and
8 | Python 3.
9 | """
10 |
11 | import chardet
12 |
13 | import sys
14 |
15 | # -------
16 | # Pythons
17 | # -------
18 |
19 | # Syntax sugar.
20 | _ver = sys.version_info
21 |
22 | #: Python 2.x?
23 | is_py2 = (_ver[0] == 2)
24 |
25 | #: Python 3.x?
26 | is_py3 = (_ver[0] == 3)
27 |
28 | try:
29 | import simplejson as json
30 | except ImportError:
31 | import json
32 |
33 | # ---------
34 | # Specifics
35 | # ---------
36 |
37 | if is_py2:
38 | from urllib import (
39 | quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
40 | proxy_bypass, proxy_bypass_environment, getproxies_environment)
41 | from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
42 | from urllib2 import parse_http_list
43 | import cookielib
44 | from Cookie import Morsel
45 | from StringIO import StringIO
46 |
47 | from urllib3.packages.ordered_dict import OrderedDict
48 |
49 | builtin_str = str
50 | bytes = str
51 | str = unicode
52 | basestring = basestring
53 | numeric_types = (int, long, float)
54 | integer_types = (int, long)
55 |
56 | elif is_py3:
57 | from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
58 | from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
59 | from http import cookiejar as cookielib
60 | from http.cookies import Morsel
61 | from io import StringIO
62 | from collections import OrderedDict
63 |
64 | builtin_str = str
65 | str = str
66 | bytes = bytes
67 | basestring = (str, bytes)
68 | numeric_types = (int, float)
69 | integer_types = (int,)
70 |
--------------------------------------------------------------------------------
/docker/requests/compat.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/compat.pyc
--------------------------------------------------------------------------------
/docker/requests/cookies.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/cookies.pyc
--------------------------------------------------------------------------------
/docker/requests/exceptions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests.exceptions
5 | ~~~~~~~~~~~~~~~~~~~
6 |
7 | This module contains the set of Requests' exceptions.
8 | """
9 | from urllib3.exceptions import HTTPError as BaseHTTPError
10 |
11 |
12 | class RequestException(IOError):
13 | """There was an ambiguous exception that occurred while handling your
14 | request.
15 | """
16 |
17 | def __init__(self, *args, **kwargs):
18 | """Initialize RequestException with `request` and `response` objects."""
19 | response = kwargs.pop('response', None)
20 | self.response = response
21 | self.request = kwargs.pop('request', None)
22 | if (response is not None and not self.request and
23 | hasattr(response, 'request')):
24 | self.request = self.response.request
25 | super(RequestException, self).__init__(*args, **kwargs)
26 |
27 |
28 | class HTTPError(RequestException):
29 | """An HTTP error occurred."""
30 |
31 |
32 | class ConnectionError(RequestException):
33 | """A Connection error occurred."""
34 |
35 |
36 | class ProxyError(ConnectionError):
37 | """A proxy error occurred."""
38 |
39 |
40 | class SSLError(ConnectionError):
41 | """An SSL error occurred."""
42 |
43 |
44 | class Timeout(RequestException):
45 | """The request timed out.
46 |
47 | Catching this error will catch both
48 | :exc:`~requests.exceptions.ConnectTimeout` and
49 | :exc:`~requests.exceptions.ReadTimeout` errors.
50 | """
51 |
52 |
53 | class ConnectTimeout(ConnectionError, Timeout):
54 | """The request timed out while trying to connect to the remote server.
55 |
56 | Requests that produced this error are safe to retry.
57 | """
58 |
59 |
60 | class ReadTimeout(Timeout):
61 | """The server did not send any data in the allotted amount of time."""
62 |
63 |
64 | class URLRequired(RequestException):
65 | """A valid URL is required to make a request."""
66 |
67 |
68 | class TooManyRedirects(RequestException):
69 | """Too many redirects."""
70 |
71 |
72 | class MissingSchema(RequestException, ValueError):
73 | """The URL schema (e.g. http or https) is missing."""
74 |
75 |
76 | class InvalidSchema(RequestException, ValueError):
77 | """See defaults.py for valid schemas."""
78 |
79 |
80 | class InvalidURL(RequestException, ValueError):
81 | """The URL provided was somehow invalid."""
82 |
83 |
84 | class InvalidHeader(RequestException, ValueError):
85 | """The header value provided was somehow invalid."""
86 |
87 |
88 | class ChunkedEncodingError(RequestException):
89 | """The server declared chunked encoding but sent an invalid chunk."""
90 |
91 |
92 | class ContentDecodingError(RequestException, BaseHTTPError):
93 | """Failed to decode response content"""
94 |
95 |
96 | class StreamConsumedError(RequestException, TypeError):
97 | """The content for this response was already consumed"""
98 |
99 |
100 | class RetryError(RequestException):
101 | """Custom retries logic failed"""
102 |
103 |
104 | class UnrewindableBodyError(RequestException):
105 | """Requests encountered an error when trying to rewind a body"""
106 |
107 | # Warnings
108 |
109 |
110 | class RequestsWarning(Warning):
111 | """Base warning for Requests."""
112 | pass
113 |
114 |
115 | class FileModeWarning(RequestsWarning, DeprecationWarning):
116 | """A file was opened in text mode, but Requests determined its binary length."""
117 | pass
118 |
119 |
120 | class RequestsDependencyWarning(RequestsWarning):
121 | """An imported dependency doesn't match the expected version range."""
122 | pass
123 |
--------------------------------------------------------------------------------
/docker/requests/exceptions.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/exceptions.pyc
--------------------------------------------------------------------------------
/docker/requests/help.py:
--------------------------------------------------------------------------------
1 | """Module containing bug report helper(s)."""
2 | from __future__ import print_function
3 |
4 | import json
5 | import platform
6 | import sys
7 | import ssl
8 |
9 | import idna
10 | import urllib3
11 | import chardet
12 |
13 | from . import __version__ as requests_version
14 |
15 | try:
16 | from .packages.urllib3.contrib import pyopenssl
17 | except ImportError:
18 | pyopenssl = None
19 | OpenSSL = None
20 | cryptography = None
21 | else:
22 | import OpenSSL
23 | import cryptography
24 |
25 |
26 | def _implementation():
27 | """Return a dict with the Python implementation and version.
28 |
29 | Provide both the name and the version of the Python implementation
30 | currently running. For example, on CPython 2.7.5 it will return
31 | {'name': 'CPython', 'version': '2.7.5'}.
32 |
33 | This function works best on CPython and PyPy: in particular, it probably
34 | doesn't work for Jython or IronPython. Future investigation should be done
35 | to work out the correct shape of the code for those platforms.
36 | """
37 | implementation = platform.python_implementation()
38 |
39 | if implementation == 'CPython':
40 | implementation_version = platform.python_version()
41 | elif implementation == 'PyPy':
42 | implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
43 | sys.pypy_version_info.minor,
44 | sys.pypy_version_info.micro)
45 | if sys.pypy_version_info.releaselevel != 'final':
46 | implementation_version = ''.join([
47 | implementation_version, sys.pypy_version_info.releaselevel
48 | ])
49 | elif implementation == 'Jython':
50 | implementation_version = platform.python_version() # Complete Guess
51 | elif implementation == 'IronPython':
52 | implementation_version = platform.python_version() # Complete Guess
53 | else:
54 | implementation_version = 'Unknown'
55 |
56 | return {'name': implementation, 'version': implementation_version}
57 |
58 |
59 | def info():
60 | """Generate information for a bug report."""
61 | try:
62 | platform_info = {
63 | 'system': platform.system(),
64 | 'release': platform.release(),
65 | }
66 | except IOError:
67 | platform_info = {
68 | 'system': 'Unknown',
69 | 'release': 'Unknown',
70 | }
71 |
72 | implementation_info = _implementation()
73 | urllib3_info = {'version': urllib3.__version__}
74 | chardet_info = {'version': chardet.__version__}
75 |
76 | pyopenssl_info = {
77 | 'version': None,
78 | 'openssl_version': '',
79 | }
80 | if OpenSSL:
81 | pyopenssl_info = {
82 | 'version': OpenSSL.__version__,
83 | 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER,
84 | }
85 | cryptography_info = {
86 | 'version': getattr(cryptography, '__version__', ''),
87 | }
88 | idna_info = {
89 | 'version': getattr(idna, '__version__', ''),
90 | }
91 |
92 | # OPENSSL_VERSION_NUMBER doesn't exist in the Python 2.6 ssl module.
93 | system_ssl = getattr(ssl, 'OPENSSL_VERSION_NUMBER', None)
94 | system_ssl_info = {
95 | 'version': '%x' % system_ssl if system_ssl is not None else ''
96 | }
97 |
98 | return {
99 | 'platform': platform_info,
100 | 'implementation': implementation_info,
101 | 'system_ssl': system_ssl_info,
102 | 'using_pyopenssl': pyopenssl is not None,
103 | 'pyOpenSSL': pyopenssl_info,
104 | 'urllib3': urllib3_info,
105 | 'chardet': chardet_info,
106 | 'cryptography': cryptography_info,
107 | 'idna': idna_info,
108 | 'requests': {
109 | 'version': requests_version,
110 | },
111 | }
112 |
113 |
114 | def main():
115 | """Pretty-print the bug information as JSON."""
116 | print(json.dumps(info(), sort_keys=True, indent=2))
117 |
118 |
119 | if __name__ == '__main__':
120 | main()
121 |
--------------------------------------------------------------------------------
/docker/requests/hooks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests.hooks
5 | ~~~~~~~~~~~~~~
6 |
7 | This module provides the capabilities for the Requests hooks system.
8 |
9 | Available hooks:
10 |
11 | ``response``:
12 | The response generated from a Request.
13 | """
14 | HOOKS = ['response']
15 |
16 |
17 | def default_hooks():
18 | return dict((event, []) for event in HOOKS)
19 |
20 | # TODO: response is the only one
21 |
22 |
23 | def dispatch_hook(key, hooks, hook_data, **kwargs):
24 | """Dispatches a hook dictionary on a given piece of data."""
25 | hooks = hooks or dict()
26 | hooks = hooks.get(key)
27 | if hooks:
28 | if hasattr(hooks, '__call__'):
29 | hooks = [hooks]
30 | for hook in hooks:
31 | _hook_data = hook(hook_data, **kwargs)
32 | if _hook_data is not None:
33 | hook_data = _hook_data
34 | return hook_data
35 |
--------------------------------------------------------------------------------
/docker/requests/hooks.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/hooks.pyc
--------------------------------------------------------------------------------
/docker/requests/models.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/models.pyc
--------------------------------------------------------------------------------
/docker/requests/packages.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | # This code exists for backwards compatibility reasons.
4 | # I don't like it either. Just look the other way. :)
5 |
6 | for package in ('urllib3', 'idna', 'chardet'):
7 | locals()[package] = __import__(package)
8 | # This traversal is apparently necessary such that the identities are
9 | # preserved (requests.packages.urllib3.* is urllib3.*)
10 | for mod in list(sys.modules):
11 | if mod == package or mod.startswith(package + '.'):
12 | sys.modules['requests.packages.' + mod] = sys.modules[mod]
13 |
14 | # Kinda cool, though, right?
15 |
--------------------------------------------------------------------------------
/docker/requests/packages.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/packages.pyc
--------------------------------------------------------------------------------
/docker/requests/sessions.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/sessions.pyc
--------------------------------------------------------------------------------
/docker/requests/status_codes.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .structures import LookupDict
4 |
5 | _codes = {
6 |
7 | # Informational.
8 | 100: ('continue',),
9 | 101: ('switching_protocols',),
10 | 102: ('processing',),
11 | 103: ('checkpoint',),
12 | 122: ('uri_too_long', 'request_uri_too_long'),
13 | 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'),
14 | 201: ('created',),
15 | 202: ('accepted',),
16 | 203: ('non_authoritative_info', 'non_authoritative_information'),
17 | 204: ('no_content',),
18 | 205: ('reset_content', 'reset'),
19 | 206: ('partial_content', 'partial'),
20 | 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
21 | 208: ('already_reported',),
22 | 226: ('im_used',),
23 |
24 | # Redirection.
25 | 300: ('multiple_choices',),
26 | 301: ('moved_permanently', 'moved', '\\o-'),
27 | 302: ('found',),
28 | 303: ('see_other', 'other'),
29 | 304: ('not_modified',),
30 | 305: ('use_proxy',),
31 | 306: ('switch_proxy',),
32 | 307: ('temporary_redirect', 'temporary_moved', 'temporary'),
33 | 308: ('permanent_redirect',
34 | 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
35 |
36 | # Client Error.
37 | 400: ('bad_request', 'bad'),
38 | 401: ('unauthorized',),
39 | 402: ('payment_required', 'payment'),
40 | 403: ('forbidden',),
41 | 404: ('not_found', '-o-'),
42 | 405: ('method_not_allowed', 'not_allowed'),
43 | 406: ('not_acceptable',),
44 | 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
45 | 408: ('request_timeout', 'timeout'),
46 | 409: ('conflict',),
47 | 410: ('gone',),
48 | 411: ('length_required',),
49 | 412: ('precondition_failed', 'precondition'),
50 | 413: ('request_entity_too_large',),
51 | 414: ('request_uri_too_large',),
52 | 415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
53 | 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
54 | 417: ('expectation_failed',),
55 | 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
56 | 421: ('misdirected_request',),
57 | 422: ('unprocessable_entity', 'unprocessable'),
58 | 423: ('locked',),
59 | 424: ('failed_dependency', 'dependency'),
60 | 425: ('unordered_collection', 'unordered'),
61 | 426: ('upgrade_required', 'upgrade'),
62 | 428: ('precondition_required', 'precondition'),
63 | 429: ('too_many_requests', 'too_many'),
64 | 431: ('header_fields_too_large', 'fields_too_large'),
65 | 444: ('no_response', 'none'),
66 | 449: ('retry_with', 'retry'),
67 | 450: ('blocked_by_windows_parental_controls', 'parental_controls'),
68 | 451: ('unavailable_for_legal_reasons', 'legal_reasons'),
69 | 499: ('client_closed_request',),
70 |
71 | # Server Error.
72 | 500: ('internal_server_error', 'server_error', '/o\\', '✗'),
73 | 501: ('not_implemented',),
74 | 502: ('bad_gateway',),
75 | 503: ('service_unavailable', 'unavailable'),
76 | 504: ('gateway_timeout',),
77 | 505: ('http_version_not_supported', 'http_version'),
78 | 506: ('variant_also_negotiates',),
79 | 507: ('insufficient_storage',),
80 | 509: ('bandwidth_limit_exceeded', 'bandwidth'),
81 | 510: ('not_extended',),
82 | 511: ('network_authentication_required', 'network_auth', 'network_authentication'),
83 | }
84 |
85 | codes = LookupDict(name='status_codes')
86 |
87 | for code, titles in _codes.items():
88 | for title in titles:
89 | setattr(codes, title, code)
90 | if not title.startswith(('\\', '/')):
91 | setattr(codes, title.upper(), code)
92 |
--------------------------------------------------------------------------------
/docker/requests/status_codes.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/status_codes.pyc
--------------------------------------------------------------------------------
/docker/requests/structures.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | requests.structures
5 | ~~~~~~~~~~~~~~~~~~~
6 |
7 | Data structures that power Requests.
8 | """
9 |
10 | import collections
11 |
12 | from .compat import OrderedDict
13 |
14 |
15 | class CaseInsensitiveDict(collections.MutableMapping):
16 | """A case-insensitive ``dict``-like object.
17 |
18 | Implements all methods and operations of
19 | ``collections.MutableMapping`` as well as dict's ``copy``. Also
20 | provides ``lower_items``.
21 |
22 | All keys are expected to be strings. The structure remembers the
23 | case of the last key to be set, and ``iter(instance)``,
24 | ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
25 | will contain case-sensitive keys. However, querying and contains
26 | testing is case insensitive::
27 |
28 | cid = CaseInsensitiveDict()
29 | cid['Accept'] = 'application/json'
30 | cid['aCCEPT'] == 'application/json' # True
31 | list(cid) == ['Accept'] # True
32 |
33 | For example, ``headers['content-encoding']`` will return the
34 | value of a ``'Content-Encoding'`` response header, regardless
35 | of how the header name was originally stored.
36 |
37 | If the constructor, ``.update``, or equality comparison
38 | operations are given keys that have equal ``.lower()``s, the
39 | behavior is undefined.
40 | """
41 |
42 | def __init__(self, data=None, **kwargs):
43 | self._store = OrderedDict()
44 | if data is None:
45 | data = {}
46 | self.update(data, **kwargs)
47 |
48 | def __setitem__(self, key, value):
49 | # Use the lowercased key for lookups, but store the actual
50 | # key alongside the value.
51 | self._store[key.lower()] = (key, value)
52 |
53 | def __getitem__(self, key):
54 | return self._store[key.lower()][1]
55 |
56 | def __delitem__(self, key):
57 | del self._store[key.lower()]
58 |
59 | def __iter__(self):
60 | return (casedkey for casedkey, mappedvalue in self._store.values())
61 |
62 | def __len__(self):
63 | return len(self._store)
64 |
65 | def lower_items(self):
66 | """Like iteritems(), but with all lowercase keys."""
67 | return (
68 | (lowerkey, keyval[1])
69 | for (lowerkey, keyval)
70 | in self._store.items()
71 | )
72 |
73 | def __eq__(self, other):
74 | if isinstance(other, collections.Mapping):
75 | other = CaseInsensitiveDict(other)
76 | else:
77 | return NotImplemented
78 | # Compare insensitively
79 | return dict(self.lower_items()) == dict(other.lower_items())
80 |
81 | # Copy is required
82 | def copy(self):
83 | return CaseInsensitiveDict(self._store.values())
84 |
85 | def __repr__(self):
86 | return str(dict(self.items()))
87 |
88 |
89 | class LookupDict(dict):
90 | """Dictionary lookup object."""
91 |
92 | def __init__(self, name=None):
93 | self.name = name
94 | super(LookupDict, self).__init__()
95 |
96 | def __repr__(self):
97 | return '' % (self.name)
98 |
99 | def __getitem__(self, key):
100 | # We allow fall-through here, so values default to None
101 |
102 | return self.__dict__.get(key, None)
103 |
104 | def get(self, key, default=None):
105 | return self.__dict__.get(key, default)
106 |
--------------------------------------------------------------------------------
/docker/requests/structures.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/structures.pyc
--------------------------------------------------------------------------------
/docker/requests/utils.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/requests/utils.pyc
--------------------------------------------------------------------------------
/docker/sonarr_discord.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import logging
4 | import sys
5 | import json
6 | import re
7 | import datetime
8 | import requests
9 | import script_config
10 |
11 | discord_headers = {'content-type': 'application/json'}
12 |
13 | # Set up the log file
14 | log_filename = os.path.join(os.path.dirname(sys.argv[0]), 'sonarr_notification.log')
15 | logging.basicConfig(
16 | filename=log_filename,
17 | level=logging.INFO,
18 | format='[%(asctime)s] %(levelname)s - %(message)s')
19 | log = logging.getLogger('Sonarr')
20 |
21 | def utc_now_iso():
22 | utcnow = datetime.datetime.utcnow()
23 | return utcnow.isoformat()
24 |
25 | def convert_string_to_int(list):
26 | int_list = [int(x) for x in list.split(',') if x.strip().isdigit()]
27 | return int_list
28 |
29 | def main():
30 | # Get/set ENV variables
31 | eventtype = os.environ.get('sonarr_eventtype')
32 |
33 | season = os.environ.get('sonarr_episodefile_seasonnumber')
34 |
35 | episode = os.environ.get('sonarr_episodefile_episodenumbers')
36 |
37 | tvdb_id = os.environ.get('sonarr_series_tvdbid')
38 |
39 | scene_name = os.environ.get('sonarr_episodefile_scenename')
40 |
41 | media_title = os.environ.get('sonarr_series_title')
42 |
43 | episode_title = os.environ.get('sonarr_episodefile_episodetitles')
44 |
45 | quality = os.environ.get('sonarr_episodefile_quality')
46 |
47 | is_upgrade = os.environ.get('sonarr_isupgrade')
48 |
49 | if eventtype == 'Test':
50 | log.info('Sonarr script test succeeded.')
51 | sys.exit(0)
52 |
53 | # Get show information from skyhook
54 | get_skyhook = requests.get(script_config.skyhook_url + str(tvdb_id))
55 |
56 | skyhook_data = get_skyhook.json()
57 |
58 | title_slug = skyhook_data['slug']
59 |
60 | # Get banner image for show
61 | try:
62 | banner = skyhook_data['seasons'][int(season)]['images'][1]['url']
63 | except Exception as ex:
64 | log.error('Season banner not found! Failing back to series banner... ({})'.format(ex))
65 | banner = skyhook_data['images'][1]['url']
66 |
67 | for line in skyhook_data['episodes']:
68 | try:
69 | if int(line['seasonNumber']) == int(season) and \
70 | int(line['episodeNumber']) == convert_string_to_int(episode)[0]:
71 |
72 | if line['overview']:
73 | overview = line['overview']
74 | else:
75 | overview = skyhook_data['overview']
76 |
77 | except Exception as ex:
78 | log.error('Failed to get episode from skyhook! Failing back to series overview... ({})'.format(ex))
79 | overview = skyhook_data['overview']
80 |
81 | if len(str(season)) == 1:
82 | season = '0{}'.format(season)
83 |
84 | if len(str(episode)) == 1:
85 | episode = '0{}'.format(episode)
86 |
87 | if is_upgrade == 'True':
88 | content = 'Upgraded Episode - {}: {}'.format(media_title, episode_title)
89 | is_upgrade = 'Yes!'
90 |
91 | else:
92 | content = 'New episode downloaded - {}: {}'.format(media_title, episode_title)
93 | is_upgrade = 'Nope'
94 |
95 | try:
96 | content_rating = skyhook_data['contentRating']
97 | except:
98 | content_rating = 'Unset'
99 |
100 | try:
101 | network = skyhook_data['network']
102 | except:
103 | network = 'Unset'
104 |
105 | try:
106 | genres = json.dumps(skyhook_data['genres'])
107 | genres = re.sub(r'[?|$|.|!|:|/|\]|\[|\"]', r'', genres)
108 | except:
109 | genres = 'Unset'
110 |
111 | message = {
112 | "username": script_config.sonarr_discord_user,
113 | "content": content,
114 | "embeds": [
115 | {
116 | "author": {
117 | "name": "TV",
118 | "url": script_config.sonarr_url,
119 | "icon_url": script_config.sonarr_icon
120 | },
121 | "title": "{}: {}".format(media_title, episode_title),
122 | "color": 3394662,
123 | "url": "{}series/{}".format(script_config.sonarr_url, title_slug),
124 | "image": {
125 | "url": banner
126 | },
127 | },
128 | {
129 | "title": "Overview",
130 | "color": 3381708,
131 | "description": overview,
132 | "fields": [
133 | {
134 | "name": "Episode",
135 | "value": "s{}e{}".format(season, episode),
136 | "inline": True
137 | },
138 | {
139 | "name": "Quality",
140 | "value": quality,
141 | "inline": True
142 | },
143 | {
144 | "name": "Upgrade?",
145 | "value": is_upgrade,
146 | "inline": True
147 | },
148 | {
149 | "name": "Content Rating",
150 | "value": content_rating,
151 | "inline": True
152 | },
153 | {
154 | "name": "Network",
155 | "value": network,
156 | "inline": True
157 | },
158 | {
159 | "name": "Genres",
160 | "value": genres,
161 | "inline": True
162 | }
163 | ],
164 | "footer": {
165 | "text": "{}".format(scene_name)
166 | },
167 | "timestamp": utc_now_iso()
168 | }
169 |
170 | ]
171 | }
172 |
173 | log.info(json.dumps(message, sort_keys=True, indent=4, separators=(',', ': ')))
174 |
175 | # Send notification
176 | sender = requests.post(script_config.sonarr_discord_url, headers=discord_headers, json=message)
177 |
178 | # Log response
179 | log.info(sender.content)
180 |
181 | # Call main
182 | main()
183 |
--------------------------------------------------------------------------------
/docker/urllib3/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | urllib3 - Thread-safe connection pooling and re-using.
3 | """
4 |
5 | from __future__ import absolute_import
6 | import warnings
7 |
8 | from .connectionpool import (
9 | HTTPConnectionPool,
10 | HTTPSConnectionPool,
11 | connection_from_url
12 | )
13 |
14 | from . import exceptions
15 | from .filepost import encode_multipart_formdata
16 | from .poolmanager import PoolManager, ProxyManager, proxy_from_url
17 | from .response import HTTPResponse
18 | from .util.request import make_headers
19 | from .util.url import get_host
20 | from .util.timeout import Timeout
21 | from .util.retry import Retry
22 |
23 |
24 | # Set default logging handler to avoid "No handler found" warnings.
25 | import logging
26 | try: # Python 2.7+
27 | from logging import NullHandler
28 | except ImportError:
29 | class NullHandler(logging.Handler):
30 | def emit(self, record):
31 | pass
32 |
33 | __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
34 | __license__ = 'MIT'
35 | __version__ = '1.22'
36 |
37 | __all__ = (
38 | 'HTTPConnectionPool',
39 | 'HTTPSConnectionPool',
40 | 'PoolManager',
41 | 'ProxyManager',
42 | 'HTTPResponse',
43 | 'Retry',
44 | 'Timeout',
45 | 'add_stderr_logger',
46 | 'connection_from_url',
47 | 'disable_warnings',
48 | 'encode_multipart_formdata',
49 | 'get_host',
50 | 'make_headers',
51 | 'proxy_from_url',
52 | )
53 |
54 | logging.getLogger(__name__).addHandler(NullHandler())
55 |
56 |
57 | def add_stderr_logger(level=logging.DEBUG):
58 | """
59 | Helper for quickly adding a StreamHandler to the logger. Useful for
60 | debugging.
61 |
62 | Returns the handler after adding it.
63 | """
64 | # This method needs to be in this __init__.py to get the __name__ correct
65 | # even if urllib3 is vendored within another package.
66 | logger = logging.getLogger(__name__)
67 | handler = logging.StreamHandler()
68 | handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
69 | logger.addHandler(handler)
70 | logger.setLevel(level)
71 | logger.debug('Added a stderr logging handler to logger: %s', __name__)
72 | return handler
73 |
74 |
75 | # ... Clean up.
76 | del NullHandler
77 |
78 |
79 | # All warning filters *must* be appended unless you're really certain that they
80 | # shouldn't be: otherwise, it's very hard for users to use most Python
81 | # mechanisms to silence them.
82 | # SecurityWarning's always go off by default.
83 | warnings.simplefilter('always', exceptions.SecurityWarning, append=True)
84 | # SubjectAltNameWarning's should go off once per host
85 | warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True)
86 | # InsecurePlatformWarning's don't vary between requests, so we keep it default.
87 | warnings.simplefilter('default', exceptions.InsecurePlatformWarning,
88 | append=True)
89 | # SNIMissingWarnings should go off only once.
90 | warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True)
91 |
92 |
93 | def disable_warnings(category=exceptions.HTTPWarning):
94 | """
95 | Helper for quickly disabling all urllib3 warnings.
96 | """
97 | warnings.simplefilter('ignore', category)
98 |
--------------------------------------------------------------------------------
/docker/urllib3/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/__init__.pyc
--------------------------------------------------------------------------------
/docker/urllib3/_collections.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/_collections.pyc
--------------------------------------------------------------------------------
/docker/urllib3/connection.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/connection.pyc
--------------------------------------------------------------------------------
/docker/urllib3/connectionpool.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/connectionpool.pyc
--------------------------------------------------------------------------------
/docker/urllib3/contrib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/__init__.py
--------------------------------------------------------------------------------
/docker/urllib3/contrib/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/__init__.pyc
--------------------------------------------------------------------------------
/docker/urllib3/contrib/_securetransport/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/_securetransport/__init__.py
--------------------------------------------------------------------------------
/docker/urllib3/contrib/appengine.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/appengine.pyc
--------------------------------------------------------------------------------
/docker/urllib3/contrib/ntlmpool.py:
--------------------------------------------------------------------------------
1 | """
2 | NTLM authenticating pool, contributed by erikcederstran
3 |
4 | Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
5 | """
6 | from __future__ import absolute_import
7 |
8 | from logging import getLogger
9 | from ntlm import ntlm
10 |
11 | from .. import HTTPSConnectionPool
12 | from ..packages.six.moves.http_client import HTTPSConnection
13 |
14 |
15 | log = getLogger(__name__)
16 |
17 |
18 | class NTLMConnectionPool(HTTPSConnectionPool):
19 | """
20 | Implements an NTLM authentication version of an urllib3 connection pool
21 | """
22 |
23 | scheme = 'https'
24 |
25 | def __init__(self, user, pw, authurl, *args, **kwargs):
26 | """
27 | authurl is a random URL on the server that is protected by NTLM.
28 | user is the Windows user, probably in the DOMAIN\\username format.
29 | pw is the password for the user.
30 | """
31 | super(NTLMConnectionPool, self).__init__(*args, **kwargs)
32 | self.authurl = authurl
33 | self.rawuser = user
34 | user_parts = user.split('\\', 1)
35 | self.domain = user_parts[0].upper()
36 | self.user = user_parts[1]
37 | self.pw = pw
38 |
39 | def _new_conn(self):
40 | # Performs the NTLM handshake that secures the connection. The socket
41 | # must be kept open while requests are performed.
42 | self.num_connections += 1
43 | log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s',
44 | self.num_connections, self.host, self.authurl)
45 |
46 | headers = {}
47 | headers['Connection'] = 'Keep-Alive'
48 | req_header = 'Authorization'
49 | resp_header = 'www-authenticate'
50 |
51 | conn = HTTPSConnection(host=self.host, port=self.port)
52 |
53 | # Send negotiation message
54 | headers[req_header] = (
55 | 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
56 | log.debug('Request headers: %s', headers)
57 | conn.request('GET', self.authurl, None, headers)
58 | res = conn.getresponse()
59 | reshdr = dict(res.getheaders())
60 | log.debug('Response status: %s %s', res.status, res.reason)
61 | log.debug('Response headers: %s', reshdr)
62 | log.debug('Response data: %s [...]', res.read(100))
63 |
64 | # Remove the reference to the socket, so that it can not be closed by
65 | # the response object (we want to keep the socket open)
66 | res.fp = None
67 |
68 | # Server should respond with a challenge message
69 | auth_header_values = reshdr[resp_header].split(', ')
70 | auth_header_value = None
71 | for s in auth_header_values:
72 | if s[:5] == 'NTLM ':
73 | auth_header_value = s[5:]
74 | if auth_header_value is None:
75 | raise Exception('Unexpected %s response header: %s' %
76 | (resp_header, reshdr[resp_header]))
77 |
78 | # Send authentication message
79 | ServerChallenge, NegotiateFlags = \
80 | ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value)
81 | auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge,
82 | self.user,
83 | self.domain,
84 | self.pw,
85 | NegotiateFlags)
86 | headers[req_header] = 'NTLM %s' % auth_msg
87 | log.debug('Request headers: %s', headers)
88 | conn.request('GET', self.authurl, None, headers)
89 | res = conn.getresponse()
90 | log.debug('Response status: %s %s', res.status, res.reason)
91 | log.debug('Response headers: %s', dict(res.getheaders()))
92 | log.debug('Response data: %s [...]', res.read()[:100])
93 | if res.status != 200:
94 | if res.status == 401:
95 | raise Exception('Server rejected request: wrong '
96 | 'username or password')
97 | raise Exception('Wrong server response: %s %s' %
98 | (res.status, res.reason))
99 |
100 | res.fp = None
101 | log.debug('Connection established')
102 | return conn
103 |
104 | def urlopen(self, method, url, body=None, headers=None, retries=3,
105 | redirect=True, assert_same_host=True):
106 | if headers is None:
107 | headers = {}
108 | headers['Connection'] = 'Keep-Alive'
109 | return super(NTLMConnectionPool, self).urlopen(method, url, body,
110 | headers, retries,
111 | redirect,
112 | assert_same_host)
113 |
--------------------------------------------------------------------------------
/docker/urllib3/contrib/pyopenssl.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/pyopenssl.pyc
--------------------------------------------------------------------------------
/docker/urllib3/contrib/socks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This module contains provisional support for SOCKS proxies from within
4 | urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and
5 | SOCKS5. To enable its functionality, either install PySocks or install this
6 | module with the ``socks`` extra.
7 |
8 | The SOCKS implementation supports the full range of urllib3 features. It also
9 | supports the following SOCKS features:
10 |
11 | - SOCKS4
12 | - SOCKS4a
13 | - SOCKS5
14 | - Usernames and passwords for the SOCKS proxy
15 |
16 | Known Limitations:
17 |
18 | - Currently PySocks does not support contacting remote websites via literal
19 | IPv6 addresses. Any such connection attempt will fail. You must use a domain
20 | name.
21 | - Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any
22 | such connection attempt will fail.
23 | """
24 | from __future__ import absolute_import
25 |
26 | try:
27 | import socks
28 | except ImportError:
29 | import warnings
30 | from ..exceptions import DependencyWarning
31 |
32 | warnings.warn((
33 | 'SOCKS support in urllib3 requires the installation of optional '
34 | 'dependencies: specifically, PySocks. For more information, see '
35 | 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies'
36 | ),
37 | DependencyWarning
38 | )
39 | raise
40 |
41 | from socket import error as SocketError, timeout as SocketTimeout
42 |
43 | from ..connection import (
44 | HTTPConnection, HTTPSConnection
45 | )
46 | from ..connectionpool import (
47 | HTTPConnectionPool, HTTPSConnectionPool
48 | )
49 | from ..exceptions import ConnectTimeoutError, NewConnectionError
50 | from ..poolmanager import PoolManager
51 | from ..util.url import parse_url
52 |
53 | try:
54 | import ssl
55 | except ImportError:
56 | ssl = None
57 |
58 |
59 | class SOCKSConnection(HTTPConnection):
60 | """
61 | A plain-text HTTP connection that connects via a SOCKS proxy.
62 | """
63 | def __init__(self, *args, **kwargs):
64 | self._socks_options = kwargs.pop('_socks_options')
65 | super(SOCKSConnection, self).__init__(*args, **kwargs)
66 |
67 | def _new_conn(self):
68 | """
69 | Establish a new connection via the SOCKS proxy.
70 | """
71 | extra_kw = {}
72 | if self.source_address:
73 | extra_kw['source_address'] = self.source_address
74 |
75 | if self.socket_options:
76 | extra_kw['socket_options'] = self.socket_options
77 |
78 | try:
79 | conn = socks.create_connection(
80 | (self.host, self.port),
81 | proxy_type=self._socks_options['socks_version'],
82 | proxy_addr=self._socks_options['proxy_host'],
83 | proxy_port=self._socks_options['proxy_port'],
84 | proxy_username=self._socks_options['username'],
85 | proxy_password=self._socks_options['password'],
86 | proxy_rdns=self._socks_options['rdns'],
87 | timeout=self.timeout,
88 | **extra_kw
89 | )
90 |
91 | except SocketTimeout as e:
92 | raise ConnectTimeoutError(
93 | self, "Connection to %s timed out. (connect timeout=%s)" %
94 | (self.host, self.timeout))
95 |
96 | except socks.ProxyError as e:
97 | # This is fragile as hell, but it seems to be the only way to raise
98 | # useful errors here.
99 | if e.socket_err:
100 | error = e.socket_err
101 | if isinstance(error, SocketTimeout):
102 | raise ConnectTimeoutError(
103 | self,
104 | "Connection to %s timed out. (connect timeout=%s)" %
105 | (self.host, self.timeout)
106 | )
107 | else:
108 | raise NewConnectionError(
109 | self,
110 | "Failed to establish a new connection: %s" % error
111 | )
112 | else:
113 | raise NewConnectionError(
114 | self,
115 | "Failed to establish a new connection: %s" % e
116 | )
117 |
118 | except SocketError as e: # Defensive: PySocks should catch all these.
119 | raise NewConnectionError(
120 | self, "Failed to establish a new connection: %s" % e)
121 |
122 | return conn
123 |
124 |
125 | # We don't need to duplicate the Verified/Unverified distinction from
126 | # urllib3/connection.py here because the HTTPSConnection will already have been
127 | # correctly set to either the Verified or Unverified form by that module. This
128 | # means the SOCKSHTTPSConnection will automatically be the correct type.
129 | class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
130 | pass
131 |
132 |
133 | class SOCKSHTTPConnectionPool(HTTPConnectionPool):
134 | ConnectionCls = SOCKSConnection
135 |
136 |
137 | class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
138 | ConnectionCls = SOCKSHTTPSConnection
139 |
140 |
141 | class SOCKSProxyManager(PoolManager):
142 | """
143 | A version of the urllib3 ProxyManager that routes connections via the
144 | defined SOCKS proxy.
145 | """
146 | pool_classes_by_scheme = {
147 | 'http': SOCKSHTTPConnectionPool,
148 | 'https': SOCKSHTTPSConnectionPool,
149 | }
150 |
151 | def __init__(self, proxy_url, username=None, password=None,
152 | num_pools=10, headers=None, **connection_pool_kw):
153 | parsed = parse_url(proxy_url)
154 |
155 | if parsed.scheme == 'socks5':
156 | socks_version = socks.PROXY_TYPE_SOCKS5
157 | rdns = False
158 | elif parsed.scheme == 'socks5h':
159 | socks_version = socks.PROXY_TYPE_SOCKS5
160 | rdns = True
161 | elif parsed.scheme == 'socks4':
162 | socks_version = socks.PROXY_TYPE_SOCKS4
163 | rdns = False
164 | elif parsed.scheme == 'socks4a':
165 | socks_version = socks.PROXY_TYPE_SOCKS4
166 | rdns = True
167 | else:
168 | raise ValueError(
169 | "Unable to determine SOCKS version from %s" % proxy_url
170 | )
171 |
172 | self.proxy_url = proxy_url
173 |
174 | socks_options = {
175 | 'socks_version': socks_version,
176 | 'proxy_host': parsed.host,
177 | 'proxy_port': parsed.port,
178 | 'username': username,
179 | 'password': password,
180 | 'rdns': rdns
181 | }
182 | connection_pool_kw['_socks_options'] = socks_options
183 |
184 | super(SOCKSProxyManager, self).__init__(
185 | num_pools, headers, **connection_pool_kw
186 | )
187 |
188 | self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme
189 |
--------------------------------------------------------------------------------
/docker/urllib3/contrib/socks.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/contrib/socks.pyc
--------------------------------------------------------------------------------
/docker/urllib3/exceptions.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/exceptions.pyc
--------------------------------------------------------------------------------
/docker/urllib3/fields.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import email.utils
3 | import mimetypes
4 |
5 | from .packages import six
6 |
7 |
8 | def guess_content_type(filename, default='application/octet-stream'):
9 | """
10 | Guess the "Content-Type" of a file.
11 |
12 | :param filename:
13 | The filename to guess the "Content-Type" of using :mod:`mimetypes`.
14 | :param default:
15 | If no "Content-Type" can be guessed, default to `default`.
16 | """
17 | if filename:
18 | return mimetypes.guess_type(filename)[0] or default
19 | return default
20 |
21 |
22 | def format_header_param(name, value):
23 | """
24 | Helper function to format and quote a single header parameter.
25 |
26 | Particularly useful for header parameters which might contain
27 | non-ASCII values, like file names. This follows RFC 2231, as
28 | suggested by RFC 2388 Section 4.4.
29 |
30 | :param name:
31 | The name of the parameter, a string expected to be ASCII only.
32 | :param value:
33 | The value of the parameter, provided as a unicode string.
34 | """
35 | if not any(ch in value for ch in '"\\\r\n'):
36 | result = '%s="%s"' % (name, value)
37 | try:
38 | result.encode('ascii')
39 | except (UnicodeEncodeError, UnicodeDecodeError):
40 | pass
41 | else:
42 | return result
43 | if not six.PY3 and isinstance(value, six.text_type): # Python 2:
44 | value = value.encode('utf-8')
45 | value = email.utils.encode_rfc2231(value, 'utf-8')
46 | value = '%s*=%s' % (name, value)
47 | return value
48 |
49 |
50 | class RequestField(object):
51 | """
52 | A data container for request body parameters.
53 |
54 | :param name:
55 | The name of this request field.
56 | :param data:
57 | The data/value body.
58 | :param filename:
59 | An optional filename of the request field.
60 | :param headers:
61 | An optional dict-like object of headers to initially use for the field.
62 | """
63 | def __init__(self, name, data, filename=None, headers=None):
64 | self._name = name
65 | self._filename = filename
66 | self.data = data
67 | self.headers = {}
68 | if headers:
69 | self.headers = dict(headers)
70 |
71 | @classmethod
72 | def from_tuples(cls, fieldname, value):
73 | """
74 | A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters.
75 |
76 | Supports constructing :class:`~urllib3.fields.RequestField` from
77 | parameter of key/value strings AND key/filetuple. A filetuple is a
78 | (filename, data, MIME type) tuple where the MIME type is optional.
79 | For example::
80 |
81 | 'foo': 'bar',
82 | 'fakefile': ('foofile.txt', 'contents of foofile'),
83 | 'realfile': ('barfile.txt', open('realfile').read()),
84 | 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'),
85 | 'nonamefile': 'contents of nonamefile field',
86 |
87 | Field names and filenames must be unicode.
88 | """
89 | if isinstance(value, tuple):
90 | if len(value) == 3:
91 | filename, data, content_type = value
92 | else:
93 | filename, data = value
94 | content_type = guess_content_type(filename)
95 | else:
96 | filename = None
97 | content_type = None
98 | data = value
99 |
100 | request_param = cls(fieldname, data, filename=filename)
101 | request_param.make_multipart(content_type=content_type)
102 |
103 | return request_param
104 |
105 | def _render_part(self, name, value):
106 | """
107 | Overridable helper function to format a single header parameter.
108 |
109 | :param name:
110 | The name of the parameter, a string expected to be ASCII only.
111 | :param value:
112 | The value of the parameter, provided as a unicode string.
113 | """
114 | return format_header_param(name, value)
115 |
116 | def _render_parts(self, header_parts):
117 | """
118 | Helper function to format and quote a single header.
119 |
120 | Useful for single headers that are composed of multiple items. E.g.,
121 | 'Content-Disposition' fields.
122 |
123 | :param header_parts:
124 | A sequence of (k, v) typles or a :class:`dict` of (k, v) to format
125 | as `k1="v1"; k2="v2"; ...`.
126 | """
127 | parts = []
128 | iterable = header_parts
129 | if isinstance(header_parts, dict):
130 | iterable = header_parts.items()
131 |
132 | for name, value in iterable:
133 | if value is not None:
134 | parts.append(self._render_part(name, value))
135 |
136 | return '; '.join(parts)
137 |
138 | def render_headers(self):
139 | """
140 | Renders the headers for this request field.
141 | """
142 | lines = []
143 |
144 | sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location']
145 | for sort_key in sort_keys:
146 | if self.headers.get(sort_key, False):
147 | lines.append('%s: %s' % (sort_key, self.headers[sort_key]))
148 |
149 | for header_name, header_value in self.headers.items():
150 | if header_name not in sort_keys:
151 | if header_value:
152 | lines.append('%s: %s' % (header_name, header_value))
153 |
154 | lines.append('\r\n')
155 | return '\r\n'.join(lines)
156 |
157 | def make_multipart(self, content_disposition=None, content_type=None,
158 | content_location=None):
159 | """
160 | Makes this request field into a multipart request field.
161 |
162 | This method overrides "Content-Disposition", "Content-Type" and
163 | "Content-Location" headers to the request parameter.
164 |
165 | :param content_type:
166 | The 'Content-Type' of the request body.
167 | :param content_location:
168 | The 'Content-Location' of the request body.
169 |
170 | """
171 | self.headers['Content-Disposition'] = content_disposition or 'form-data'
172 | self.headers['Content-Disposition'] += '; '.join([
173 | '', self._render_parts(
174 | (('name', self._name), ('filename', self._filename))
175 | )
176 | ])
177 | self.headers['Content-Type'] = content_type
178 | self.headers['Content-Location'] = content_location
179 |
--------------------------------------------------------------------------------
/docker/urllib3/fields.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/fields.pyc
--------------------------------------------------------------------------------
/docker/urllib3/filepost.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import codecs
3 |
4 | from uuid import uuid4
5 | from io import BytesIO
6 |
7 | from .packages import six
8 | from .packages.six import b
9 | from .fields import RequestField
10 |
11 | writer = codecs.lookup('utf-8')[3]
12 |
13 |
14 | def choose_boundary():
15 | """
16 | Our embarrassingly-simple replacement for mimetools.choose_boundary.
17 | """
18 | return uuid4().hex
19 |
20 |
21 | def iter_field_objects(fields):
22 | """
23 | Iterate over fields.
24 |
25 | Supports list of (k, v) tuples and dicts, and lists of
26 | :class:`~urllib3.fields.RequestField`.
27 |
28 | """
29 | if isinstance(fields, dict):
30 | i = six.iteritems(fields)
31 | else:
32 | i = iter(fields)
33 |
34 | for field in i:
35 | if isinstance(field, RequestField):
36 | yield field
37 | else:
38 | yield RequestField.from_tuples(*field)
39 |
40 |
41 | def iter_fields(fields):
42 | """
43 | .. deprecated:: 1.6
44 |
45 | Iterate over fields.
46 |
47 | The addition of :class:`~urllib3.fields.RequestField` makes this function
48 | obsolete. Instead, use :func:`iter_field_objects`, which returns
49 | :class:`~urllib3.fields.RequestField` objects.
50 |
51 | Supports list of (k, v) tuples and dicts.
52 | """
53 | if isinstance(fields, dict):
54 | return ((k, v) for k, v in six.iteritems(fields))
55 |
56 | return ((k, v) for k, v in fields)
57 |
58 |
59 | def encode_multipart_formdata(fields, boundary=None):
60 | """
61 | Encode a dictionary of ``fields`` using the multipart/form-data MIME format.
62 |
63 | :param fields:
64 | Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
65 |
66 | :param boundary:
67 | If not specified, then a random boundary will be generated using
68 | :func:`mimetools.choose_boundary`.
69 | """
70 | body = BytesIO()
71 | if boundary is None:
72 | boundary = choose_boundary()
73 |
74 | for field in iter_field_objects(fields):
75 | body.write(b('--%s\r\n' % (boundary)))
76 |
77 | writer(body).write(field.render_headers())
78 | data = field.data
79 |
80 | if isinstance(data, int):
81 | data = str(data) # Backwards compatibility
82 |
83 | if isinstance(data, six.text_type):
84 | writer(body).write(data)
85 | else:
86 | body.write(data)
87 |
88 | body.write(b'\r\n')
89 |
90 | body.write(b('--%s--\r\n' % (boundary)))
91 |
92 | content_type = str('multipart/form-data; boundary=%s' % boundary)
93 |
94 | return body.getvalue(), content_type
95 |
--------------------------------------------------------------------------------
/docker/urllib3/filepost.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/filepost.pyc
--------------------------------------------------------------------------------
/docker/urllib3/packages/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from . import ssl_match_hostname
4 |
5 | __all__ = ('ssl_match_hostname', )
6 |
--------------------------------------------------------------------------------
/docker/urllib3/packages/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/__init__.pyc
--------------------------------------------------------------------------------
/docker/urllib3/packages/backports/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/backports/__init__.py
--------------------------------------------------------------------------------
/docker/urllib3/packages/backports/makefile.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | backports.makefile
4 | ~~~~~~~~~~~~~~~~~~
5 |
6 | Backports the Python 3 ``socket.makefile`` method for use with anything that
7 | wants to create a "fake" socket object.
8 | """
9 | import io
10 |
11 | from socket import SocketIO
12 |
13 |
14 | def backport_makefile(self, mode="r", buffering=None, encoding=None,
15 | errors=None, newline=None):
16 | """
17 | Backport of ``socket.makefile`` from Python 3.5.
18 | """
19 | if not set(mode) <= set(["r", "w", "b"]):
20 | raise ValueError(
21 | "invalid mode %r (only r, w, b allowed)" % (mode,)
22 | )
23 | writing = "w" in mode
24 | reading = "r" in mode or not writing
25 | assert reading or writing
26 | binary = "b" in mode
27 | rawmode = ""
28 | if reading:
29 | rawmode += "r"
30 | if writing:
31 | rawmode += "w"
32 | raw = SocketIO(self, rawmode)
33 | self._makefile_refs += 1
34 | if buffering is None:
35 | buffering = -1
36 | if buffering < 0:
37 | buffering = io.DEFAULT_BUFFER_SIZE
38 | if buffering == 0:
39 | if not binary:
40 | raise ValueError("unbuffered streams must be binary")
41 | return raw
42 | if reading and writing:
43 | buffer = io.BufferedRWPair(raw, raw, buffering)
44 | elif reading:
45 | buffer = io.BufferedReader(raw, buffering)
46 | else:
47 | assert writing
48 | buffer = io.BufferedWriter(raw, buffering)
49 | if binary:
50 | return buffer
51 | text = io.TextIOWrapper(buffer, encoding, errors, newline)
52 | text.mode = mode
53 | return text
54 |
--------------------------------------------------------------------------------
/docker/urllib3/packages/ordered_dict.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/ordered_dict.pyc
--------------------------------------------------------------------------------
/docker/urllib3/packages/six.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/six.pyc
--------------------------------------------------------------------------------
/docker/urllib3/packages/ssl_match_hostname/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | try:
4 | # Our match_hostname function is the same as 3.5's, so we only want to
5 | # import the match_hostname function if it's at least that good.
6 | if sys.version_info < (3, 5):
7 | raise ImportError("Fallback to vendored code")
8 |
9 | from ssl import CertificateError, match_hostname
10 | except ImportError:
11 | try:
12 | # Backport of the function from a pypi module
13 | from backports.ssl_match_hostname import CertificateError, match_hostname
14 | except ImportError:
15 | # Our vendored copy
16 | from ._implementation import CertificateError, match_hostname
17 |
18 | # Not needed, but documenting what we provide.
19 | __all__ = ('CertificateError', 'match_hostname')
20 |
--------------------------------------------------------------------------------
/docker/urllib3/packages/ssl_match_hostname/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/ssl_match_hostname/__init__.pyc
--------------------------------------------------------------------------------
/docker/urllib3/packages/ssl_match_hostname/_implementation.py:
--------------------------------------------------------------------------------
1 | """The match_hostname() function from Python 3.3.3, essential when using SSL."""
2 |
3 | # Note: This file is under the PSF license as the code comes from the python
4 | # stdlib. http://docs.python.org/3/license.html
5 |
6 | import re
7 | import sys
8 |
9 | # ipaddress has been backported to 2.6+ in pypi. If it is installed on the
10 | # system, use it to handle IPAddress ServerAltnames (this was added in
11 | # python-3.5) otherwise only do DNS matching. This allows
12 | # backports.ssl_match_hostname to continue to be used all the way back to
13 | # python-2.4.
14 | try:
15 | import ipaddress
16 | except ImportError:
17 | ipaddress = None
18 |
19 | __version__ = '3.5.0.1'
20 |
21 |
22 | class CertificateError(ValueError):
23 | pass
24 |
25 |
26 | def _dnsname_match(dn, hostname, max_wildcards=1):
27 | """Matching according to RFC 6125, section 6.4.3
28 |
29 | http://tools.ietf.org/html/rfc6125#section-6.4.3
30 | """
31 | pats = []
32 | if not dn:
33 | return False
34 |
35 | # Ported from python3-syntax:
36 | # leftmost, *remainder = dn.split(r'.')
37 | parts = dn.split(r'.')
38 | leftmost = parts[0]
39 | remainder = parts[1:]
40 |
41 | wildcards = leftmost.count('*')
42 | if wildcards > max_wildcards:
43 | # Issue #17980: avoid denials of service by refusing more
44 | # than one wildcard per fragment. A survey of established
45 | # policy among SSL implementations showed it to be a
46 | # reasonable choice.
47 | raise CertificateError(
48 | "too many wildcards in certificate DNS name: " + repr(dn))
49 |
50 | # speed up common case w/o wildcards
51 | if not wildcards:
52 | return dn.lower() == hostname.lower()
53 |
54 | # RFC 6125, section 6.4.3, subitem 1.
55 | # The client SHOULD NOT attempt to match a presented identifier in which
56 | # the wildcard character comprises a label other than the left-most label.
57 | if leftmost == '*':
58 | # When '*' is a fragment by itself, it matches a non-empty dotless
59 | # fragment.
60 | pats.append('[^.]+')
61 | elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
62 | # RFC 6125, section 6.4.3, subitem 3.
63 | # The client SHOULD NOT attempt to match a presented identifier
64 | # where the wildcard character is embedded within an A-label or
65 | # U-label of an internationalized domain name.
66 | pats.append(re.escape(leftmost))
67 | else:
68 | # Otherwise, '*' matches any dotless string, e.g. www*
69 | pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
70 |
71 | # add the remaining fragments, ignore any wildcards
72 | for frag in remainder:
73 | pats.append(re.escape(frag))
74 |
75 | pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
76 | return pat.match(hostname)
77 |
78 |
79 | def _to_unicode(obj):
80 | if isinstance(obj, str) and sys.version_info < (3,):
81 | obj = unicode(obj, encoding='ascii', errors='strict')
82 | return obj
83 |
84 | def _ipaddress_match(ipname, host_ip):
85 | """Exact matching of IP addresses.
86 |
87 | RFC 6125 explicitly doesn't define an algorithm for this
88 | (section 1.7.2 - "Out of Scope").
89 | """
90 | # OpenSSL may add a trailing newline to a subjectAltName's IP address
91 | # Divergence from upstream: ipaddress can't handle byte str
92 | ip = ipaddress.ip_address(_to_unicode(ipname).rstrip())
93 | return ip == host_ip
94 |
95 |
96 | def match_hostname(cert, hostname):
97 | """Verify that *cert* (in decoded format as returned by
98 | SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
99 | rules are followed, but IP addresses are not accepted for *hostname*.
100 |
101 | CertificateError is raised on failure. On success, the function
102 | returns nothing.
103 | """
104 | if not cert:
105 | raise ValueError("empty or no certificate, match_hostname needs a "
106 | "SSL socket or SSL context with either "
107 | "CERT_OPTIONAL or CERT_REQUIRED")
108 | try:
109 | # Divergence from upstream: ipaddress can't handle byte str
110 | host_ip = ipaddress.ip_address(_to_unicode(hostname))
111 | except ValueError:
112 | # Not an IP address (common case)
113 | host_ip = None
114 | except UnicodeError:
115 | # Divergence from upstream: Have to deal with ipaddress not taking
116 | # byte strings. addresses should be all ascii, so we consider it not
117 | # an ipaddress in this case
118 | host_ip = None
119 | except AttributeError:
120 | # Divergence from upstream: Make ipaddress library optional
121 | if ipaddress is None:
122 | host_ip = None
123 | else:
124 | raise
125 | dnsnames = []
126 | san = cert.get('subjectAltName', ())
127 | for key, value in san:
128 | if key == 'DNS':
129 | if host_ip is None and _dnsname_match(value, hostname):
130 | return
131 | dnsnames.append(value)
132 | elif key == 'IP Address':
133 | if host_ip is not None and _ipaddress_match(value, host_ip):
134 | return
135 | dnsnames.append(value)
136 | if not dnsnames:
137 | # The subject is only checked when there is no dNSName entry
138 | # in subjectAltName
139 | for sub in cert.get('subject', ()):
140 | for key, value in sub:
141 | # XXX according to RFC 2818, the most specific Common Name
142 | # must be used.
143 | if key == 'commonName':
144 | if _dnsname_match(value, hostname):
145 | return
146 | dnsnames.append(value)
147 | if len(dnsnames) > 1:
148 | raise CertificateError("hostname %r "
149 | "doesn't match either of %s"
150 | % (hostname, ', '.join(map(repr, dnsnames))))
151 | elif len(dnsnames) == 1:
152 | raise CertificateError("hostname %r "
153 | "doesn't match %r"
154 | % (hostname, dnsnames[0]))
155 | else:
156 | raise CertificateError("no appropriate commonName or "
157 | "subjectAltName fields were found")
158 |
--------------------------------------------------------------------------------
/docker/urllib3/packages/ssl_match_hostname/_implementation.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/packages/ssl_match_hostname/_implementation.pyc
--------------------------------------------------------------------------------
/docker/urllib3/poolmanager.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/poolmanager.pyc
--------------------------------------------------------------------------------
/docker/urllib3/request.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .filepost import encode_multipart_formdata
4 | from .packages.six.moves.urllib.parse import urlencode
5 |
6 |
7 | __all__ = ['RequestMethods']
8 |
9 |
10 | class RequestMethods(object):
11 | """
12 | Convenience mixin for classes who implement a :meth:`urlopen` method, such
13 | as :class:`~urllib3.connectionpool.HTTPConnectionPool` and
14 | :class:`~urllib3.poolmanager.PoolManager`.
15 |
16 | Provides behavior for making common types of HTTP request methods and
17 | decides which type of request field encoding to use.
18 |
19 | Specifically,
20 |
21 | :meth:`.request_encode_url` is for sending requests whose fields are
22 | encoded in the URL (such as GET, HEAD, DELETE).
23 |
24 | :meth:`.request_encode_body` is for sending requests whose fields are
25 | encoded in the *body* of the request using multipart or www-form-urlencoded
26 | (such as for POST, PUT, PATCH).
27 |
28 | :meth:`.request` is for making any kind of request, it will look up the
29 | appropriate encoding format and use one of the above two methods to make
30 | the request.
31 |
32 | Initializer parameters:
33 |
34 | :param headers:
35 | Headers to include with all requests, unless other headers are given
36 | explicitly.
37 | """
38 |
39 | _encode_url_methods = set(['DELETE', 'GET', 'HEAD', 'OPTIONS'])
40 |
41 | def __init__(self, headers=None):
42 | self.headers = headers or {}
43 |
44 | def urlopen(self, method, url, body=None, headers=None,
45 | encode_multipart=True, multipart_boundary=None,
46 | **kw): # Abstract
47 | raise NotImplemented("Classes extending RequestMethods must implement "
48 | "their own ``urlopen`` method.")
49 |
50 | def request(self, method, url, fields=None, headers=None, **urlopen_kw):
51 | """
52 | Make a request using :meth:`urlopen` with the appropriate encoding of
53 | ``fields`` based on the ``method`` used.
54 |
55 | This is a convenience method that requires the least amount of manual
56 | effort. It can be used in most situations, while still having the
57 | option to drop down to more specific methods when necessary, such as
58 | :meth:`request_encode_url`, :meth:`request_encode_body`,
59 | or even the lowest level :meth:`urlopen`.
60 | """
61 | method = method.upper()
62 |
63 | if method in self._encode_url_methods:
64 | return self.request_encode_url(method, url, fields=fields,
65 | headers=headers,
66 | **urlopen_kw)
67 | else:
68 | return self.request_encode_body(method, url, fields=fields,
69 | headers=headers,
70 | **urlopen_kw)
71 |
72 | def request_encode_url(self, method, url, fields=None, headers=None,
73 | **urlopen_kw):
74 | """
75 | Make a request using :meth:`urlopen` with the ``fields`` encoded in
76 | the url. This is useful for request methods like GET, HEAD, DELETE, etc.
77 | """
78 | if headers is None:
79 | headers = self.headers
80 |
81 | extra_kw = {'headers': headers}
82 | extra_kw.update(urlopen_kw)
83 |
84 | if fields:
85 | url += '?' + urlencode(fields)
86 |
87 | return self.urlopen(method, url, **extra_kw)
88 |
89 | def request_encode_body(self, method, url, fields=None, headers=None,
90 | encode_multipart=True, multipart_boundary=None,
91 | **urlopen_kw):
92 | """
93 | Make a request using :meth:`urlopen` with the ``fields`` encoded in
94 | the body. This is useful for request methods like POST, PUT, PATCH, etc.
95 |
96 | When ``encode_multipart=True`` (default), then
97 | :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode
98 | the payload with the appropriate content type. Otherwise
99 | :meth:`urllib.urlencode` is used with the
100 | 'application/x-www-form-urlencoded' content type.
101 |
102 | Multipart encoding must be used when posting files, and it's reasonably
103 | safe to use it in other times too. However, it may break request
104 | signing, such as with OAuth.
105 |
106 | Supports an optional ``fields`` parameter of key/value strings AND
107 | key/filetuple. A filetuple is a (filename, data, MIME type) tuple where
108 | the MIME type is optional. For example::
109 |
110 | fields = {
111 | 'foo': 'bar',
112 | 'fakefile': ('foofile.txt', 'contents of foofile'),
113 | 'realfile': ('barfile.txt', open('realfile').read()),
114 | 'typedfile': ('bazfile.bin', open('bazfile').read(),
115 | 'image/jpeg'),
116 | 'nonamefile': 'contents of nonamefile field',
117 | }
118 |
119 | When uploading a file, providing a filename (the first parameter of the
120 | tuple) is optional but recommended to best mimick behavior of browsers.
121 |
122 | Note that if ``headers`` are supplied, the 'Content-Type' header will
123 | be overwritten because it depends on the dynamic random boundary string
124 | which is used to compose the body of the request. The random boundary
125 | string can be explicitly set with the ``multipart_boundary`` parameter.
126 | """
127 | if headers is None:
128 | headers = self.headers
129 |
130 | extra_kw = {'headers': {}}
131 |
132 | if fields:
133 | if 'body' in urlopen_kw:
134 | raise TypeError(
135 | "request got values for both 'fields' and 'body', can only specify one.")
136 |
137 | if encode_multipart:
138 | body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)
139 | else:
140 | body, content_type = urlencode(fields), 'application/x-www-form-urlencoded'
141 |
142 | extra_kw['body'] = body
143 | extra_kw['headers'] = {'Content-Type': content_type}
144 |
145 | extra_kw['headers'].update(headers)
146 | extra_kw.update(urlopen_kw)
147 |
148 | return self.urlopen(method, url, **extra_kw)
149 |
--------------------------------------------------------------------------------
/docker/urllib3/request.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/request.pyc
--------------------------------------------------------------------------------
/docker/urllib3/response.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/response.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | # For backwards compatibility, provide imports that used to be here.
3 | from .connection import is_connection_dropped
4 | from .request import make_headers
5 | from .response import is_fp_closed
6 | from .ssl_ import (
7 | SSLContext,
8 | HAS_SNI,
9 | IS_PYOPENSSL,
10 | IS_SECURETRANSPORT,
11 | assert_fingerprint,
12 | resolve_cert_reqs,
13 | resolve_ssl_version,
14 | ssl_wrap_socket,
15 | )
16 | from .timeout import (
17 | current_time,
18 | Timeout,
19 | )
20 |
21 | from .retry import Retry
22 | from .url import (
23 | get_host,
24 | parse_url,
25 | split_first,
26 | Url,
27 | )
28 | from .wait import (
29 | wait_for_read,
30 | wait_for_write
31 | )
32 |
33 | __all__ = (
34 | 'HAS_SNI',
35 | 'IS_PYOPENSSL',
36 | 'IS_SECURETRANSPORT',
37 | 'SSLContext',
38 | 'Retry',
39 | 'Timeout',
40 | 'Url',
41 | 'assert_fingerprint',
42 | 'current_time',
43 | 'is_connection_dropped',
44 | 'is_fp_closed',
45 | 'get_host',
46 | 'parse_url',
47 | 'make_headers',
48 | 'resolve_cert_reqs',
49 | 'resolve_ssl_version',
50 | 'split_first',
51 | 'ssl_wrap_socket',
52 | 'wait_for_read',
53 | 'wait_for_write'
54 | )
55 |
--------------------------------------------------------------------------------
/docker/urllib3/util/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/__init__.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/connection.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import socket
3 | from .wait import wait_for_read
4 | from .selectors import HAS_SELECT, SelectorError
5 |
6 |
7 | def is_connection_dropped(conn): # Platform-specific
8 | """
9 | Returns True if the connection is dropped and should be closed.
10 |
11 | :param conn:
12 | :class:`httplib.HTTPConnection` object.
13 |
14 | Note: For platforms like AppEngine, this will always return ``False`` to
15 | let the platform handle connection recycling transparently for us.
16 | """
17 | sock = getattr(conn, 'sock', False)
18 | if sock is False: # Platform-specific: AppEngine
19 | return False
20 | if sock is None: # Connection already closed (such as by httplib).
21 | return True
22 |
23 | if not HAS_SELECT:
24 | return False
25 |
26 | try:
27 | return bool(wait_for_read(sock, timeout=0.0))
28 | except SelectorError:
29 | return True
30 |
31 |
32 | # This function is copied from socket.py in the Python 2.7 standard
33 | # library test suite. Added to its signature is only `socket_options`.
34 | # One additional modification is that we avoid binding to IPv6 servers
35 | # discovered in DNS if the system doesn't have IPv6 functionality.
36 | def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
37 | source_address=None, socket_options=None):
38 | """Connect to *address* and return the socket object.
39 |
40 | Convenience function. Connect to *address* (a 2-tuple ``(host,
41 | port)``) and return the socket object. Passing the optional
42 | *timeout* parameter will set the timeout on the socket instance
43 | before attempting to connect. If no *timeout* is supplied, the
44 | global default timeout setting returned by :func:`getdefaulttimeout`
45 | is used. If *source_address* is set it must be a tuple of (host, port)
46 | for the socket to bind as a source address before making the connection.
47 | An host of '' or port 0 tells the OS to use the default.
48 | """
49 |
50 | host, port = address
51 | if host.startswith('['):
52 | host = host.strip('[]')
53 | err = None
54 |
55 | # Using the value from allowed_gai_family() in the context of getaddrinfo lets
56 | # us select whether to work with IPv4 DNS records, IPv6 records, or both.
57 | # The original create_connection function always returns all records.
58 | family = allowed_gai_family()
59 |
60 | for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
61 | af, socktype, proto, canonname, sa = res
62 | sock = None
63 | try:
64 | sock = socket.socket(af, socktype, proto)
65 |
66 | # If provided, set socket level options before connecting.
67 | _set_socket_options(sock, socket_options)
68 |
69 | if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
70 | sock.settimeout(timeout)
71 | if source_address:
72 | sock.bind(source_address)
73 | sock.connect(sa)
74 | return sock
75 |
76 | except socket.error as e:
77 | err = e
78 | if sock is not None:
79 | sock.close()
80 | sock = None
81 |
82 | if err is not None:
83 | raise err
84 |
85 | raise socket.error("getaddrinfo returns an empty list")
86 |
87 |
88 | def _set_socket_options(sock, options):
89 | if options is None:
90 | return
91 |
92 | for opt in options:
93 | sock.setsockopt(*opt)
94 |
95 |
96 | def allowed_gai_family():
97 | """This function is designed to work in the context of
98 | getaddrinfo, where family=socket.AF_UNSPEC is the default and
99 | will perform a DNS search for both IPv6 and IPv4 records."""
100 |
101 | family = socket.AF_INET
102 | if HAS_IPV6:
103 | family = socket.AF_UNSPEC
104 | return family
105 |
106 |
107 | def _has_ipv6(host):
108 | """ Returns True if the system can bind an IPv6 address. """
109 | sock = None
110 | has_ipv6 = False
111 |
112 | if socket.has_ipv6:
113 | # has_ipv6 returns true if cPython was compiled with IPv6 support.
114 | # It does not tell us if the system has IPv6 support enabled. To
115 | # determine that we must bind to an IPv6 address.
116 | # https://github.com/shazow/urllib3/pull/611
117 | # https://bugs.python.org/issue658327
118 | try:
119 | sock = socket.socket(socket.AF_INET6)
120 | sock.bind((host, 0))
121 | has_ipv6 = True
122 | except Exception:
123 | pass
124 |
125 | if sock:
126 | sock.close()
127 | return has_ipv6
128 |
129 |
130 | HAS_IPV6 = _has_ipv6('::1')
131 |
--------------------------------------------------------------------------------
/docker/urllib3/util/connection.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/connection.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/request.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from base64 import b64encode
3 |
4 | from ..packages.six import b, integer_types
5 | from ..exceptions import UnrewindableBodyError
6 |
7 | ACCEPT_ENCODING = 'gzip,deflate'
8 | _FAILEDTELL = object()
9 |
10 |
11 | def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
12 | basic_auth=None, proxy_basic_auth=None, disable_cache=None):
13 | """
14 | Shortcuts for generating request headers.
15 |
16 | :param keep_alive:
17 | If ``True``, adds 'connection: keep-alive' header.
18 |
19 | :param accept_encoding:
20 | Can be a boolean, list, or string.
21 | ``True`` translates to 'gzip,deflate'.
22 | List will get joined by comma.
23 | String will be used as provided.
24 |
25 | :param user_agent:
26 | String representing the user-agent you want, such as
27 | "python-urllib3/0.6"
28 |
29 | :param basic_auth:
30 | Colon-separated username:password string for 'authorization: basic ...'
31 | auth header.
32 |
33 | :param proxy_basic_auth:
34 | Colon-separated username:password string for 'proxy-authorization: basic ...'
35 | auth header.
36 |
37 | :param disable_cache:
38 | If ``True``, adds 'cache-control: no-cache' header.
39 |
40 | Example::
41 |
42 | >>> make_headers(keep_alive=True, user_agent="Batman/1.0")
43 | {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
44 | >>> make_headers(accept_encoding=True)
45 | {'accept-encoding': 'gzip,deflate'}
46 | """
47 | headers = {}
48 | if accept_encoding:
49 | if isinstance(accept_encoding, str):
50 | pass
51 | elif isinstance(accept_encoding, list):
52 | accept_encoding = ','.join(accept_encoding)
53 | else:
54 | accept_encoding = ACCEPT_ENCODING
55 | headers['accept-encoding'] = accept_encoding
56 |
57 | if user_agent:
58 | headers['user-agent'] = user_agent
59 |
60 | if keep_alive:
61 | headers['connection'] = 'keep-alive'
62 |
63 | if basic_auth:
64 | headers['authorization'] = 'Basic ' + \
65 | b64encode(b(basic_auth)).decode('utf-8')
66 |
67 | if proxy_basic_auth:
68 | headers['proxy-authorization'] = 'Basic ' + \
69 | b64encode(b(proxy_basic_auth)).decode('utf-8')
70 |
71 | if disable_cache:
72 | headers['cache-control'] = 'no-cache'
73 |
74 | return headers
75 |
76 |
77 | def set_file_position(body, pos):
78 | """
79 | If a position is provided, move file to that point.
80 | Otherwise, we'll attempt to record a position for future use.
81 | """
82 | if pos is not None:
83 | rewind_body(body, pos)
84 | elif getattr(body, 'tell', None) is not None:
85 | try:
86 | pos = body.tell()
87 | except (IOError, OSError):
88 | # This differentiates from None, allowing us to catch
89 | # a failed `tell()` later when trying to rewind the body.
90 | pos = _FAILEDTELL
91 |
92 | return pos
93 |
94 |
95 | def rewind_body(body, body_pos):
96 | """
97 | Attempt to rewind body to a certain position.
98 | Primarily used for request redirects and retries.
99 |
100 | :param body:
101 | File-like object that supports seek.
102 |
103 | :param int pos:
104 | Position to seek to in file.
105 | """
106 | body_seek = getattr(body, 'seek', None)
107 | if body_seek is not None and isinstance(body_pos, integer_types):
108 | try:
109 | body_seek(body_pos)
110 | except (IOError, OSError):
111 | raise UnrewindableBodyError("An error occurred when rewinding request "
112 | "body for redirect/retry.")
113 | elif body_pos is _FAILEDTELL:
114 | raise UnrewindableBodyError("Unable to record file position for rewinding "
115 | "request body during a redirect/retry.")
116 | else:
117 | raise ValueError("body_pos must be of type integer, "
118 | "instead it was %s." % type(body_pos))
119 |
--------------------------------------------------------------------------------
/docker/urllib3/util/request.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/request.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/response.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from ..packages.six.moves import http_client as httplib
3 |
4 | from ..exceptions import HeaderParsingError
5 |
6 |
7 | def is_fp_closed(obj):
8 | """
9 | Checks whether a given file-like object is closed.
10 |
11 | :param obj:
12 | The file-like object to check.
13 | """
14 |
15 | try:
16 | # Check `isclosed()` first, in case Python3 doesn't set `closed`.
17 | # GH Issue #928
18 | return obj.isclosed()
19 | except AttributeError:
20 | pass
21 |
22 | try:
23 | # Check via the official file-like-object way.
24 | return obj.closed
25 | except AttributeError:
26 | pass
27 |
28 | try:
29 | # Check if the object is a container for another file-like object that
30 | # gets released on exhaustion (e.g. HTTPResponse).
31 | return obj.fp is None
32 | except AttributeError:
33 | pass
34 |
35 | raise ValueError("Unable to determine whether fp is closed.")
36 |
37 |
38 | def assert_header_parsing(headers):
39 | """
40 | Asserts whether all headers have been successfully parsed.
41 | Extracts encountered errors from the result of parsing headers.
42 |
43 | Only works on Python 3.
44 |
45 | :param headers: Headers to verify.
46 | :type headers: `httplib.HTTPMessage`.
47 |
48 | :raises urllib3.exceptions.HeaderParsingError:
49 | If parsing errors are found.
50 | """
51 |
52 | # This will fail silently if we pass in the wrong kind of parameter.
53 | # To make debugging easier add an explicit check.
54 | if not isinstance(headers, httplib.HTTPMessage):
55 | raise TypeError('expected httplib.Message, got {0}.'.format(
56 | type(headers)))
57 |
58 | defects = getattr(headers, 'defects', None)
59 | get_payload = getattr(headers, 'get_payload', None)
60 |
61 | unparsed_data = None
62 | if get_payload: # Platform-specific: Python 3.
63 | unparsed_data = get_payload()
64 |
65 | if defects or unparsed_data:
66 | raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
67 |
68 |
69 | def is_response_to_head(response):
70 | """
71 | Checks whether the request of a response has been a HEAD-request.
72 | Handles the quirks of AppEngine.
73 |
74 | :param conn:
75 | :type conn: :class:`httplib.HTTPResponse`
76 | """
77 | # FIXME: Can we do this somehow without accessing private httplib _method?
78 | method = response._method
79 | if isinstance(method, int): # Platform-specific: Appengine
80 | return method == 3
81 | return method.upper() == 'HEAD'
82 |
--------------------------------------------------------------------------------
/docker/urllib3/util/response.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/response.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/retry.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/retry.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/selectors.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/selectors.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/ssl_.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/ssl_.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/timeout.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/timeout.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/url.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/url.pyc
--------------------------------------------------------------------------------
/docker/urllib3/util/wait.py:
--------------------------------------------------------------------------------
1 | from .selectors import (
2 | HAS_SELECT,
3 | DefaultSelector,
4 | EVENT_READ,
5 | EVENT_WRITE
6 | )
7 |
8 |
9 | def _wait_for_io_events(socks, events, timeout=None):
10 | """ Waits for IO events to be available from a list of sockets
11 | or optionally a single socket if passed in. Returns a list of
12 | sockets that can be interacted with immediately. """
13 | if not HAS_SELECT:
14 | raise ValueError('Platform does not have a selector')
15 | if not isinstance(socks, list):
16 | # Probably just a single socket.
17 | if hasattr(socks, "fileno"):
18 | socks = [socks]
19 | # Otherwise it might be a non-list iterable.
20 | else:
21 | socks = list(socks)
22 | with DefaultSelector() as selector:
23 | for sock in socks:
24 | selector.register(sock, events)
25 | return [key[0].fileobj for key in
26 | selector.select(timeout) if key[1] & events]
27 |
28 |
29 | def wait_for_read(socks, timeout=None):
30 | """ Waits for reading to be available from a list of sockets
31 | or optionally a single socket if passed in. Returns a list of
32 | sockets that can be read from immediately. """
33 | return _wait_for_io_events(socks, EVENT_READ, timeout)
34 |
35 |
36 | def wait_for_write(socks, timeout=None):
37 | """ Waits for writing to be available from a list of sockets
38 | or optionally a single socket if passed in. Returns a list of
39 | sockets that can be written to immediately. """
40 | return _wait_for_io_events(socks, EVENT_WRITE, timeout)
41 |
--------------------------------------------------------------------------------
/docker/urllib3/util/wait.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samwiseg0/better-discord-notifications/70c4df2a3848210995b2c5d5dd54f87986fb4a02/docker/urllib3/util/wait.pyc
--------------------------------------------------------------------------------
/radarr_discord.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import logging
4 | import sys
5 | import json
6 | import re
7 | import requests
8 | import script_config
9 | from datetime import datetime
10 |
11 | discord_headers = {'content-type': 'application/json'}
12 |
13 | # Set up the log file
14 | log_filename = os.path.join(os.path.dirname(sys.argv[0]), 'radarr_notification.log')
15 | logging.basicConfig(
16 | filename=log_filename,
17 | level=logging.INFO,
18 | format='[%(asctime)s] %(levelname)s - %(message)s'
19 | )
20 | log = logging.getLogger('Radarr')
21 |
22 | def get_profile_name(profile_id):
23 | try:
24 | get_profiles = requests.get('{}api/profile/{}?apikey={}'.format(script_config.radarr_url, profile_id, script_config.radarr_key)).json()
25 | except Exception as ex:
26 | log.error('Could not pull profile from radarr! ({})'.format(ex))
27 |
28 | try:
29 | profile_name = get_profiles['name']
30 |
31 | except Exception as ex:
32 | log.error('Could not find profile ID in radarr! ({})'.format(ex))
33 | profile_name = 'Unknown'
34 |
35 | return profile_name
36 |
37 | def get_imdb_rating(imdb_id):
38 | get_imdb = requests.get('https://imdb-api.com/API/Ratings/{}/{}'.format(script_config.radarr_imdbapi_key, imdb_id)).json()
39 | imdb_rating = get_imdb['imDb']
40 | if not imdb_rating:
41 | imdb_rating = "Unknown"
42 | return imdb_rating
43 |
44 | def utc_now_iso():
45 | utcnow = datetime.utcnow()
46 | return utcnow.isoformat()
47 |
48 | # Get Event Type
49 | eventtype = os.environ.get('radarr_eventtype')
50 |
51 | if eventtype == 'test':
52 | TEST_MODE = True
53 | else:
54 | TEST_MODE = False
55 |
56 | #Get ENV variables
57 | movie_id = os.environ.get('radarr_movie_id')
58 | if not movie_id:
59 | movie_id = 10
60 |
61 | media_title = os.environ.get('radarr_movie_title')
62 | if not media_title:
63 | media_title = 'The Lego Movie'
64 |
65 | imdb_id = os.environ.get('radarr_movie_imdbid')
66 | if not imdb_id:
67 | imdb_id = 'tt1490017'
68 |
69 | quality = os.environ.get('radarr_moviefile_quality')
70 | if not quality:
71 | quality = 'Bluray-2160p'
72 |
73 | scene_name = os.environ.get('radarr_moviefile_scenename')
74 |
75 | imdb_rating = get_imdb_rating(imdb_id)
76 |
77 | imdb_url = 'https://www.imdb.com/title/' + imdb_id
78 |
79 | #Get Radarr data
80 | radarr_api_url = '{}api/movie/{}?apikey={}'.format(script_config.radarr_url, movie_id, script_config.radarr_key)
81 |
82 | radarr = requests.get(radarr_api_url)
83 |
84 | radarr_data = radarr.json()
85 |
86 | if not TEST_MODE:
87 | year = radarr_data['year']
88 |
89 | if TEST_MODE:
90 | scene_name = 'A.Movie.2020.TrueHD.Atmos.AC3.MULTISUBS.UHD.4k.BluRay.x264.HQ-TUSAHD'
91 | year = '2014'
92 |
93 | #Get Trailer Link from Radarr
94 | try:
95 | trailer_link = 'https://www.youtube.com/watch?v={}'.format(radarr_data['youTubeTrailerId'])
96 | except:
97 | trailer_link = 'None'
98 |
99 | title_slug = re.sub(r'[?|$|.|!|:|/]', r'', media_title).replace(' ', '-')
100 |
101 | #Get data from TMDB
102 | moviedb_api_url = 'https://api.themoviedb.org/3/find/{}?api_key={}&external_source=imdb_id'.format(imdb_id, script_config.moviedb_key)
103 |
104 | moviedb_api = requests.get(moviedb_api_url)
105 |
106 | moviedb_api_data = moviedb_api.json()
107 |
108 | radarr_id = moviedb_api_data['movie_results'][0]['id']
109 |
110 | try:
111 | overview = moviedb_api_data['movie_results'][0]['overview']
112 | except:
113 | overview = 'None'
114 |
115 | try:
116 | physical_release = radarr_data['physicalRelease']
117 | physical_release= datetime.strptime(physical_release,"%Y-%m-%dT%H:%M:%SZ").strftime("%B %d, %Y")
118 | except:
119 | physical_release = 'None'
120 |
121 | try:
122 | release = moviedb_api_data['movie_results'][0]['release_date']
123 | release = datetime.strptime(release, "%Y-%m-%d").strftime("%B %d, %Y")
124 | except:
125 | release = 'None'
126 |
127 | try:
128 | genres = json.dumps(radarr_data['genres'])
129 | genres = re.sub(r'[?|$|.|!|:|/|\]|\[|\"]', r'', genres)
130 | except:
131 | genres = 'None'
132 |
133 | quality_profile = get_profile_name(radarr_data['qualityProfileId'])
134 |
135 | #Get Poster from TMDB
136 | poster_path = moviedb_api_data['movie_results'][0]['poster_path']
137 |
138 | try:
139 | poster_path = 'https://image.tmdb.org/t/p/w500' + poster_path
140 | except TypeError:
141 | #Send a generic poster if there is not one for this movie
142 | poster_path = 'https://i.imgur.com/GoqfZJe.jpg'
143 |
144 | # Format the message
145 | message = {
146 | 'username': script_config.radarr_discord_user,
147 | 'content': 'New movie downloaded - {} ({}) IMDb: {}'.format(media_title, year, imdb_rating),
148 | 'embeds': [
149 | {
150 | 'author': {
151 | 'name': 'Movies',
152 | 'url': script_config.radarr_url,
153 | 'icon_url': script_config.radarr_icon
154 | },
155 | 'title': '{} ({})'.format(media_title, year),
156 | 'color': 3394662,
157 | 'url': '{}movie/{}-{}'.format(script_config.radarr_url, title_slug.lower(), radarr_id),
158 | 'image': {
159 | 'url': poster_path
160 | },
161 | 'fields': [
162 |
163 | ]
164 | },
165 | {
166 | 'title': 'Overview',
167 | 'color': 3381708,
168 | 'description': overview,
169 | "fields": [
170 | {
171 | "name": 'Quality',
172 | "value": quality,
173 | "inline": True
174 | },
175 | {
176 | "name": 'Quality Profile',
177 | "value": quality_profile,
178 | "inline": True
179 | },
180 | {
181 | "name": 'Release Date',
182 | "value": release,
183 | "inline": True
184 | },
185 | {
186 | "name": 'Physical Release Date',
187 | "value": physical_release,
188 | "inline": True
189 | },
190 | {
191 | "name": 'IMDb Rating',
192 | "value": "[{}]({})".format(imdb_rating, imdb_url),
193 | "inline": True
194 | },
195 | {
196 | "name": 'Genres',
197 | "value": genres,
198 | "inline": True
199 | },
200 | {
201 | "inline": False,
202 | "name": "Trailer",
203 | "value": trailer_link
204 | }
205 | ],
206 | 'footer': {
207 | 'text': '{}'.format(scene_name)
208 | },
209 | 'timestamp': utc_now_iso()
210 |
211 | },
212 |
213 | ]
214 | }
215 |
216 | # Log json
217 | log.info(json.dumps(message, sort_keys=True, indent=4, separators=(',', ': ')))
218 |
219 | # Send notification
220 | sender = requests.post(script_config.radarr_discord_url, headers=discord_headers, json=message)
221 |
222 | # Log response from discord
223 | log.info(sender.content)
224 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | #---------------------------------------------------------
2 | # Potential requirements.
3 | # pip install -r requirements.txt
4 | #---------------------------------------------------------
5 | requests
6 |
--------------------------------------------------------------------------------
/script_config.example.py:
--------------------------------------------------------------------------------
1 | ######## SET YOUR CONFIGURATION ITEMS HERE ########
2 |
3 | ######## Sonarr Configuration ########
4 | sonarr_discord_user = 'User'
5 |
6 | sonarr_discord_url = 'https://discordapp.com/api/webhooks/XXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXX'
7 |
8 | sonarr_url = 'https://tv.domain.ltd/' # Make sure you include the trailing /
9 |
10 | sonarr_icon = 'https://github.com/Sonarr/Sonarr/raw/develop/Logo/256.png'
11 |
12 | skyhook_url = 'https://skyhook.sonarr.tv/v1/tvdb/shows/en/' #should not need to change this
13 |
14 | ######## Radarr Configuration ########
15 | radarr_discord_user = 'User'
16 |
17 | radarr_discord_url = 'https://discordapp.com/api/webhooks/XXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXX'
18 |
19 | radarr_url = 'https://movies.domain.ltd/' # Make sure you include the trailing /
20 |
21 | radarr_key = 'XXXXXXXXXXXXXXXX'
22 |
23 | moviedb_key = 'XXXXXXXXXXXXXXXX'
24 |
25 | radarr_icon = 'https://github.com/Radarr/Radarr/raw/aphrodite/Logo/128.png'
26 |
27 | # Register for an API here https://imdb-api.com/Identity/Account/Register
28 | radarr_imdbapi_key = 'XXXXXXXXXXXX'
29 |
30 | ##################################################
31 |
--------------------------------------------------------------------------------
/sonarr_discord.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import logging
4 | import sys
5 | import json
6 | import re
7 | import datetime
8 | import requests
9 | import script_config
10 |
11 | discord_headers = {'content-type': 'application/json'}
12 |
13 | # Set up the log file
14 | log_filename = os.path.join(os.path.dirname(sys.argv[0]), 'sonarr_notification.log')
15 | logging.basicConfig(
16 | filename=log_filename,
17 | level=logging.INFO,
18 | format='[%(asctime)s] %(levelname)s - %(message)s')
19 | log = logging.getLogger('Sonarr')
20 |
21 | def utc_now_iso():
22 | utcnow = datetime.datetime.utcnow()
23 | return utcnow.isoformat()
24 |
25 | def convert_string_to_int(list):
26 | int_list = [int(x) for x in list.split(',') if x.strip().isdigit()]
27 | return int_list
28 |
29 | def main():
30 | # Get/set ENV variables
31 | eventtype = os.environ.get('sonarr_eventtype')
32 |
33 | season = os.environ.get('sonarr_episodefile_seasonnumber')
34 |
35 | episode = os.environ.get('sonarr_episodefile_episodenumbers')
36 |
37 | tvdb_id = os.environ.get('sonarr_series_tvdbid')
38 |
39 | scene_name = os.environ.get('sonarr_episodefile_scenename')
40 |
41 | media_title = os.environ.get('sonarr_series_title')
42 |
43 | episode_title = os.environ.get('sonarr_episodefile_episodetitles')
44 |
45 | quality = os.environ.get('sonarr_episodefile_quality')
46 |
47 | is_upgrade = os.environ.get('sonarr_isupgrade')
48 |
49 | if eventtype == 'Test':
50 | log.info('Sonarr script test succeeded.')
51 | sys.exit(0)
52 |
53 | # Get show information from skyhook
54 | get_skyhook = requests.get(script_config.skyhook_url + str(tvdb_id))
55 |
56 | skyhook_data = get_skyhook.json()
57 |
58 | title_slug = skyhook_data['slug']
59 |
60 | # Get banner image for show
61 | try:
62 | banner = skyhook_data['seasons'][int(season)]['images'][1]['url']
63 | except Exception as ex:
64 | log.error('Season banner not found! Failing back to series banner... ({})'.format(ex))
65 | banner = skyhook_data['images'][1]['url']
66 |
67 | for line in skyhook_data['episodes']:
68 | try:
69 | if int(line['seasonNumber']) == int(season) and \
70 | int(line['episodeNumber']) == convert_string_to_int(episode)[0]:
71 |
72 | if line['overview']:
73 | overview = line['overview']
74 | else:
75 | overview = skyhook_data['overview']
76 |
77 | except Exception as ex:
78 | log.error('Failed to get episode from skyhook! Failing back to series overview... ({})'.format(ex))
79 | overview = skyhook_data['overview']
80 |
81 | if len(str(season)) == 1:
82 | season = '0{}'.format(season)
83 |
84 | if len(str(episode)) == 1:
85 | episode = '0{}'.format(episode)
86 |
87 | if is_upgrade == 'True':
88 | content = 'Upgraded Episode - {}: {}'.format(media_title, episode_title)
89 | is_upgrade = 'Yes!'
90 |
91 | else:
92 | content = 'New episode downloaded - {}: {}'.format(media_title, episode_title)
93 | is_upgrade = 'Nope'
94 |
95 | try:
96 | content_rating = skyhook_data['contentRating']
97 | except:
98 | content_rating = 'Unset'
99 |
100 | try:
101 | network = skyhook_data['network']
102 | except:
103 | network = 'Unset'
104 |
105 | try:
106 | genres = json.dumps(skyhook_data['genres'])
107 | genres = re.sub(r'[?|$|.|!|:|/|\]|\[|\"]', r'', genres)
108 | except:
109 | genres = 'Unset'
110 |
111 | message = {
112 | "username": script_config.sonarr_discord_user,
113 | "content": content,
114 | "embeds": [
115 | {
116 | "author": {
117 | "name": "TV",
118 | "url": script_config.sonarr_url,
119 | "icon_url": script_config.sonarr_icon
120 | },
121 | "title": "{}: {}".format(media_title, episode_title),
122 | "color": 3394662,
123 | "url": "{}series/{}".format(script_config.sonarr_url, title_slug),
124 | "image": {
125 | "url": banner
126 | },
127 | },
128 | {
129 | "title": "Overview",
130 | "color": 3381708,
131 | "description": overview,
132 | "fields": [
133 | {
134 | "name": "Episode",
135 | "value": "s{}e{}".format(season, episode),
136 | "inline": True
137 | },
138 | {
139 | "name": "Quality",
140 | "value": quality,
141 | "inline": True
142 | },
143 | {
144 | "name": "Upgrade?",
145 | "value": is_upgrade,
146 | "inline": True
147 | },
148 | {
149 | "name": "Content Rating",
150 | "value": content_rating,
151 | "inline": True
152 | },
153 | {
154 | "name": "Network",
155 | "value": network,
156 | "inline": True
157 | },
158 | {
159 | "name": "Genres",
160 | "value": genres,
161 | "inline": True
162 | }
163 | ],
164 | "footer": {
165 | "text": "{}".format(scene_name)
166 | },
167 | "timestamp": utc_now_iso()
168 | }
169 |
170 | ]
171 | }
172 |
173 | log.info(json.dumps(message, sort_keys=True, indent=4, separators=(',', ': ')))
174 |
175 | # Send notification
176 | sender = requests.post(script_config.sonarr_discord_url, headers=discord_headers, json=message)
177 |
178 | # Log response
179 | log.info(sender.content)
180 |
181 | # Call main
182 | main()
183 |
--------------------------------------------------------------------------------