xenoslib 0.1.29.14__tar.gz → 0.1.29.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {xenoslib-0.1.29.14/xenoslib.egg-info → xenoslib-0.1.29.16}/PKG-INFO +12 -2
  2. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/about.py +1 -1
  3. xenoslib-0.1.29.16/xenoslib/extend.py +368 -0
  4. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/mail.py +14 -3
  5. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16/xenoslib.egg-info}/PKG-INFO +12 -2
  6. xenoslib-0.1.29.14/xenoslib/extend.py +0 -196
  7. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/LICENSE +0 -0
  8. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/README.md +0 -0
  9. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/setup.cfg +0 -0
  10. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/setup.py +0 -0
  11. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/__init__.py +0 -0
  12. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/__main__.py +0 -0
  13. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/base.py +0 -0
  14. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/dev.py +0 -0
  15. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/linux.py +0 -0
  16. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/mock.py +0 -0
  17. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/onedrive.py +0 -0
  18. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/win_trayicon.py +0 -0
  19. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib/windows.py +0 -0
  20. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib.egg-info/SOURCES.txt +0 -0
  21. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib.egg-info/dependency_links.txt +0 -0
  22. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib.egg-info/requires.txt +0 -0
  23. {xenoslib-0.1.29.14 → xenoslib-0.1.29.16}/xenoslib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: xenoslib
3
- Version: 0.1.29.14
3
+ Version: 0.1.29.16
4
4
  Summary: Xenos' common lib
5
5
  Home-page: https://github.com/XenosLu/xenoslib.git
6
6
  Author: Xenocider
@@ -18,3 +18,13 @@ Provides-Extra: colorful
18
18
  Requires-Dist: colorama>=0.4.4; sys_platform == "win32" and extra == "colorful"
19
19
  Provides-Extra: mock
20
20
  Requires-Dist: requests_mock>=1.9.3; extra == "mock"
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: license
26
+ Dynamic: license-file
27
+ Dynamic: provides-extra
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
@@ -4,4 +4,4 @@ __url__ = "https://github.com/XenosLu/xenoslib.git"
4
4
  __author__ = "Xenocider"
5
5
  __author_email__ = "xenos.lu@gmail.com"
6
6
  __license__ = "MIT License"
7
- __version__ = "0.1.29.14"
7
+ __version__ = "0.1.29.16"
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ import os
4
+ import sys
5
+ import logging
6
+
7
+ import yaml
8
+ import requests
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class YamlConfig(dict):
15
+ """A thread unsafe yaml file config utility , can work as a dict except __init__"""
16
+
17
+ def __getattr__(self, key):
18
+ return self.get(key)
19
+
20
+ def __setattr__(self, name, value):
21
+ try:
22
+ getattr(super(), name)
23
+ except AttributeError as exc:
24
+ if str(exc).startswith("'super' object has no attribute "):
25
+ self[name] = value
26
+ return
27
+ raise exc
28
+ raise AttributeError(f"'{__class__.__name__}' object attribute '{name}' is read-only")
29
+
30
+ def __str__(self):
31
+ return yaml.safe_dump(self.copy(), allow_unicode=True)
32
+
33
+ def __repr__(self):
34
+ return repr(str(self))
35
+
36
+ def __init__(self, conf_path=None):
37
+ pass
38
+
39
+ def __new__(cls, conf_path="config.yml", *args, **kwargs):
40
+ if not hasattr(cls, "_instances"):
41
+ cls._instances = {}
42
+ if cls._instances.get(conf_path) is None:
43
+ cls._instances[conf_path] = super().__new__(cls)
44
+ super().__setattr__(cls._instances[conf_path], "_conf_path", conf_path)
45
+ cls._instances[conf_path]._load_conf()
46
+ return cls._instances[conf_path]
47
+
48
+ def _load_conf(self):
49
+ if os.path.exists(self._conf_path):
50
+ with open(self._conf_path, encoding="utf-8") as r:
51
+ self.update(yaml.safe_load(r))
52
+
53
+ def save(self):
54
+ data = str(self)
55
+ with open(self._conf_path, "w", encoding="utf-8") as w:
56
+ w.write(data)
57
+ # yaml.safe_dump(self.copy(), w, allow_unicode=True)
58
+
59
+
60
+ class RequestAdapter:
61
+ def request(self, method, path, *args, **kwargs):
62
+ """to-do: support stream=True"""
63
+ url = f"{self.base_url}/{path}"
64
+ logger.debug(url)
65
+ response = self.session.request(method, url, *args, **kwargs)
66
+ logger.debug(response.text)
67
+ response.raise_for_status()
68
+ try:
69
+ return response.json()
70
+ except Exception as exc:
71
+ logger.debug(exc)
72
+ return response
73
+
74
+ def get(self, path, *args, **kwargs):
75
+ return self.request("get", path, *args, **kwargs)
76
+
77
+ def post(self, path, *args, **kwargs):
78
+ return self.request("post", path, *args, **kwargs)
79
+
80
+ def put(self, path, *args, **kwargs):
81
+ return self.request("put", path, *args, **kwargs)
82
+
83
+ def delete(self, path, *args, **kwargs):
84
+ return self.request("delete", path, *args, **kwargs)
85
+
86
+ def patch(self, path, *args, **kwargs):
87
+ return self.request("patch", path, *args, **kwargs)
88
+
89
+ def head(self, path, *args, **kwargs):
90
+ return self.request("head", path, *args, **kwargs)
91
+
92
+ def __init__(self):
93
+ self.session = requests.Session()
94
+
95
+
96
+ def del_to_recyclebin(filepath, on_fail_delete=False):
97
+ """delete file to recyclebin if possible"""
98
+ if not sys.platform == "win32":
99
+ if on_fail_delete:
100
+ os.remove(filepath)
101
+ return True
102
+ return False
103
+ from win32com.shell import shell, shellcon
104
+
105
+ res, _ = shell.SHFileOperation(
106
+ (
107
+ 0,
108
+ shellcon.FO_DELETE,
109
+ filepath,
110
+ None,
111
+ shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION,
112
+ None,
113
+ None,
114
+ )
115
+ )
116
+ return res == 0
117
+
118
+
119
+ def send_notify(msg, key):
120
+ """send a message for ifttt"""
121
+ url = f"https://maker.ifttt.com/trigger/message/with/key/{key}"
122
+ data = {"value1": msg}
123
+ return requests.post(url, data=data, timeout=(30, 30))
124
+
125
+
126
+ class IFTTTLogHandler(logging.Handler):
127
+ """
128
+ log handler for IFTTT
129
+ usage:
130
+ key = 'xxxxx.xxxzx.xxxzx.xxxzx'
131
+ iftttloghandler = IFTTTLogHandler(key, level=logging.INFO)
132
+ logging.getLogger(__name__).addHandler(iftttloghandler)
133
+ """
134
+
135
+ def __init__(self, key, level=logging.CRITICAL, *args, **kwargs):
136
+ self.key = key
137
+ super().__init__(level=level, *args, **kwargs)
138
+
139
+ def emit(self, record):
140
+ try:
141
+ send_notify(self.format(record), self.key)
142
+ except Exception as exc:
143
+ print(exc)
144
+
145
+
146
+ class SlackLogHandler(logging.Handler):
147
+ """
148
+ log handler for Slack
149
+ usage:
150
+ slackloghandler = SlackLogHandler(webhook_url, level=logging.INFO)
151
+ logging.getLogger(__name__).addHandler(slackloghandler)
152
+ """
153
+
154
+ def __init__(self, webhook_url, level=logging.CRITICAL, *args, **kwargs):
155
+ self.url = webhook_url
156
+ self.headers = {"Content-type": "application/json"}
157
+ super().__init__(level=level, *args, **kwargs)
158
+
159
+ def emit(self, record):
160
+ try:
161
+ data = {"text": self.format(record)}
162
+ requests.post(self.url, headers=self.headers, json=data, timeout=(30, 30))
163
+ except Exception as exc:
164
+ print(exc)
165
+
166
+
167
+ class DingTalkLogHandler(logging.Handler):
168
+ """
169
+ log handler for DingTalk
170
+ usage:
171
+ token = 'xxxxx.xxxzx.xxxzx.xxxzx'
172
+ dingtalkloghandler = DingTalkLogHandler(token, level=logging.INFO)
173
+ logging.getLogger(__name__).addHandler(dingtalkloghandler)
174
+ """
175
+
176
+ def __init__(self, token, level=logging.CRITICAL, *args, **kwargs):
177
+ self.token = token
178
+ super().__init__(level=level, *args, **kwargs)
179
+
180
+ def emit(self, record):
181
+ headers = {"Content-Type": "application/json"}
182
+ url = "https://oapi.dingtalk.com/robot/send"
183
+ params = {"access_token": self.token}
184
+ msg = self.format(record)
185
+ data = {"msgtype": "text", "text": {"content": msg}}
186
+ try:
187
+ response = requests.post(
188
+ url, headers=headers, params=params, json=data, timeout=(10, 10)
189
+ )
190
+ print(response.json())
191
+ except Exception as exc:
192
+ print(exc)
193
+
194
+
195
+ class ConfigLoader:
196
+ """Centralized configuration management with optional Vault integration.
197
+
198
+ Args:
199
+ config_file_path (str): Path to the YAML configuration file. Defaults to "config.yml".
200
+ vault_secret_id (str, optional): Secret ID for Vault authentication.
201
+ If provided, enables Vault functionality and imports hvac module.
202
+
203
+ Attributes:
204
+ cache (dict): Cache storage for frequently accessed configuration values.
205
+
206
+ Example:
207
+ # Without Vault (hvac not imported)
208
+ >>> config = ConfigLoader("config.yml")
209
+
210
+ # With Vault (hvac imported on demand)
211
+ >>> config = ConfigLoader("config.yml", vault_secret_id="my-secret-id")
212
+ """
213
+
214
+ def __init__(self, config_file_path="config.yml", vault_secret_id=None):
215
+ """Initialize the ConfigLoader with a configuration file and optional Vault secret."""
216
+ with open(config_file_path, "r") as f:
217
+ self._raw_config = yaml.safe_load(f) or {}
218
+
219
+ self.cache = {}
220
+ self.vault_client = None
221
+
222
+ if vault_secret_id is not None:
223
+ self._init_vault_client(vault_secret_id)
224
+
225
+ def _init_vault_client(self, vault_secret_id):
226
+ """Initialize and authenticate the Vault client (imports hvac on demand).
227
+
228
+ Args:
229
+ vault_secret_id (str): Secret ID for Vault authentication.
230
+
231
+ Raises:
232
+ ImportError: If hvac package is not installed.
233
+ KeyError: If required Vault configuration is missing.
234
+ Exception: If Vault authentication fails.
235
+ """
236
+ try:
237
+ import hvac # Lazy import
238
+ except ImportError as e:
239
+ raise ImportError(
240
+ "hvac package is required for Vault integration. " "Install with: pip install hvac"
241
+ ) from e
242
+
243
+ try:
244
+ vault_config = self._raw_config.get("vault", {})
245
+ vault_url = vault_config.get("url")
246
+ vault_space = vault_config.get("space")
247
+ vault_role_id = vault_config.get("role_id")
248
+
249
+ if not all([vault_url, vault_space, vault_role_id]):
250
+ raise KeyError("Missing required Vault configuration in config.yml")
251
+
252
+ self.vault_client = hvac.Client(url=vault_url, namespace=vault_space)
253
+ self.vault_client.auth.approle.login(role_id=vault_role_id, secret_id=vault_secret_id)
254
+ except Exception as e:
255
+ self.vault_client = None
256
+ raise Exception(f"Failed to initialize Vault client: {str(e)}")
257
+
258
+ def get(self, section, key_name, use_cache=True):
259
+ """Retrieve a configuration value.
260
+
261
+ Args:
262
+ section (str): The configuration section name.
263
+ key_name (str): The key name within the section.
264
+ use_cache (bool): Whether to use cached values. Defaults to True.
265
+
266
+ Returns:
267
+ The configuration value, which may come from:
268
+ - Direct configuration value
269
+ - Vault secret (if Vault is initialized)
270
+ - Cache (if enabled)
271
+
272
+ Raises:
273
+ KeyError: If the section or key is not found.
274
+ Exception: If Vault access is required but not available.
275
+ """
276
+ if section not in self._raw_config:
277
+ raise KeyError(f"Section '{section}' not found")
278
+
279
+ # Check for direct value first
280
+ if key_name in self._raw_config[section]:
281
+ return self._raw_config[section][key_name]
282
+
283
+ # Handle Vault reference if Vault is enabled
284
+ vault_key = f"{key_name}@vault"
285
+ if vault_key in self._raw_config[section]:
286
+ if self.vault_client is None:
287
+ raise Exception(
288
+ f"Vault access required for {key_name} but Vault is not initialized"
289
+ )
290
+
291
+ cache_key = f"{section}_{key_name}"
292
+ if use_cache and cache_key in self.cache:
293
+ return self.cache[cache_key]
294
+
295
+ value = self._get_value_from_vault(section, key_name)
296
+ self.cache[cache_key] = value
297
+ return value
298
+
299
+ raise KeyError(f"Key '{key_name}' or '{vault_key}' not found in section '{section}'")
300
+
301
+ def _get_value_from_vault(self, section, key_name):
302
+ """Retrieve a secret value from Vault.
303
+
304
+ Args:
305
+ section (str): The configuration section name.
306
+ key_name (str): The key name within the section.
307
+
308
+ Returns:
309
+ The secret value from Vault.
310
+
311
+ Raises:
312
+ Exception: If Vault access fails.
313
+ """
314
+ try:
315
+ vault_path = self._raw_config[section]["vault_path"]
316
+ vault_key = self._raw_config[section][f"{key_name}@vault"]
317
+ data = self.vault_client.secrets.kv.read_secret_version(
318
+ path=vault_path, mount_point="kv", raise_on_deleted_version=True
319
+ )
320
+ return data["data"]["data"][vault_key]
321
+ except Exception as e:
322
+ raise Exception(f"Failed to fetch {key_name} from Vault: {str(e)}")
323
+
324
+ def __getitem__(self, section):
325
+ """Dictionary-style access to configuration sections."""
326
+ if section not in self._raw_config:
327
+ raise KeyError(f"Section '{section}' not found")
328
+ return SectionProxy(self, section)
329
+
330
+ def __getattr__(self, section):
331
+ """Attribute-style access to configuration sections."""
332
+ try:
333
+ return self[section]
334
+ except KeyError as e:
335
+ raise AttributeError(str(e))
336
+
337
+
338
+ class SectionProxy:
339
+ """Proxy class for configuration section access."""
340
+
341
+ def __init__(self, config_loader, section):
342
+ self._loader = config_loader
343
+ self._section = section
344
+
345
+ def __getitem__(self, key):
346
+ """Dictionary-style access to configuration values."""
347
+ return self._loader.get(self._section, key)
348
+
349
+ def __getattr__(self, key):
350
+ """Attribute-style access to configuration values."""
351
+ try:
352
+ return self[key]
353
+ except KeyError as e:
354
+ raise AttributeError(str(e))
355
+
356
+ def __repr__(self):
357
+ """String representation of the section's configuration."""
358
+ return yaml.dump(self._loader._raw_config[self._section])
359
+
360
+
361
+ if __name__ == "__main__":
362
+ config_without_vault = ConfigLoader("config.yml")
363
+ print("Without Vault:", config_without_vault.get("jira", "url"))
364
+
365
+ # This will only work if you provide a valid Vault secret ID
366
+ # and hvac package is installed
367
+ config_with_vault = ConfigLoader("config.yml", vault_secret_id="your-secret-id")
368
+ print("With Vault:", config_with_vault.get("cis", "cis_client_id"))
@@ -40,13 +40,22 @@ class MailFetcher:
40
40
  """
41
41
 
42
42
  def __new__(
43
- cls, imap_server, mail_addr, mail_pwd, interval=30, days=1, skip_current=True, endless=True
43
+ cls,
44
+ imap_server,
45
+ mail_addr,
46
+ mail_pwd,
47
+ interval=30,
48
+ days=1,
49
+ timeout=30,
50
+ skip_current=True,
51
+ endless=True,
44
52
  ):
45
53
  self = super().__new__(cls)
46
54
  self.imap_server = imap_server
47
55
  self.mail_addr = mail_addr
48
56
  self.mail_pwd = mail_pwd
49
57
  self.days = days
58
+ self.timeout = timeout
50
59
 
51
60
  self.msg_ids = deque(maxlen=999)
52
61
  if not endless:
@@ -95,7 +104,7 @@ class MailFetcher:
95
104
  logger.debug(f"Fetching emails since {from_date:%Y-%m-%d %H:%M:%S} ({self.days} days ago)")
96
105
  for i in range(5):
97
106
  try:
98
- with IMAPClient(self.imap_server, timeout=30) as client:
107
+ with IMAPClient(self.imap_server, timeout=self.timeout) as client:
99
108
  client.login(self.mail_addr, self.mail_pwd)
100
109
  client.select_folder("INBOX", readonly=True)
101
110
  messages = client.search(["SINCE", from_date])
@@ -104,7 +113,9 @@ class MailFetcher:
104
113
  except Exception as exc:
105
114
  logger.warning(exc)
106
115
  sleep(30)
107
- raise Exception("Reached maximum retry attempts. Giving up connection.")
116
+ raise Exception(
117
+ "Reached maximum retry attempts when connect IMAP server. Giving up connection."
118
+ )
108
119
 
109
120
 
110
121
  class SMTPMail:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: xenoslib
3
- Version: 0.1.29.14
3
+ Version: 0.1.29.16
4
4
  Summary: Xenos' common lib
5
5
  Home-page: https://github.com/XenosLu/xenoslib.git
6
6
  Author: Xenocider
@@ -18,3 +18,13 @@ Provides-Extra: colorful
18
18
  Requires-Dist: colorama>=0.4.4; sys_platform == "win32" and extra == "colorful"
19
19
  Provides-Extra: mock
20
20
  Requires-Dist: requests_mock>=1.9.3; extra == "mock"
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: license
26
+ Dynamic: license-file
27
+ Dynamic: provides-extra
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
@@ -1,196 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- import os
4
- import sys
5
- import logging
6
-
7
- import yaml
8
- import requests
9
-
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class YamlConfig(dict):
15
- """A thread unsafe yaml file config utility , can work as a dict except __init__"""
16
-
17
- def __getattr__(self, key):
18
- return self.get(key)
19
-
20
- def __setattr__(self, name, value):
21
- try:
22
- getattr(super(), name)
23
- except AttributeError as exc:
24
- if str(exc).startswith("'super' object has no attribute "):
25
- self[name] = value
26
- return
27
- raise exc
28
- raise AttributeError(f"'{__class__.__name__}' object attribute '{name}' is read-only")
29
-
30
- def __str__(self):
31
- return yaml.safe_dump(self.copy(), allow_unicode=True)
32
-
33
- def __repr__(self):
34
- return repr(str(self))
35
-
36
- def __init__(self, conf_path=None):
37
- pass
38
-
39
- def __new__(cls, conf_path="config.yml", *args, **kwargs):
40
- if not hasattr(cls, "_instances"):
41
- cls._instances = {}
42
- if cls._instances.get(conf_path) is None:
43
- cls._instances[conf_path] = super().__new__(cls)
44
- super().__setattr__(cls._instances[conf_path], "_conf_path", conf_path)
45
- cls._instances[conf_path]._load_conf()
46
- return cls._instances[conf_path]
47
-
48
- def _load_conf(self):
49
- if os.path.exists(self._conf_path):
50
- with open(self._conf_path, encoding="utf-8") as r:
51
- self.update(yaml.safe_load(r))
52
-
53
- def save(self):
54
- data = str(self)
55
- with open(self._conf_path, "w", encoding="utf-8") as w:
56
- w.write(data)
57
- # yaml.safe_dump(self.copy(), w, allow_unicode=True)
58
-
59
-
60
- class RequestAdapter:
61
- def request(self, method, path, *args, **kwargs):
62
- """to-do: support stream=True"""
63
- url = f"{self.base_url}/{path}"
64
- logger.debug(url)
65
- response = self.session.request(method, url, *args, **kwargs)
66
- logger.debug(response.text)
67
- response.raise_for_status()
68
- try:
69
- return response.json()
70
- except Exception as exc:
71
- logger.debug(exc)
72
- return response
73
-
74
- def get(self, path, *args, **kwargs):
75
- return self.request("get", path, *args, **kwargs)
76
-
77
- def post(self, path, *args, **kwargs):
78
- return self.request("post", path, *args, **kwargs)
79
-
80
- def put(self, path, *args, **kwargs):
81
- return self.request("put", path, *args, **kwargs)
82
-
83
- def delete(self, path, *args, **kwargs):
84
- return self.request("delete", path, *args, **kwargs)
85
-
86
- def patch(self, path, *args, **kwargs):
87
- return self.request("patch", path, *args, **kwargs)
88
-
89
- def head(self, path, *args, **kwargs):
90
- return self.request("head", path, *args, **kwargs)
91
-
92
- def __init__(self):
93
- self.session = requests.Session()
94
-
95
-
96
- def del_to_recyclebin(filepath, on_fail_delete=False):
97
- """delete file to recyclebin if possible"""
98
- if not sys.platform == "win32":
99
- if on_fail_delete:
100
- os.remove(filepath)
101
- return True
102
- return False
103
- from win32com.shell import shell, shellcon
104
-
105
- res, _ = shell.SHFileOperation(
106
- (
107
- 0,
108
- shellcon.FO_DELETE,
109
- filepath,
110
- None,
111
- shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION,
112
- None,
113
- None,
114
- )
115
- )
116
- return res == 0
117
-
118
-
119
- def send_notify(msg, key):
120
- """send a message for ifttt"""
121
- url = f"https://maker.ifttt.com/trigger/message/with/key/{key}"
122
- data = {"value1": msg}
123
- return requests.post(url, data=data, timeout=(30, 30))
124
-
125
-
126
- class IFTTTLogHandler(logging.Handler):
127
- """
128
- log handler for IFTTT
129
- usage:
130
- key = 'xxxxx.xxxzx.xxxzx.xxxzx'
131
- iftttloghandler = IFTTTLogHandler(key, level=logging.INFO)
132
- logging.getLogger(__name__).addHandler(iftttloghandler)
133
- """
134
-
135
- def __init__(self, key, level=logging.CRITICAL, *args, **kwargs):
136
- self.key = key
137
- super().__init__(level=level, *args, **kwargs)
138
-
139
- def emit(self, record):
140
- try:
141
- send_notify(self.format(record), self.key)
142
- except Exception as exc:
143
- print(exc)
144
-
145
-
146
- class SlackLogHandler(logging.Handler):
147
- """
148
- log handler for Slack
149
- usage:
150
- slackloghandler = SlackLogHandler(webhook_url, level=logging.INFO)
151
- logging.getLogger(__name__).addHandler(slackloghandler)
152
- """
153
-
154
- def __init__(self, webhook_url, level=logging.CRITICAL, *args, **kwargs):
155
- self.url = webhook_url
156
- self.headers = {"Content-type": "application/json"}
157
- super().__init__(level=level, *args, **kwargs)
158
-
159
- def emit(self, record):
160
- try:
161
- data = {"text": self.format(record)}
162
- requests.post(self.url, headers=self.headers, json=data, timeout=(30, 30))
163
- except Exception as exc:
164
- print(exc)
165
-
166
-
167
- class DingTalkLogHandler(logging.Handler):
168
- """
169
- log handler for DingTalk
170
- usage:
171
- token = 'xxxxx.xxxzx.xxxzx.xxxzx'
172
- dingtalkloghandler = DingTalkLogHandler(token, level=logging.INFO)
173
- logging.getLogger(__name__).addHandler(dingtalkloghandler)
174
- """
175
-
176
- def __init__(self, token, level=logging.CRITICAL, *args, **kwargs):
177
- self.token = token
178
- super().__init__(level=level, *args, **kwargs)
179
-
180
- def emit(self, record):
181
- headers = {"Content-Type": "application/json"}
182
- url = "https://oapi.dingtalk.com/robot/send"
183
- params = {"access_token": self.token}
184
- msg = self.format(record)
185
- data = {"msgtype": "text", "text": {"content": msg}}
186
- try:
187
- response = requests.post(
188
- url, headers=headers, params=params, json=data, timeout=(10, 10)
189
- )
190
- print(response.json())
191
- except Exception as exc:
192
- print(exc)
193
-
194
-
195
- if __name__ == "__main__":
196
- pass
File without changes
File without changes
File without changes
File without changes