├── .gitignore ├── README.md ├── _example ├── an.sh ├── cmd_console.py ├── magisk │ ├── META-INF │ │ └── com │ │ │ └── google │ │ │ └── android │ │ │ ├── update-binary │ │ │ └── updater-script │ ├── magisk.zip │ ├── module.prop │ ├── service.sh │ └── skip_mount ├── win.py ├── win.py.bat ├── win.py.bat.vbs └── win.py.bat.vbs - 快捷方式.lnk ├── data.py ├── example.jsonc ├── img ├── image1.png ├── image2.png └── server-1.png ├── install_lib.bat ├── install_lib.sh ├── jsonc_parser ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── __init__.py ├── errors.py ├── jsonc_parser │ └── README.md ├── parser.py └── setup.py ├── requirements.txt ├── server.py ├── start.py ├── static └── favicon.ico ├── templates ├── index.html └── style.css ├── utils.py └── 前台应用状态.macro /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | data.json 3 | data.*.json 4 | o.txt 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sleepy 2 | 3 | > What are you doing? 4 | 5 | 一个查看个人在线状态,以及视奸他在用什么软件的 Flask 网站,让他人能知道你不在而不是故意吊他/她 6 | 仅仅是在仓库中加入了App_name字段,手机客户端使用请搭配Macrodroid,来实现切换应用上报信息/同步睡眠信息 7 | Macrodroid配置文件请下载仓库中的"前台应用状态.macro" 8 | 9 | [**演示**](#preview) / [**部署**](#部署) / [**使用**](#使用) 10 | 11 | 12 | 13 | ## Preview 14 | 15 | 演示站: [Here](https://sleepy.1812z.top) 16 | 17 | 网页: 18 | 19 | ![web-1](img/image1.png) 20 | 21 | ![web-2](img/image2.png) 22 | 23 | 服务器: 24 | 25 | ![server-1](img/server-1.png) 26 | 27 | ## 部署 28 | 29 | 30 | 理论上全平台通用, 安装了 Python >= **3.6** 即可 31 | 32 | 1. Clone 本仓库 (建议先 Fork / Use this template) 33 | 34 | ```shell 35 | git clone https://github.com/1812z/sleepy.git 36 | # or ssh: 37 | # git clone git@github.com:1812z/sleepy.git 38 | ``` 39 | 40 | 2. 安装依赖 41 | 42 | ```shell 43 | cd sleepy 44 | ./install_lib.sh 45 | # or windows: 46 | # .\install_lib.bat 47 | # 也可自行安装: pip install -r requirements.txt 48 | # 其实只有 Flask (目前) 49 | ``` 50 | 51 | 3. 编辑配置文件 52 | 53 | 先启动一遍程序: 54 | 55 | ```shell 56 | python3 server.py 57 | ``` 58 | 59 | 如果不出意外,会提示: `data.json not exist, creating`,同时目录下出现 `data.json` 文件,编辑该文件中的配置并重新运行即可 (示例请 [查看 `example.jsonc`](./example.jsonc) ) 60 | 61 | ## 使用 62 | 63 | 有两种启动方式: 64 | 65 | - 直接启动 66 | 67 | ```shell 68 | python3 server.py 69 | ``` 70 | 71 | - 简易启动器 72 | 73 | ```shell 74 | python3 start.py 75 | ``` 76 | 77 | 相比直接启动, 启动器可实现在服务器退出 *(如开启 debug 后更改时自动保存导致有语法错误)* 后自动重启 78 | 79 |
80 | 点击展开 81 | 82 | ```shell 83 | Server path: /mnt/usb16/dev/wyf9/sleepy/server.py 84 | Starting server #1 85 | * Serving Flask app 'server' 86 | * Debug mode: on 87 | WARNING: This is a development server. Do not use it in a production deployment.rUse a production WSGI server instead. 88 | * Running on all addresses (0.0.0.0) 89 | * Running on http://127.0.0.1:9010 90 | * Running on http://192.168.1.20:9010 91 | Press CTRL+C to quit 92 | * Restarting with stat 93 | * Debugger is active! 94 | * Debugger PIN: 114-514-191 95 | ^C#1 exited with code 2 96 | waiting 5s 97 | Starting server #2 98 | * Serving Flask app 'server' 99 | * Debug mode: on 100 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. 101 | * Running on all addresses (0.0.0.0) 102 | * Running on http://127.0.0.1:9010 103 | * Running on http://192.168.1.20:9010 104 | Press CTRL+C to quit 105 | * Restarting with stat 106 | * Debugger is active! 107 | * Debugger PIN: 114-514-191 108 | ``` 109 | 110 |
111 | 112 | 113 | 默认服务 http 端口: `9010` 114 | 115 | | 路径 | 作用 | 116 | | -------------------------------------- | ------------------- | 117 | | `/` | 显示主页 | 118 | | `/query` | 获取状态 | 119 | | `/get/status_list` | 获取可用状态列表 | 120 | | `/set?secret=&status=` | 设置状态 (url 参数) | 121 | 122 | 123 | 1. `/query`: 124 | 125 | 获取当前的状态 (无需鉴权) 126 | 127 | 返回 json: 128 | 129 | ```jsonc 130 | { 131 | "success": true, // 请求是否成功 132 | "status": 0, // 获取到的状态码 133 | "info": { // 对应状态码的信息 134 | "name": "活着", // 状态名称 135 | "desc": "目前在线,可以通过任何可用的联系方式联系本人。", // 状态描述 136 | "color": "awake"// 状态颜色, 对应 static/style.css 中的 .sleeping .awake 等类 137 | } 138 | } 139 | ``` 140 | 141 | 2. `/get/status_list` 142 | 143 | 获取可用状态的列表 (无需鉴权) 144 | 145 | 返回 json: 146 | 147 | ```jsonc 148 | [ 149 | { 150 | "id": 0, // 索引,取决于配置文件中的有无 151 | "name": "活着", // 状态名称 152 | "desc": "目前在线,可以通过任何可用的联系方式联系本人。", // 状态描述 153 | "color": "awake" // 状态颜色, 对应 static/style.css 中的 .sleeping .awake 等类 154 | }, 155 | { 156 | "id": 1, 157 | "name": "似了", 158 | "desc": "睡似了或其他原因不在线,紧急情况请使用电话联系。", 159 | "color": "sleeping" 160 | }, 161 | // 以此类推 162 | ] 163 | ``` 164 | 165 | > 就是返回 `data.json` 中的 `status_list` 列表 166 | 167 | 3. `/set?secret=&status=` 168 | 169 | 设置当前状态 170 | 171 | - ``: 在 `data.json` 中配置的 `secret` 172 | - ``: 状态码 *(`int`)* 173 | 174 | 返回 json: 175 | 176 | ```jsonc 177 | // 1. 成功 178 | { 179 | "success": true, // 请求是否成功 180 | "code": "OK", // 返回代码 181 | "set_to": 0 // 设置到的状态码 182 | } 183 | 184 | // 2. 失败 - 密钥错误 185 | { 186 | "success": false, // 请求是否成功 187 | "code": "not authorized", // 返回代码 188 | "message": "invaild secret" // 详细信息 189 | } 190 | 191 | // 3. 失败 - 请求无效 192 | { 193 | "success": false, // 请求是否成功 194 | "code": "bad request", // 返回代码 195 | "message": "argument 'status' must be a number" // 详细信息 196 | } 197 | ``` 198 | 199 | 200 | 201 | ## 客户端示例 202 | 203 | 在 `_example/` 目录下, 可参考 204 | 205 | -------------------------------------------------------------------------------- /_example/an.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 目标URL 4 | URL="http://192.168.2.123:9010/set" 5 | SECRET="MySecret" 6 | 7 | while true; do 8 | # 获取当前应用的包名 9 | CURRENT_FOCUS=$(dumpsys window | grep mCurrentFocus) 10 | # mCurrentFocus=null 11 | # mCurrentFocus=Window{b061da6 u0 com.termux/com.termux.app.TermuxActivity} 12 | # 提取包名 13 | PACKAGE_NAME=$(echo "$CURRENT_FOCUS" | awk -F '[ /}]' '{print $5}') 14 | # 去除空格 15 | PACKAGE_NAME=$(echo "$PACKAGE_NAME" | tr -d '[:space:]') 16 | # 去除%0a 17 | # PACKAGE_NAME=$(echo "$PACKAGE_NAME" | tr -d '%0a') 18 | # 输出一下PACKAGE_NAME 19 | echo "$PACKAGE_NAME" 20 | # 若PACKAGE_NAME 为 NotificationShade ,则设置变量status为1否则为0 21 | if [ "$PACKAGE_NAME" = "NotificationShade" ]; then 22 | STATUS=1 23 | else 24 | STATUS=0 25 | fi 26 | # 如果包名不为空,则发送GET请求 27 | if [ ! -z "$PACKAGE_NAME" ]; then 28 | # 使用curl发送GET请求 29 | curl -G "$URL" --data-urlencode "secret=$SECRET" --data-urlencode "app_name=$PACKAGE_NAME" --data-urlencode "status=$STATUS" 30 | fi 31 | 32 | # 等待10秒 33 | sleep 10 34 | done -------------------------------------------------------------------------------- /_example/cmd_console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding:utf-8 3 | 4 | ''' 5 | 一个 python 命令行示例 6 | by @wyf9 7 | ''' 8 | 9 | import requests 10 | import json 11 | global server 12 | 13 | # 密钥 14 | SECRET = 'YourSecret' 15 | # 服务列表, 末尾不加 `/` 16 | SERVER_LIST = ['https://example.com', 17 | 'http://192.168.114.114', 18 | 'http://192.168.191.191:9810'] 19 | # 请求重试次数 20 | RETRY = 3 21 | 22 | 23 | def get(url): 24 | for t1 in range(RETRY): 25 | t = t1 + 1 26 | try: 27 | x = requests.get(url) 28 | return x.text 29 | except: 30 | print(f'Retrying... {t}/{RETRY}') 31 | if t >= RETRY: 32 | print('Max retry limit!') 33 | raise 34 | continue 35 | 36 | 37 | def loadjson(url): 38 | raw = get(url) 39 | try: 40 | return json.loads(raw) 41 | except json.decoder.JSONDecodeError: 42 | print('Error decoding json!\nRaw data:\n"""') 43 | print(raw) 44 | print('"""') 45 | raise 46 | except: 47 | print('Error:') 48 | raise 49 | 50 | 51 | def main(): 52 | print('\n---\nSelect Server:') 53 | serverlst_show = '' 54 | for n1 in range(len(SERVER_LIST)): 55 | n = n1 + 1 56 | serverlst_show += f' {n}. {SERVER_LIST[n1]}\n' 57 | print(f''' 58 | 0. Quit 59 | {serverlst_show}''') 60 | while True: 61 | try: 62 | inp = int(input('> ')) 63 | if inp == 0: 64 | return 0 65 | else: 66 | server = SERVER_LIST[inp - 1] 67 | print(f'Selected server: {server}') 68 | break 69 | except: 70 | print('invaild input') 71 | 72 | print('\n---\nStatus now:') 73 | stnow = loadjson(f'{server}/query') 74 | try: 75 | print(f'success: [{stnow["success"]}], status: [{stnow["status"]}], info_name: [{ 76 | stnow["info"]["name"]}], info_desc: [{stnow["info"]["desc"]}], info_color: [{stnow["info"]["color"]}]') 77 | except KeyError: 78 | print(f'RawData: {stnow}') 79 | 80 | print('\n---\nSelect status:') 81 | 82 | stlst = loadjson(f'{server}/get/status_list') 83 | for n in stlst: 84 | print(f'{n["id"]} - {n["name"]} - {n["desc"]}') 85 | 86 | st = input('\n> ') 87 | ''' 88 | print(get()) 89 | { 90 | "success": true, 91 | "code": "OK", 92 | "set_to": 0 93 | } 94 | ''' 95 | ret = loadjson(f'{server}/set/{SECRET}/{st}') 96 | try: 97 | print( 98 | f'success: [{ret["success"]}], code: [{ret["code"]}], set_to: [{ret["set_to"]}]') 99 | except: 100 | print(f'RawData: {ret}') 101 | input('\n---\nPress Enter to exit.') 102 | return 0 103 | 104 | 105 | if __name__ == "__main__": 106 | main() 107 | -------------------------------------------------------------------------------- /_example/magisk/META-INF/com/google/android/update-binary: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ################# 4 | # Initialization 5 | ################# 6 | 7 | umask 022 8 | 9 | # echo before loading util_functions 10 | ui_print() { echo "$1"; } 11 | 12 | require_new_magisk() { 13 | ui_print "*******************************" 14 | ui_print " Please install Magisk v20.4+! " 15 | ui_print "*******************************" 16 | exit 1 17 | } 18 | 19 | ######################### 20 | # Load util_functions.sh 21 | ######################### 22 | 23 | OUTFD=$2 24 | ZIPFILE=$3 25 | 26 | mount /data 2>/dev/null 27 | 28 | [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk 29 | . /data/adb/magisk/util_functions.sh 30 | [ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk 31 | 32 | install_module 33 | exit 0 -------------------------------------------------------------------------------- /_example/magisk/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK -------------------------------------------------------------------------------- /_example/magisk/magisk.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/_example/magisk/magisk.zip -------------------------------------------------------------------------------- /_example/magisk/module.prop: -------------------------------------------------------------------------------- 1 | id=HBWuChang-sleepy 2 | name=HBWuChangSleepy 3 | version=0.0.1 4 | versionCode=1 5 | author=HBWuChang 6 | description=Update status by adb and curl -------------------------------------------------------------------------------- /_example/magisk/service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 目标URL 4 | URL="http://192.168.2.123:9010/set" 5 | SECRET="MySecret" 6 | 7 | while true; do 8 | # 获取当前应用的包名 9 | CURRENT_FOCUS=$(dumpsys window | grep mCurrentFocus) 10 | # mCurrentFocus=null 11 | # mCurrentFocus=Window{b061da6 u0 com.termux/com.termux.app.TermuxActivity} 12 | # 提取包名 13 | PACKAGE_NAME=$(echo "$CURRENT_FOCUS" | awk -F '[ /}]' '{print $5}') 14 | # 去除空格 15 | PACKAGE_NAME=$(echo "$PACKAGE_NAME" | tr -d '[:space:]') 16 | # 输出一下PACKAGE_NAME 17 | echo "$PACKAGE_NAME" 18 | # 若PACKAGE_NAME 为 NotificationShade ,则设置变量status为1否则为0 19 | if [ "$PACKAGE_NAME" = "NotificationShade" ]; then 20 | STATUS=1 21 | else 22 | STATUS=0 23 | fi 24 | # 如果包名不为空,则发送GET请求 25 | if [ ! -z "$PACKAGE_NAME" ]; then 26 | # 使用curl发送GET请求 27 | curl -G "$URL" --data-urlencode "secret=$SECRET" --data-urlencode "app_name=$PACKAGE_NAME" --data-urlencode "status=$STATUS" 28 | fi 29 | 30 | # 等待10秒 31 | sleep 10 32 | done -------------------------------------------------------------------------------- /_example/magisk/skip_mount: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/_example/magisk/skip_mount -------------------------------------------------------------------------------- /_example/win.py: -------------------------------------------------------------------------------- 1 | 排除列表 = [ 2 | "任务栏", 3 | "NVIDIA GeForce Overlay DT", 4 | "NVIDIA GeForce Overlay", 5 | "Program Manager", 6 | "任务切换程序" 7 | ] 8 | 服务器地址 = "https://sl.040905.xyz/set" 9 | secret = "Mysecert" 10 | 11 | import requests 12 | import time 13 | from pywinauto import Desktop 14 | while True: 15 | try: 16 | windows = Desktop(backend="uia").windows() 17 | o = [] 18 | for w in windows: 19 | t = w.window_text().split("-")[-1].strip() 20 | if t not in 排除列表 and t not in o and t: 21 | o.append(t) 22 | print(o) 23 | if len(o) > 0: 24 | requests.get( 25 | 服务器地址, 26 | params={ 27 | "pc_status": 0, 28 | "pc_app_name": " | ".join(o) 29 | + " || " 30 | + time.strftime("%Y-%m-%d %H:%M:%S"), 31 | "secret": secret, 32 | }, 33 | ) 34 | else: 35 | requests.get( 36 | 服务器地址, params={"pc_status": 1, "pc_app_name": "", "secret": secret} 37 | ) 38 | time.sleep(10) 39 | except Exception as e: 40 | if str(e) == "KeyboardInterrupt": 41 | break -------------------------------------------------------------------------------- /_example/win.py.bat: -------------------------------------------------------------------------------- 1 | D:\Python312\python.exe E:\HB_WuChang\code\sleepy\_example\win.py -------------------------------------------------------------------------------- /_example/win.py.bat.vbs: -------------------------------------------------------------------------------- 1 | Set ws = CreateObject("Wscript.Shell") 2 | ws.run "cmd /c E:\HB_WuChang\code\sleepy\_example\win.py.bat",vbhide -------------------------------------------------------------------------------- /_example/win.py.bat.vbs - 快捷方式.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/_example/win.py.bat.vbs - 快捷方式.lnk -------------------------------------------------------------------------------- /data.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import json 4 | import os 5 | import utils as u 6 | from jsonc_parser.parser import JsoncParser as jsonp 7 | 8 | 9 | def initJson(): 10 | try: 11 | jsonData = jsonp.parse_file('example.jsonc', encoding='utf-8') 12 | with open('data.json', 'w+', encoding='utf-8') as file: 13 | json.dump(jsonData, file, indent=4, ensure_ascii=False) 14 | except: 15 | u.error('Create data.json failed') 16 | raise 17 | 18 | 19 | class data: 20 | def __init__(self): 21 | if not os.path.exists('data.json'): 22 | u.warning('data.json not exist, creating') 23 | initJson() 24 | with open('data.json', 'r', encoding='utf-8') as file: 25 | self.data = json.load(file) 26 | 27 | def load(self): 28 | with open('data.json', 'r', encoding='utf-8') as file: 29 | self.data = json.load(file) 30 | 31 | def save(self): 32 | with open('data.json', 'w+', encoding='utf-8') as file: 33 | json.dump(self.data, file, indent=4, ensure_ascii=False) 34 | 35 | def dset(self, name, value): 36 | self.data[name] = value 37 | with open('data.json', 'w+', encoding='utf-8') as file: 38 | json.dump(self.data, file, indent=4, ensure_ascii=False) 39 | 40 | def dget(self, name): 41 | with open('data.json', 'r', encoding='utf-8') as file: 42 | self.data = json.load(file) 43 | try: 44 | gotdata = self.data[name] 45 | except: 46 | gotdata = None 47 | return gotdata 48 | -------------------------------------------------------------------------------- /example.jsonc: -------------------------------------------------------------------------------- 1 | /* 2 | version now: 2 3 | 从旧版本更新? 请看 Release 中的更改说明 4 | */ 5 | { 6 | "version": 2, // 配置版本号,一般无需更改 (其实没用, 用来给自己看配置版本的) 7 | "debug": false, // Flask 服务器的 debug 开关 8 | "host": "127.0.0.1", // 监听地址, 0.0.0.0 表示所有; 注意: 如果还需要监听 ipv6 请求,请设置为 :: 9 | "port": 9010, // 监听端口, 默认 9010 10 | "secret": "MySecret", // 密钥, 更新状态时使用 11 | "status": 0, // 目前的状态 (整数) 12 | "app_name": "系统桌面", //当前使用的软件名称 13 | "status_list": [ // 状态列表, 索引从 0 开始, 对应上面的 status 14 | { // status: 0 15 | "id": 0, // 与索引相同,非必须,仅为方便查看 (建议加上) 16 | "name": "APP", // 状态名称 17 | "desc": "目前手机使用的应用,大概率在玩手机摸鱼。", // 状态描述 18 | "color": "awake" // 状态颜色, 对应 templates/style.css 中的 .sleeping .awake 等类, 可自行前往修改 19 | }, 20 | { // status: 1 21 | "id": 1, 22 | "name": "似了", 23 | "desc": "睡似了或其他原因不在线,紧急情况请使用电话联系。", 24 | "color": "sleeping" 25 | } 26 | // 还可添加更多,以此类推 27 | ], 28 | "other": { // 其他配置 (网页上的信息) 29 | "user": "玄", // 你的名字 30 | "background": "https://s.040905.xyz/d/v/1686810550994.jpg?sign=vxvbNJbxPZ-thzF6Pf7QKy4jp0p9lKb3YVGGlLRhQms=:0", // 背景图片 url, 可用网络上的图片 api; 推荐 repo: wyf01239/imgapi (网络 api 集合) 31 | "alpha": 0.85, // 卡片不透明度设置 (0 ~ 1), 0 为完全透明, 1 为完全不透明 32 | "learn_more": "GitHub Repo", // 更多信息链接的提示, 默认为 github repo 33 | "repo": "https://github.com/HBWuChang/sleepy", // 更多信息链接的目标, 可填存储库链接 34 | "more_text": "" // 内容将在状态信息和 "你可以通过这个页面知晓..." 中间插入 (不转义); ps: 可以在这里放你联系方式的链接 35 | } 36 | } -------------------------------------------------------------------------------- /img/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/img/image1.png -------------------------------------------------------------------------------- /img/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/img/image2.png -------------------------------------------------------------------------------- /img/server-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/img/server-1.png -------------------------------------------------------------------------------- /install_lib.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set path_now=%~dp0 3 | echo Run: pip install -r %path_now%requirements.txt 4 | pip install -r %path_now%requirements.txt -------------------------------------------------------------------------------- /install_lib.sh: -------------------------------------------------------------------------------- 1 | pip3 install -r requirements.txt --break-system-packages -------------------------------------------------------------------------------- /jsonc_parser/.gitignore: -------------------------------------------------------------------------------- 1 | # Builder script 2 | project_manifest.json 3 | build.py 4 | 5 | # __pycache__ folder 6 | **/__pycache__ 7 | 8 | # Tools for docs 9 | compile_docs.py 10 | **/docs_templates 11 | 12 | # Test files 13 | test.py 14 | test.json 15 | test.jsonc 16 | 17 | # Distribution folders 18 | /build 19 | /dist 20 | /*.egg-info -------------------------------------------------------------------------------- /jsonc_parser/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 NJickolai Beloguzov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /jsonc_parser/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md -------------------------------------------------------------------------------- /jsonc_parser/README.md: -------------------------------------------------------------------------------- 1 | 本文件夹中的内容来自 [kobayashi-deepinsight/jsonc-parser](https://github.com/kobayashi-deepinsight/jsonc-parser) 2 | 3 | > 以下为原文件内容 4 | 5 | # jsonc-parser 6 | 7 | This package is a lightweight, zero-dependency module for parsing files with .jsonc extension. (a.k.a. JSON with comments) 8 | 9 | ## Installation 10 | 11 | To install this package, simply download it from [PyPI](https://pypi.org/project/jsonc-parser): 12 | 13 | pip install jsonc-parser 14 | 15 | Also you can build it yourself from source code available on [GitHub](https://github.com/NickolaiBeloguzov/jsonc-parser) 16 | 17 | ## Usage 18 | 19 | You need to just import _JsoncParser_ class from this package: 20 | 21 | from jsonc_parser.parser import JsoncParser 22 | 23 | This class requires no instance to function (i.e. it is fully static) 24 | 25 | ## Functions 26 | 27 | These are all methods that JsoncParser class provides for working with .jsonc files: 28 | 29 | - ##### JsoncParser.parse_file(filepath: PathLike) -> dict 30 | 31 | This function parses file, specified in _filepath_ parameter, and deserializes it into a valid Python object (dictionary), removing any comment in the process. No alterations are made it the file itself. _filepath_ parameter specifies path to .jsonc file. 32 | 33 | from jsonc_parser.parser import JsoncParser 34 | 35 | file_path = "./data.jsonc" 36 | # Content from 'data.jsonc' -> {"version": "1.0.0" /*This is my project's version*/} 37 | 38 | data = JsoncParser.parse_file(file_path) 39 | 40 | print(data) 41 | # Output: {'version': '1.0.0'} 42 | 43 | This function can raise _[FunctionParameterError](#exc-function-parameter-error)_ if filepath parameter is not a path-like object (str, bytes object representing a path, or os.PathLike compliant) or is empty. Also this function will raise _[FileError](#exc-file-error)_ exception if file's format is unsupported and a _[ParserError](#exc-parser-error)_ exception if file cannot be parsed/contains invalid JSON data. 44 | 45 | - ##### JsoncParser.parse_str(_string: str) -> dict 46 | 47 | This function parses string, specified in __string_ parameter, and deserializes it into a valid Python object (dictionary), removing any comment in the process. 48 | 49 | from jsonc_parser.parser import JsoncParser 50 | 51 | json_string = """{"version": "1.0.0" /*This is my project's version*/}""" 52 | 53 | data = JsoncParser.parse_str(json_string) 54 | 55 | print(data) 56 | # Output: {'version': '1.0.0'} 57 | 58 | This function can raise _[FunctionParameterError](#exc-function-parameter-error)_ if __string_ parameter is not a string or is empty. Also this function will raise a _[ParserError](#exc-parser-error)_ exception if file cannot be parsed/contains invalid JSON data. 59 | 60 | ##### JsoncParser.convert_to_json(filepath: PathLike, remove_file: bool = False) -> None 61 | This function converts file from .jsonc to .json format, removing any comments in the process. filepath parameter specifies path to file and remove_file parameter specifies if .jsonc file will be removed (deleted from hard drive) after conversion. If set to True, this function will delete .jsonc file leaving only .json file. Otherwise, both files are not deleted. This function can raise _[FunctionParameterError](#exc-function-parameter-error)_ if _filepath_ parameter is not a path-like object or is empty or if _remove_file_ parameter is not a boolean. 62 | 63 | - ##### JsoncParser.convert_to_jsonc(filepath: PathLike, remove_file: bool = False) -> None 64 | This function converts file from .json to .jsonc format, enabling comment support. filepath parameter specifies path to file and _remove_file_ parameter specifies if .jsonc file will be removed (deleted from hard drive) after conversion. If set to True, this function will delete .jsonc file leaving only .json file. Otherwise, both files are not deleted. 65 | This function can raise _[FunctionParameterError](#exc-function-parameter-error)_ if _filepath_ parameter is not a path-like object or is empty or if _remove_file_ parameter is not a boolean. 66 | 67 | ## Exceptions 68 | 69 | There are a total of 3 custom exceptions that jsonc-parser can raise during its runtime. To access the in your script, simply import them from jsonc_parser.errors module: 70 | 71 | from jsonc_parser.errors import FileError, IncorrectParameterError, ParserError 72 | 73 | #### Exceptions: 74 | 75 | - **FileError** 76 |
77 | This exception indicates that there is a problem with selected file. 78 | 79 | - **FunctionParameterError** 80 |
81 | This exception indicates that some of function's parameters are invalid. They may have wrong type, have invalid values or be erroneous in some other way. 82 | 83 | - **ParserError** 84 |
85 | This exception indicates that file cannot be parsed. It can have wrong extension, invalid data, etc. 86 | -------------------------------------------------------------------------------- /jsonc_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/jsonc_parser/__init__.py -------------------------------------------------------------------------------- /jsonc_parser/errors.py: -------------------------------------------------------------------------------- 1 | class FunctionParameterError(Exception): 2 | """ 3 | This excption indicates that one or more of function's parameters are incorrect 4 | """ 5 | 6 | def __init__(self, *args): 7 | self.__msg = args[0] 8 | super().__init__(self.__msg) 9 | 10 | def __str__(self): 11 | return self.__msg 12 | 13 | 14 | class FileError(Exception): 15 | """ 16 | This exception indicates that file format cannot be parsed 17 | """ 18 | 19 | def __init__(self, *args: object) -> None: 20 | super().__init__(*args) 21 | 22 | def __str__(self) -> str: 23 | return super().__str__() 24 | 25 | 26 | class ParserError(Exception): 27 | """ 28 | This exception indicates that file cannot be parsed 29 | """ 30 | 31 | def __init__(self, *args: object) -> None: 32 | super().__init__(*args) 33 | 34 | def __str__(self) -> str: 35 | return super().__str__() -------------------------------------------------------------------------------- /jsonc_parser/jsonc_parser/README.md: -------------------------------------------------------------------------------- 1 | 本文件夹中以下内容已移至上级目录: 2 | 3 | - `__init__.py` 4 | - `parser.py` 5 | - `errors.py` -------------------------------------------------------------------------------- /jsonc_parser/parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from jsonc_parser.errors import FileError, FunctionParameterError, ParserError 4 | import os 5 | from typing import Union 6 | 7 | 8 | class JsoncParser: 9 | 10 | # regex = re.compile(r"//.*?\n|/\*.*?\*/", re.MULTILINE | re.DOTALL) 11 | regex = re.compile(r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)", re.MULTILINE | re.DOTALL) 12 | newline_replace_regex = re.compile("\n{2,}", re.MULTILINE) 13 | 14 | def parse_str(_string: str) -> dict: 15 | """ 16 | Parse JSON-as-string and deserialize its content into Python dictionary, 17 | ignoring any comments. 18 | 19 | Parameters: `_string:str` - path to file 20 | 21 | This function will raise a `FunctionParameterError` exception if `_string` parameter 22 | has an incorrect type or is empty. 23 | This function will raise a `ParserError` exception if the string cannot be parsed. 24 | This function will raise any additional exceptions if occurred. 25 | """ 26 | 27 | def __re_sub(match): 28 | if match.group(2) is not None: 29 | return "" 30 | else: 31 | return match.group(1) 32 | 33 | if type(_string) != str: 34 | raise FunctionParameterError( 35 | "_string parameter must be str; got {} instead".format(type(_string).__name__) 36 | ) 37 | 38 | try: 39 | data = JsoncParser.regex.sub(__re_sub, _string) 40 | return json.loads(JsoncParser.regex.sub(__re_sub, data)) 41 | except Exception as e: 42 | raise ParserError("{} file cannot be parsed (message: {})".format(_string, str(e))) 43 | 44 | @staticmethod 45 | def parse_file(filepath: Union[str, os.PathLike], encoding: str = None) -> dict: 46 | """ 47 | Parse .jsonc file and deserialize its content into Python dictionary, 48 | ignoring any comments. 49 | 50 | Parameters: 51 | `filepath: str | os.PathLike` - path to file 52 | `encoding: str = None` - option of open() 53 | 54 | This function will raise a `FunctionParameterError` exception if `filepath` parameter 55 | has an incorrect type or is empty. 56 | This function will raise a `FileError` exception if file format is unsupported. 57 | This function will raise a `ParserError` exception if file cannot be parsed. 58 | This function will raise any additional exceptions if occurred. 59 | """ 60 | 61 | def __re_sub(match): 62 | if match.group(2) is not None: 63 | return "" 64 | else: 65 | return match.group(1) 66 | 67 | # verify that provided path is a valid path-like object 68 | try: 69 | filepath = os.fspath(filepath) 70 | except TypeError: 71 | raise FunctionParameterError( 72 | "filepath parameter must be path-like; got {} instead".format(type(filepath).__name__) 73 | ) 74 | 75 | if not filepath: 76 | raise FunctionParameterError("path is empty.") 77 | 78 | if not os.path.exists(filepath) or not os.path.isfile(filepath): 79 | raise FileError("{} does not exist or is not a file".format(filepath)) 80 | 81 | if filepath.split(".")[-1] not in ["json", "jsonc"]: 82 | raise FileError("file {} has an unsupported extension.".format(filepath)) 83 | 84 | json_file = open(filepath, "r", encoding=encoding) 85 | data_raw = json_file.read() 86 | json_file.close() 87 | 88 | try: 89 | data = JsoncParser.regex.sub(__re_sub, data_raw) 90 | return json.loads(JsoncParser.regex.sub(__re_sub, data)) 91 | except Exception as e: 92 | raise ParserError("{} file cannot be parsed (message: {})".format(filepath, str(e))) 93 | 94 | @staticmethod 95 | def convert_to_json( 96 | filepath: Union[str, os.PathLike], 97 | remove_file: bool = False, 98 | encoding: str = None, 99 | ensure_ascii: bool = True, 100 | ) -> None: 101 | """ 102 | Convert file from .jsonc to .json format, removing any comments from it 103 | 104 | Parameters: `path: str | os.PathLike` is a path to file, `remove_file:bool` indicates if 105 | source file will be deleted or not. If set to True, .jsonc file will be deleted from the 106 | hard drive, otherwise file remains alongside with its .json output. 107 | `encoding: str = None` - option of open() 108 | `ensure_ascii: bool = True` - option of json.dump() 109 | 110 | This function will raise a `FunctionParameterError` if one or more of function's parameters 111 | has an incorrect type/invalid value. 112 | This function will raise any additional exceptions if occurred. 113 | 114 | """ 115 | 116 | # verify that provided path is a valid path-like object 117 | try: 118 | filepath = os.fspath(filepath) 119 | except TypeError: 120 | raise FunctionParameterError( 121 | "filepath parameter must be path-like; got {} instead".format(type(filepath).__name__) 122 | ) 123 | 124 | if not filepath: 125 | raise FunctionParameterError("path is empty.") 126 | 127 | if type(remove_file) != bool: 128 | raise FunctionParameterError( 129 | "remove_file parameter must be bool; got {} instead.".format( 130 | type(remove_file).__name__ 131 | ) 132 | ) 133 | 134 | data = JsoncParser.parse_file(filepath, encoding=encoding) 135 | 136 | if remove_file: 137 | os.remove(filepath) 138 | 139 | new_filename = os.path.splitext(filepath)[0] + ".json" 140 | if os.path.exists(new_filename) and os.path.isfile(new_filename): 141 | raise FileError("{} file already exists".format(new_filename)) 142 | 143 | json_file = open(new_filename, "x", encoding=encoding) 144 | json_file.write(json.dumps(data, indent=2, ensure_ascii=ensure_ascii)) 145 | json_file.close() 146 | 147 | @staticmethod 148 | def convert_to_jsonc( 149 | filepath: Union[str, os.PathLike], 150 | remove_file: bool = False, 151 | encoding: str = None, 152 | ensure_ascii: bool = True 153 | ): 154 | """ 155 | Convert file .jsonc format, enabling comments. 156 | 157 | Parameters: `filepath: str | os.PathLike` is a path to file, `remove_file:bool` indicates 158 | if source file will be deleted or not. If set to True, .json file will be deleted from the 159 | hard drive, otherwise file remains alongside with its .jsonc output. 160 | `encoding: str = None` - option of open() 161 | `ensure_ascii: bool = True` - option of json.dump() 162 | 163 | This function will raise a `FunctionParameterError` if one or more of function's parameters 164 | has an incorrect type/invalid value. 165 | This function will raise any additional exceptions if occurred. 166 | """ 167 | # verify that provided path is a valid path-like object 168 | try: 169 | filepath = os.fspath(filepath) 170 | except TypeError: 171 | raise FunctionParameterError( 172 | "filepath parameter must be path-like; got {} instead".format(type(filepath).__name__) 173 | ) 174 | 175 | if not filepath: 176 | raise FunctionParameterError("path is empty.") 177 | 178 | if type(remove_file) != bool: 179 | raise FunctionParameterError( 180 | "remove_file parameter must be bool; got {} instead.".format( 181 | type(remove_file).__name__ 182 | ) 183 | ) 184 | 185 | data = JsoncParser.parse_file(filepath, encoding=encoding) 186 | 187 | if remove_file: 188 | os.remove(filepath) 189 | 190 | new_filename = os.path.splitext(filepath)[0] + ".jsonc" 191 | if os.path.exists(new_filename) and os.path.isfile(new_filename): 192 | raise FileError("{} file already exists".format(new_filename)) 193 | 194 | json_file = open(new_filename, "x", encoding=encoding) 195 | json_file.write(json.dumps(data, indent=2, ensure_ascii=ensure_ascii)) 196 | json_file.close() 197 | -------------------------------------------------------------------------------- /jsonc_parser/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r", encoding="utf-8") as docs: 4 | long_description = docs.read() 5 | 6 | setup( 7 | name="jsonc-parser", 8 | version="1.1.0", 9 | author="Nickolai Beloguzov", 10 | author_email="nickolai.beloguzov@gmail.com", 11 | description="A lightweight, native tool for parsing .jsonc files", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | packages=["jsonc_parser"], 15 | url="https://github.com/NickolaiBeloguzov/jsonc-parser", 16 | license="MIT", 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.6", 20 | "Programming Language :: Python :: 3.5", 21 | "Programming Language :: Python :: 3.7", 22 | "Programming Language :: Python :: 3.8", 23 | "Programming Language :: Python :: 3.9", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | "Development Status :: 5 - Production/Stable", 27 | "Intended Audience :: Developers", 28 | "Natural Language :: English", 29 | ], 30 | python_requires=">=3.5", 31 | ) 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | import utils as u 4 | from data import data as data_init 5 | from flask import Flask, render_template, request, url_for, redirect, flash, make_response 6 | from markupsafe import escape 7 | 8 | 9 | d = data_init() 10 | app = Flask(__name__) 11 | 12 | # --- 13 | 14 | 15 | def reterr(code, message): 16 | ret = { 17 | 'success': False, 18 | 'code': code, 19 | 'message': message 20 | } 21 | u.error(f'{code} - {message}') 22 | return u.format_dict(ret) 23 | 24 | 25 | def showip(req, msg): 26 | ip1 = req.remote_addr 27 | try: 28 | ip2 = req.headers['X-Forwarded-For'] 29 | u.infon(f'- Request: {ip1} / {ip2} : {msg}') 30 | except: 31 | ip2 = None 32 | u.infon(f'- Request: {ip1} : {msg}') 33 | 34 | @app.route('/') 35 | def index(): 36 | d.load() 37 | showip(request, '/') 38 | ot = d.data['other'] 39 | try: 40 | stat = d.data['status_list'][d.data['status']].copy() 41 | pc_stat = d.data['status_list'][d.data['pc_status']].copy() 42 | if(d.data['pc_status'] == 0): 43 | pc_app_name = d.data['pc_app_name'] 44 | pc_stat['name'] = pc_app_name 45 | if(d.data['status'] == 0): 46 | app_name = d.data['app_name'] 47 | stat['name'] = app_name 48 | except: 49 | stat = { 50 | 'name': '未知', 51 | 'desc': '未知的标识符,可能是配置问题。', 52 | 'color': 'error' 53 | } 54 | return render_template( 55 | 'index.html', 56 | user=ot['user'], 57 | learn_more=ot['learn_more'], 58 | repo=ot['repo'], 59 | status_name=stat['name'], 60 | status_desc=stat['desc'], 61 | pc_status_name=pc_stat['name'], 62 | pc_status_desc=pc_stat['pc_desc'], 63 | status_color=stat['color'], 64 | pc_status_color=pc_stat['pc_color'], 65 | more_text=ot['more_text'] 66 | ) 67 | 68 | 69 | @app.route('/style.css') 70 | def style_css(): 71 | response = make_response(render_template( 72 | 'style.css', 73 | bg=d.data['other']['background'], 74 | alpha=d.data['other']['alpha'] 75 | )) 76 | response.mimetype = 'text/css' 77 | return response 78 | 79 | 80 | @app.route('/query') 81 | def query(): 82 | d.load() 83 | showip(request, '/query') 84 | st = d.data['status'] 85 | # stlst = d.data['status_list'] 86 | try: 87 | stinfo = d.data['status_list'][st] 88 | if(st == 0): 89 | stinfo['name'] = d.data['app_name'] 90 | except: 91 | stinfo = { 92 | 'status': st, 93 | 'name': '未知' 94 | } 95 | ret = { 96 | 'success': True, 97 | 'status': st, 98 | 'info': stinfo 99 | } 100 | return u.format_dict(ret) 101 | 102 | 103 | @app.route('/get/status_list') 104 | def get_status_list(): 105 | showip(request, '/get/status_list') 106 | stlst = d.dget('status_list') 107 | return u.format_dict(stlst) 108 | 109 | 110 | @app.route('/set') 111 | def set_normal(): 112 | showip(request, '/set') 113 | status = escape(request.args.get("status")) 114 | app_name = escape(request.args.get("app_name")) 115 | if not status.isdigit(): 116 | status = None 117 | if app_name == "" or app_name == "None": 118 | app_name = None 119 | pc_status = escape(request.args.get("pc_status")) 120 | pc_app_name = escape(request.args.get("pc_app_name")) 121 | if not pc_status.isdigit(): 122 | pc_status = None 123 | if pc_app_name == "" or pc_app_name == "None": 124 | pc_app_name = None 125 | secret = escape(request.args.get("secret")) 126 | u.info(f'status: {status}, name: {app_name}, secret: "{secret}", pc_status: {pc_status}, pc_name: {pc_app_name}') 127 | print(f'status: {status}, name: {app_name}, secret: "{secret}", pc_status: {pc_status}, pc_name: {pc_app_name}') 128 | secret_real = d.dget('secret') 129 | if secret == secret_real: 130 | if status is not None: 131 | d.dset('status', int(status)) 132 | if app_name is not None: 133 | d.dset('app_name', app_name) 134 | if pc_status is not None: 135 | d.dset('pc_status', int(pc_status)) 136 | if pc_app_name is not None: 137 | d.dset('pc_app_name', pc_app_name) 138 | u.info('set success') 139 | ret = { 140 | 'success': True, 141 | 'code': 'OK', 142 | 'set_to': status, 143 | 'app_name':app_name, 144 | 'pc_set_to': pc_status, 145 | 'pc_app_name': pc_app_name 146 | } 147 | return u.format_dict(ret) 148 | else: 149 | return reterr( 150 | code='not authorized', 151 | message='invaild secret' 152 | ) 153 | 154 | 155 | 156 | if __name__ == '__main__': 157 | d.load() 158 | app.run( 159 | host=d.data['host'], 160 | port=d.data['port'], 161 | debug=d.data['debug'] 162 | ) 163 | -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | import os 4 | import sys 5 | import time 6 | c = 0 7 | selfn = sys.argv[0] 8 | dirn = os.path.dirname(selfn) 9 | server = os.path.join(dirn, 'server.py') 10 | if len(sys.argv) > 1: 11 | match sys.argv[1]: 12 | case 'screen': 13 | st = os.system(f'cd {dirn} && screen -dmS sleepy {sys.argv[0]}') 14 | if st == 0: 15 | print(f'Started screen: cd {dirn} && screen -dmS sleepy {sys.argv[0]}') 16 | while True: 17 | time.sleep(114514) 18 | #exit(0) 19 | else: 20 | print(f'Start screen failed: cd {dirn} && screen -dmS sleepy {sys.argv[0]}') 21 | exit(1) 22 | case _: 23 | print('Invaild arg.') 24 | exit(1) 25 | print(f'Server path: {server}') 26 | while True: 27 | c += 1 28 | print(f'Starting server #{c}') 29 | if os.name == 'nt': 30 | r = os.system(f'python {server}') 31 | else: 32 | r = os.system(f'python3 {server}') 33 | print(f'#{c} exited with code {r}\nwaiting 5s') 34 | time.sleep(5) 35 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBWuChang/sleepy/5cee1c19020511a87774cb6e3e20a3b77f7c1ac9/static/favicon.ico -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ user }} Alive? 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 45 | 46 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /templates/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 25px; 3 | } 4 | 5 | rt { 6 | font-size: 0.6em; 7 | } 8 | 9 | body, html { 10 | margin: 0; 11 | padding: 0; 12 | height: 100%; 13 | overflow: hidden; 14 | } 15 | 16 | body { 17 | /* 背景图设置 */ 18 | background: url('{{ bg }}') no-repeat center center fixed; 19 | background-size: cover; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | .card { 26 | background-color: rgba(255, 255, 255, {{ alpha }}); 27 | border-radius: 30px; 28 | padding: 50px; 29 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 30 | text-align: center; 31 | } 32 | 33 | /* 以下为各种状态的颜色 */ 34 | 35 | .sleeping { 36 | color: gray; 37 | } 38 | 39 | .awake { 40 | color: rgb(16, 128, 0); 41 | } 42 | 43 | .error { 44 | color: red; 45 | } -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | import os 4 | 5 | 6 | def info(log): 7 | print(f"[Info] {datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')} " + log) 8 | 9 | 10 | def infon(log): 11 | print(f"\n[Info] {datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')} " + log) 12 | 13 | 14 | def warning(log): 15 | print(f"[Warning] {datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')} " + log) 16 | 17 | 18 | def error(log): 19 | print(f"[Error] {datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')} " + log) 20 | 21 | 22 | def format_dict(dic): 23 | ''' 24 | 列表 -> 格式化 json 25 | @param dic: 列表 26 | ''' 27 | return json.dumps(dic, indent=4, ensure_ascii=False, sort_keys=False, separators=(', ', ': ')) 28 | -------------------------------------------------------------------------------- /前台应用状态.macro: -------------------------------------------------------------------------------- 1 | {"macro":{"disabledTimestamp":0,"exportedActionBlocks":[],"forceEvenIfNotEnabledTimestamp":0,"isActionBlock":false,"isBeingImported":false,"isClonedInstance":false,"isExtra":false,"isFavourite":false,"lastEditedTimestamp":1732691436491,"localVariables":[],"m_GUID":-6031364693124630676,"m_actionList":[{"requestConfig":{"allowAnyCertificate":false,"basicAuthEnabled":false,"basicAuthPassword":"","basicAuthUsername":"","blockNextAction":false,"contentBodyFileDisplayName":"","contentBodyFileUri":"","contentBodySource":0,"contentBodyText":"","contentType":"","followRedirects":false,"headerParams":[],"queryParams":[],"requestTimeOutSeconds":30,"requestType":0,"saveResponseFileName":"","saveResponseFolderPathDisplayName":"","saveResponseFolderPathUri":"","saveResponseType":0,"saveReturnCodeToVariable":false,"saveReturnHeadersToVariable":false,"urlToOpen":"http://sleepy.1812z.top/set?secret\u003d123456\u0026status\u003d0\u0026app_name\u003d{fg_app_name}"},"m_SIGUID":-6631721251777490861,"m_classType":"HttpRequestAction","m_constraintList":[],"m_isDisabled":false,"m_isOrCondition":false}],"m_category":"未分类","m_constraintList":[],"m_description":"","m_descriptionOpen":false,"m_enabled":false,"m_excludeLog":false,"m_headingColor":0,"m_isOrCondition":false,"m_name":"前台应用状态","m_triggerList":[{"isAllApps":true,"m_applicationNameList":[],"m_launched":true,"m_packageNameList":[],"usePackageNameOption":0,"m_SIGUID":-5937240281190642385,"m_classType":"ApplicationLaunchedTrigger","m_constraintList":[],"m_isDisabled":false,"m_isOrCondition":false}]},"macroExportVersion":1} --------------------------------------------------------------------------------