myappnotify 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,37 @@
1
+ """myappnotify — DingTalk + Gotify markdown notifications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Iterable, Optional
7
+
8
+ from myappnotify.dingtalk import send_dingtalk
9
+ from myappnotify.gotify import send_gotify
10
+
11
+ __all__ = ["send_dingtalk", "send_gotify", "notify"]
12
+ __version__ = "0.1.0"
13
+
14
+ log = logging.getLogger("myappnotify")
15
+
16
+
17
+ def notify(
18
+ title: str,
19
+ text: str,
20
+ *,
21
+ gotify_text: Optional[str] = None,
22
+ channels: Iterable[str] = ("dingtalk", "gotify"),
23
+ ) -> dict:
24
+ """Send to configured channels. Missing credentials skip that channel.
25
+
26
+ If ``gotify_text`` is set, Gotify uses it while DingTalk still uses ``text``.
27
+ """
28
+ results = {}
29
+ for ch in channels:
30
+ if ch == "dingtalk":
31
+ results["dingtalk"] = send_dingtalk(title, text)
32
+ elif ch == "gotify":
33
+ results["gotify"] = send_gotify(title, gotify_text if gotify_text is not None else text)
34
+ else:
35
+ log.error("unknown notify channel: %s", ch)
36
+ results[ch] = False
37
+ return results
@@ -0,0 +1,61 @@
1
+ """DingTalk group robot markdown push."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import hmac
8
+ import json
9
+ import logging
10
+ import os
11
+ import time
12
+ import urllib.parse
13
+ import urllib.request
14
+ from typing import Optional
15
+
16
+ log = logging.getLogger("myappnotify.dingtalk")
17
+
18
+ DINGDING_WEBHOOK = "https://oapi.dingtalk.com/robot/send"
19
+
20
+
21
+ def send_dingtalk(
22
+ title: str,
23
+ text: str,
24
+ *,
25
+ token: Optional[str] = None,
26
+ secret: Optional[str] = None,
27
+ ) -> bool:
28
+ """Push markdown to DingTalk. Missing config or failure returns False, never raises."""
29
+ token = (token if token is not None else os.environ.get("DINGDING_WEB_HOOK_TOKEN", "")).strip()
30
+ secret = (secret if secret is not None else os.environ.get("DINGDING_BOT_SIGN", "")).strip()
31
+ if not token or not secret:
32
+ log.error("DingTalk not configured (DINGDING_WEB_HOOK_TOKEN/DINGDING_BOT_SIGN), skip")
33
+ return False
34
+
35
+ ts = str(round(time.time() * 1000))
36
+ string_to_sign = "{}\n{}".format(ts, secret)
37
+ hmac_code = hmac.new(
38
+ secret.encode("utf-8"), string_to_sign.encode("utf-8"), digestmod=hashlib.sha256
39
+ ).digest()
40
+ sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
41
+ url = "{}?access_token={}&timestamp={}&sign={}".format(
42
+ DINGDING_WEBHOOK, token, ts, sign
43
+ )
44
+ payload = json.dumps(
45
+ {"msgtype": "markdown", "markdown": {"title": title, "text": text}}
46
+ ).encode("utf-8")
47
+ try:
48
+ req = urllib.request.Request(
49
+ url,
50
+ data=payload,
51
+ headers={"Content-Type": "application/json", "User-Agent": "Mozilla/5.0"},
52
+ )
53
+ with urllib.request.urlopen(req, timeout=10) as resp:
54
+ result = json.loads(resp.read().decode("utf-8", "ignore"))
55
+ if result.get("errcode") == 0:
56
+ log.info("DingTalk push ok")
57
+ return True
58
+ log.error("DingTalk push failed: %s", result)
59
+ except Exception as e:
60
+ log.error("DingTalk push failed: %s", e)
61
+ return False
myappnotify/gotify.py ADDED
@@ -0,0 +1,57 @@
1
+ """Gotify application message push (markdown)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ import urllib.request
9
+ from typing import Optional
10
+
11
+ log = logging.getLogger("myappnotify.gotify")
12
+
13
+
14
+ def send_gotify(
15
+ title: str,
16
+ text: str,
17
+ *,
18
+ url: Optional[str] = None,
19
+ token: Optional[str] = None,
20
+ priority: int = 5,
21
+ ) -> bool:
22
+ """Push markdown to Gotify. Missing config or failure returns False, never raises."""
23
+ base = (url if url is not None else os.environ.get("GOTIFY_URL", "")).strip().rstrip("/")
24
+ token = (token if token is not None else os.environ.get("GOTIFY_TOKEN", "")).strip()
25
+ if not base or not token:
26
+ log.error("Gotify not configured (GOTIFY_URL/GOTIFY_TOKEN), skip")
27
+ return False
28
+
29
+ endpoint = base + "/message"
30
+ payload = json.dumps(
31
+ {
32
+ "title": title,
33
+ "message": text,
34
+ "priority": priority,
35
+ "extras": {"client::display": {"contentType": "text/markdown"}},
36
+ }
37
+ ).encode("utf-8")
38
+ try:
39
+ req = urllib.request.Request(
40
+ endpoint,
41
+ data=payload,
42
+ headers={
43
+ "Content-Type": "application/json",
44
+ "X-Gotify-Key": token,
45
+ "User-Agent": "Mozilla/5.0",
46
+ },
47
+ )
48
+ with urllib.request.urlopen(req, timeout=10) as resp:
49
+ body = resp.read().decode("utf-8", "ignore")
50
+ status = getattr(resp, "status", None) or resp.getcode()
51
+ if 200 <= int(status) < 300:
52
+ log.info("Gotify push ok")
53
+ return True
54
+ log.error("Gotify push failed: status=%s body=%s", status, body)
55
+ except Exception as e:
56
+ log.error("Gotify push failed: %s", e)
57
+ return False
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: myappnotify
3
+ Version: 0.1.0
4
+ Summary: DingTalk + Gotify markdown notification helpers
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # myappnotify
10
+
11
+ DingTalk robot + Gotify push helpers (stdlib only).
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install myappnotify
17
+ ```
18
+
19
+ ## Env
20
+
21
+ | Variable | Channel |
22
+ |----------|---------|
23
+ | `DINGDING_WEB_HOOK_TOKEN` / `DINGDING_BOT_SIGN` | DingTalk |
24
+ | `GOTIFY_URL` / `GOTIFY_TOKEN` | Gotify |
25
+
26
+ Missing config skips that channel and logs; never raises.
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from myappnotify import send_dingtalk, send_gotify, notify
32
+
33
+ send_dingtalk("Title", "## body")
34
+ send_gotify("Title", "## body") # markdown via client::display
35
+
36
+ # Fan-out; optional Gotify-specific body (e.g. GFM tables)
37
+ notify("选股结果", dingtalk_md, gotify_text=gotify_table_md)
38
+ ```
@@ -0,0 +1,7 @@
1
+ myappnotify/__init__.py,sha256=1U-b-9LVb6lwEQl5s8JNw2m_j2jx6xCeGY8RqVSo4H4,1065
2
+ myappnotify/dingtalk.py,sha256=Y3zTdj15zuOV_ASRDhF4DUqyY-7WxPTYsd2N7-QCNg4,2008
3
+ myappnotify/gotify.py,sha256=3oh-YSnfRukhmUXRMrDKAPZ1hl4M57QA2ZlzI32tg9M,1775
4
+ myappnotify-0.1.0.dist-info/METADATA,sha256=POEvV1i-2YkBRUYXc9kSrNVUASKArHK4U7IgNVOVSZc,883
5
+ myappnotify-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ myappnotify-0.1.0.dist-info/top_level.txt,sha256=GDPWXjdamjD_cBPlIKXPQOzflKJvN4TcDk5tY9BWQg0,12
7
+ myappnotify-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ myappnotify