feat: AgentCheckin v1.2.2 — fnOS 应用:TRAE/WorkBuddy/Qoder 三平台每日积分自动签到
- 原生 fnOS 应用(CGI 入口 + Python 守护进程),依赖 python312 运行时 - 三平台签到:TRAE 每日 200 积分、WorkBuddy 7 天循环、Qoder 每日 100 Credits - WorkBuddy 网页授权(OAuth 设备流)、TRAE icubeAuth 解密 Token、Qoder Cosy 设备头 - 多时段调度(默认 00:05/09:00/12:30/19:00)、启动补跑、幂等签到+回查确认 - 多平台 Webhook 通知(企业微信/钉钉/飞书/Bark/Server酱/PushPlus 自动识别) - Qoder 双站点 Token 隔离自动探测与修复 - 前端:仪表盘/账号管理/签到记录/设置四个页签,30s 轮询+变化检测+隐藏暂停 - 测试:34 后端 + 25 E2E + 9 前端回归 + 真实时钟调度测试
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate AgentCheckin app icons as PNG files (pure stdlib, no PIL).
|
||||
|
||||
Design: rounded gradient tile + calendar outline + check mark (daily check-in).
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
BASE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "AgentCheckin")
|
||||
|
||||
|
||||
class Canvas:
|
||||
def __init__(self, w, h, scale=4):
|
||||
self.w, self.h, self.s = w, h, scale
|
||||
self.W, self.H = w * scale, h * scale
|
||||
self.px = [(0, 0, 0, 0)] * (self.W * self.H)
|
||||
|
||||
def _set(self, x, y, rgba):
|
||||
if 0 <= x < self.W and 0 <= y < self.H:
|
||||
i = y * self.W + x
|
||||
r, g, b, a = rgba
|
||||
orr, og, ob, oa = self.px[i]
|
||||
na = a / 255.0 + oa / 255.0 * (1 - a / 255.0)
|
||||
if na == 0:
|
||||
self.px[i] = (0, 0, 0, 0)
|
||||
else:
|
||||
nr = (r * a / 255.0 + orr * oa / 255.0 * (1 - a / 255.0)) / na
|
||||
ng = (g * a / 255.0 + og * g / 255.0 * (1 - a / 255.0)) / na
|
||||
nb = (b * a / 255.0 + ob * oa / 255.0 * (1 - a / 255.0)) / na
|
||||
self.px[i] = (int(nr), int(ng), int(nb), int(na * 255))
|
||||
|
||||
def rect(self, x, y, w, h, rgba):
|
||||
for yy in range(int(y), int(y + h)):
|
||||
for xx in range(int(x), int(x + w)):
|
||||
self._set(xx, yy, rgba)
|
||||
|
||||
def rounded(self, x, y, w, h, rad, rgba):
|
||||
for yy in range(y, y + h):
|
||||
for xx in range(x, x + w):
|
||||
dx = max(x + rad - xx, xx - (x + w - 1 - rad), 0)
|
||||
dy = max(y + rad - yy, yy - (y + h - 1 - rad), 0)
|
||||
if dx * dx + dy * dy <= rad * rad:
|
||||
self._set(xx, yy, rgba)
|
||||
|
||||
def ring(self, x, y, w, h, rad, thick, rgba):
|
||||
for yy in range(y, y + h):
|
||||
for xx in range(x, x + w):
|
||||
def outside(px, py, shrink):
|
||||
ddx = max(x + rad + shrink - px, px - (x + w - 1 - rad - shrink), 0)
|
||||
ddy = max(y + rad + shrink - py, py - (y + h - 1 - rad - shrink), 0)
|
||||
return ddx * ddx + ddy * ddy > (rad + shrink) * (rad + shrink)
|
||||
if not outside(xx, yy, 0) and outside(xx, yy, thick):
|
||||
self._set(xx, yy, rgba)
|
||||
|
||||
def line(self, x1, y1, x2, y2, thick, rgba):
|
||||
steps = int(max(abs(x2 - x1), abs(y2 - y1)) * 2) + 1
|
||||
for i in range(steps + 1):
|
||||
t = i / steps
|
||||
cx = x1 + (x2 - x1) * t
|
||||
cy = y1 + (y2 - y1) * t
|
||||
r = thick / 2.0
|
||||
for yy in range(int(cy - r) - 1, int(cy + r) + 2):
|
||||
for xx in range(int(cx - r) - 1, int(cx + r) + 2):
|
||||
if (xx - cx) ** 2 + (yy - cy) ** 2 <= r * r:
|
||||
self._set(xx, yy, rgba)
|
||||
|
||||
def gradient_fill(self, x, y, w, h, rad, c1, c2):
|
||||
for yy in range(y, y + h):
|
||||
t = (yy - y) / max(h - 1, 1)
|
||||
r = int(c1[0] + (c2[0] - c1[0]) * t)
|
||||
g = int(c1[1] + (c2[1] - c1[1]) * t)
|
||||
b = int(c1[2] + (c2[2] - c1[2]) * t)
|
||||
for xx in range(x, x + w):
|
||||
dx = max(x + rad - xx, xx - (x + w - 1 - rad), 0)
|
||||
dy = max(y + rad - yy, yy - (y + h - 1 - rad), 0)
|
||||
if dx * dx + dy * dy <= rad * rad:
|
||||
self._set(xx, yy, (r, g, b, 255))
|
||||
|
||||
def save(self, path):
|
||||
s = self.s
|
||||
rows = []
|
||||
for y in range(self.h):
|
||||
row = bytearray([0])
|
||||
for x in range(self.w):
|
||||
rs = gs = bs = as_ = 0
|
||||
cnt = 0
|
||||
for dy in range(s):
|
||||
for dx in range(s):
|
||||
p = self.px[(y * s + dy) * self.W + (x * s + dx)]
|
||||
rs += p[0]; gs += p[1]; bs += p[2]; as_ += p[3]
|
||||
cnt += 1
|
||||
row += bytes((rs // cnt, gs // cnt, bs // cnt, as_ // cnt))
|
||||
rows.append(bytes(row))
|
||||
raw = b"".join(rows)
|
||||
|
||||
def chunk(tag, data):
|
||||
c = struct.pack(">I", len(data)) + tag + data
|
||||
return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
|
||||
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
png += chunk(b"IHDR", struct.pack(">IIBBBBB", self.w, self.h, 8, 6, 0, 0, 0))
|
||||
png += chunk(b"IDAT", zlib.compress(raw, 9))
|
||||
png += chunk(b"IEND", b"")
|
||||
with open(path, "wb") as f:
|
||||
f.write(png)
|
||||
|
||||
|
||||
def draw_icon(size):
|
||||
c = Canvas(size, size)
|
||||
S = c.s
|
||||
u = S
|
||||
m = int(size * 0.05) * S
|
||||
|
||||
# gradient tile
|
||||
c.gradient_fill(m, m, c.W - 2 * m, c.H - 2 * m, int(size * 0.22) * S,
|
||||
(59, 108, 255), (122, 90, 248))
|
||||
|
||||
# calendar body
|
||||
bx, by = int(size * 0.22) * S, int(size * 0.24) * S
|
||||
bw, bh = int(size * 0.56) * S, int(size * 0.52) * S
|
||||
rad = int(size * 0.09) * S
|
||||
thick = max(int(size * 0.05) * S, 2 * S)
|
||||
c.ring(bx, by, bw, bh, rad, thick, (255, 255, 255, 255))
|
||||
|
||||
# hangers
|
||||
hw, hh = int(size * 0.07) * S, int(size * 0.12) * S
|
||||
c.rounded(bx + bw // 2 - int(size * 0.16) * S, by - hh // 2, hw, hh, hw // 2, (255, 255, 255, 255))
|
||||
c.rounded(bx + bw // 2 + int(size * 0.09) * S, by - hh // 2, hw, hh, hw // 2, (255, 255, 255, 255))
|
||||
|
||||
# header line
|
||||
ly = by + int(size * 0.13) * S
|
||||
c.line(bx + thick // 2, ly, bx + bw - thick // 2, ly, thick, (255, 255, 255, 255))
|
||||
|
||||
# check mark
|
||||
cx1 = bx + int(size * 0.13) * S
|
||||
cy1 = by + int(size * 0.30) * S
|
||||
t2 = max(int(size * 0.065) * S, 2 * S)
|
||||
c.line(cx1, cy1, cx1 + int(size * 0.09) * S, cy1 + int(size * 0.09) * S, t2, (126, 240, 178, 255))
|
||||
c.line(cx1 + int(size * 0.09) * S, cy1 + int(size * 0.09) * S,
|
||||
cx1 + int(size * 0.26) * S, cy1 - int(size * 0.11) * S, t2, (126, 240, 178, 255))
|
||||
return c
|
||||
|
||||
|
||||
def main():
|
||||
paths = [
|
||||
(64, os.path.join(BASE, "ICON.PNG")),
|
||||
(256, os.path.join(BASE, "ICON_256.PNG")),
|
||||
(64, os.path.join(BASE, "app/ui/images/icon_64.png")),
|
||||
(256, os.path.join(BASE, "app/ui/images/icon_256.png")),
|
||||
]
|
||||
for size, path in paths:
|
||||
c = draw_icon(size)
|
||||
c.save(path)
|
||||
print("wrote", path, os.path.getsize(path), "bytes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user