├── LICENSE ├── README.md ├── SillyDev ├── README.md ├── config.json ├── index.js └── package.json ├── bark-server ├── index.js └── package.json └── ddddocr ├── README.md ├── main.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sliverkiss 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Awesome Serverless stars

4 | 5 | 本仓库收录基于Serverless服务的开源工具,为独立开发者早期摸索期提供一个省心省时的工具集,持续整理中…… 6 |
7 | 8 | **收录标准:** 9 | 10 | - 帮助但不限于独立开发者提升开发效率 11 | - 帮助但不限于独立开发者降低成本 12 | - 足够简单便捷 13 | 14 | 欢迎提 pr 和 issues 更新。 部署或操作过程中有任何问题可以提issue或者私信咨询~ 15 | 16 | ## Python 17 | 18 | | 名称 | 特性 |在线地址 | 状态| 19 | | --- | --- | --- |---| 20 | | ddddocr |滑块识别 | | 有效中| 21 | 22 | ## Node.js 23 | 24 | | 名称 | 特性 |在线地址 | 状态| 25 | | --- | --- | --- |---| 26 | | SillyDev |silly自动刷积分 | | 有效中| 27 | | bark-server |bark推送服务器搭建 | | 有效中| 28 | 29 | 30 | ## Star History 31 | 32 | [![Star History Chart](https://api.star-history.com/svg?repos=Slliverkiss/serverlesss&type=Timeline)](https://star-history.com/#sliverkiss/serverless&Timeline) 33 | -------------------------------------------------------------------------------- /SillyDev/README.md: -------------------------------------------------------------------------------- 1 | # SillyDev 2 | 在 Silly 上部署 ```Node服务``` 自动获取```Silly Development```积分、并通过telegram bot管理信息、兑换资源及服务器续期。如果本项目对你有所帮助的话,不妨点个Star⭐️ 3 | 4 | ### 一、准备材料 5 | 6 | 1. 一枚邮箱,不建议使用qq邮箱。 7 | 8 | ### 二、部署服务。 9 | 1. 打开 Silly 的网站:https://panel.sillydevelopment.co.uk ,然后点击里面的“Signup with email”进行注册。如有账户直接登录即可。 10 | 11 | kkk.png 12 | 13 | 2. 打开自己的邮件,然后点击“Verify email”验证邮箱. 14 | 15 | wcc.png 16 | 17 | 3. 点击左侧栏的编辑按钮,然后点击“Create”进行创建 18 | 19 | cvv.png 20 | 21 | 4. 可以使用最小配置(Cpu选择50,RAM选择512,Storage选择512),选择地区随意选择,然后 Nest 选择“Code Languages”,Egg 选择“Node.js”,最后点击“Create”按钮,创建服务器。 22 | 23 | csc.png 24 | 25 | cscs.png 26 | 27 | 5. 等待服务器安装的时候,可以将本仓库的内容下载项目文件下来。 28 | 29 | xaa.png 30 | 31 | 6. 将除了 ```README.md``` 和 ```LICENSE``` 文件外的所有文件,上传至服务器上 32 | 33 | rgrg.png 34 | 35 | 7. 根据下面表格信息,修改```conf.json```内容 36 | 37 | | 变量名 | 是否必须 | 默认 | 备注 | 38 | | :----: | :--: |:--: | -------: | 39 | | tgBotToken | Yes | | 可以通过bot来管理面板 | 40 | | X-XSRF-Token | Yes | | 自行通过抓包获取 | 41 | | Cookie | Yes | | 自行通过抓包获取 | 42 | | User-Agent | No | | 自行通过抓包获取 | 43 | 44 | > Bot指令如下: 45 | 46 | | 命令 | 说明 | 47 | | :----: | :--: | 48 | | ```/start``` | 开始 | 49 | | ```/help``` | 查看菜单 | 50 | | ```/info``` | 查看Silly个人信息 | 51 | | ```/server``` | 查看当前服务器信息 | 52 | | ```/renew``` | 服务器续期 | 53 | | ```/resources``` | 兑换服务器资源 | 54 | 55 | 56 | 8. 转到“Console”页面,点击“Start”按钮,即可食用。 57 | 58 | -------------------------------------------------------------------------------- /SillyDev/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tgBotToken":"", 3 | "X-XSRF-Token":"", 4 | "Cookie":"", 5 | "User-Agent":"" 6 | } 7 | -------------------------------------------------------------------------------- /SillyDev/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const TelegramBot = require("node-telegram-bot-api"); 3 | const { 4 | tgBotToken, 5 | "X-XSRF-Token": token, 6 | Cookie: ck, 7 | "User-Agent": ua, 8 | } = require("./config.json"); 9 | const SERVER = process.env.SERVER_IP; 10 | const PORT = process.env.SERVER_PORT; 11 | const app = express(); 12 | 13 | const bot = new TelegramBot(tgBotToken, { polling: true }); 14 | 15 | bot.onText(/\/start/, (msg) => { 16 | bot.sendMessage( 17 | msg.chat.id, 18 | "Welcome to Silly Bot. Use /help to see available commands." 19 | ); 20 | }); 21 | 22 | // Listen for /help command 23 | bot.onText(/\/help/, (msg) => { 24 | bot.sendMessage( 25 | msg.chat.id, 26 | "Available commands:\n" + 27 | "/info - 查看Silly个人信息\n" + 28 | "/server - 查看当前服务器信息\n" + 29 | "/renew - 服务器续期\n" + 30 | "/resources - 兑换服务器资源" 31 | ); 32 | }); 33 | 34 | bot.onText(/\/info/, async (msg) => { 35 | try { 36 | const escapeMarkdown = (text) => { 37 | return text 38 | .toString() 39 | .replace(/([\_\*\[\]\(\)\~\`\>\#\+\-\=\|\{\}\.\!])/g, "\\$1"); 40 | }; 41 | // 计算每列的最大宽度 42 | const calculateColumnWidths = (keys, values) => { 43 | let keyWidth = Math.max(...keys.map((key) => key.length)); 44 | let valueWidth = Math.max( 45 | ...values.map((value) => value.toString().length) 46 | ); 47 | return [keyWidth, valueWidth]; 48 | }; 49 | 50 | // 格式化 JSON 为两列多行 Markdown 表格 51 | const formatJsonToTable = (data) => { 52 | const keys = Object.keys(data); 53 | const values = Object.values(data); 54 | const [keyWidth, valueWidth] = calculateColumnWidths(keys, values); 55 | 56 | let formattedTable = ""; 57 | 58 | // 打印表头 59 | formattedTable += `| ${"Key".padEnd(keyWidth)} | ${"Value".padEnd( 60 | valueWidth 61 | )} |\n`; 62 | formattedTable += `| ${"-".repeat(keyWidth)} | ${"-".repeat( 63 | valueWidth 64 | )} |\n`; // 表头分隔线 65 | 66 | // 打印数据行 67 | keys.forEach((key, index) => { 68 | const escapedKey = escapeMarkdown(key).padEnd(keyWidth); 69 | const escapedValue = escapeMarkdown( 70 | values[index].toString() 71 | ).padEnd(valueWidth); 72 | formattedTable += `| ${escapedKey} | ${escapedValue} |\n`; 73 | }); 74 | 75 | return formattedTable; 76 | }; 77 | let response = await get("/api/client/store"); 78 | const chatId = msg.chat.id; 79 | const tableMessage = formatJsonToTable(response?.attributes); 80 | 81 | bot.sendMessage( 82 | chatId, 83 | "查询成功!当前可用资源如下:\n" + 84 | `\`\`\`\n${tableMessage}\n\`\`\``, 85 | { parse_mode: "MarkdownV2" } 86 | ); 87 | } catch (e) { 88 | bot.sendMessage(msg.chat.id, `Error:${e}`); 89 | } 90 | }); 91 | 92 | bot.onText(/\/server/, async (msg) => { 93 | try { 94 | let response = await get("/api/client?page=1"); 95 | let total = response.meta.pagination.total; 96 | let serverList = response?.data 97 | ?.map((e) => { 98 | let o = e.attributes; 99 | return { 100 | name: o.name, 101 | serverId: o.uuid, 102 | renewal: o.renewal, 103 | status: o.status, 104 | }; 105 | }) 106 | .filter((e) => e.status != "suspended"); 107 | Promise.all( 108 | serverList.map((e) => { 109 | const text = `*${e.name}*\nUUID:${e.serverId}\n到期天数:${e.renewal}`; 110 | return bot.sendMessage(msg.chat.id, text, { 111 | parse_mode: "Markdown", 112 | }); 113 | }) 114 | ); 115 | bot.sendMessage( 116 | msg.chat.id, 117 | `共${total}台服务器,可用数量:${serverList.length}` 118 | ); 119 | } catch (e) { 120 | bot.sendMessage(msg.chat.id, `Error:${e}`); 121 | } 122 | }); 123 | 124 | bot.onText(/\/renew/, async (msg) => { 125 | try { 126 | let response = await get("/api/client?page=1"); 127 | let arr = [[{ text: "全部", callback_data: "all" }]]; 128 | response?.data?.map((e) => { 129 | let o = e.attributes; 130 | if (o.status != "suspended") 131 | arr.push([{ text: o.name, callback_data: o.name }]); 132 | }); 133 | const options = { 134 | reply_markup: { 135 | inline_keyboard: arr, 136 | }, 137 | }; 138 | bot.sendMessage(msg.chat.id, "请选择要续期的服务器:", options); 139 | } catch (e) { 140 | bot.sendMessage(msg.chat.id, `Error:${e}`); 141 | } 142 | }); 143 | 144 | bot.onText(/\/resources/, async (msg) => { 145 | try { 146 | let arr = [ 147 | [ 148 | { text: "balance", callback_data: "balance" }, 149 | { text: "cpu", callback_data: "cpu" }, 150 | ], 151 | [ 152 | { text: "memory", callback_data: "memory" }, 153 | { text: "disk", callback_data: "disk" }, 154 | ], 155 | [ 156 | { text: "slots", callback_data: "slots" }, 157 | { text: "ports", callback_data: "ports" }, 158 | ], 159 | [ 160 | { text: "backups", callback_data: "backups" }, 161 | { text: "databases", callback_data: "databases" }, 162 | ], 163 | ]; 164 | const options = { 165 | reply_markup: { 166 | inline_keyboard: arr, 167 | }, 168 | }; 169 | bot.sendMessage(msg.chat.id, "请选择要兑换的资源:", options); 170 | } catch (e) { 171 | bot.sendMessage(msg.chat.id, `Error:${e}`); 172 | } 173 | }); 174 | 175 | // 处理按钮点击事件 176 | bot.on("callback_query", async (callbackQuery) => { 177 | const msg = callbackQuery.message; 178 | const data = callbackQuery.data; 179 | let response = await get("/api/client?page=1"); 180 | let serverList = response?.data 181 | ?.map((e) => { 182 | let o = e.attributes; 183 | return { 184 | name: o.name, 185 | serverId: o.uuid, 186 | renewal: o.renewal, 187 | status: o.status, 188 | }; 189 | }) 190 | .filter((e) => e.status != "suspended"); 191 | switch (data) { 192 | case "all": 193 | await Promise.all( 194 | serverList.map((e) => 195 | post(`/api/client/servers/${e.serverId}/renew`) 196 | ) 197 | ); 198 | let message = []; 199 | serverList.map((e) => message.push(`*${e.name}*`)); 200 | bot.sendMessage( 201 | msg.chat.id, 202 | `${message.join("、")}续期成功🎉🎉🎉`, 203 | { parse_mode: "Markdown" } 204 | ); 205 | break; 206 | case "balance": 207 | case "cpu": 208 | case "memory": 209 | case "disk": 210 | case "slots": 211 | case "ports": 212 | case "backups": 213 | case "databases": 214 | await post(`/api/client/store/resources`, { resource: data }); 215 | bot.sendMessage(msg.chat.id, `兑换*${data}*资源成功🎉🎉🎉`, { 216 | parse_mode: "Markdown", 217 | }); 218 | break; 219 | default: 220 | let server = serverList.find((e) => e.name == data); 221 | await post(`/api/client/servers/${server.serverId}/renew`); 222 | bot.sendMessage(msg.chat.id, `*${server.name}*续期成功🎉🎉🎉`, { 223 | parse_mode: "Markdown", 224 | }); 225 | break; 226 | } 227 | }); 228 | 229 | //首页显示内容 230 | app.get("/", function (req, res) { 231 | res.send("hello world"); 232 | }); 233 | 234 | app.get("/test", async function (req, res) { 235 | let response = await get("/api/client?page=1"); 236 | res.send(response); 237 | }); 238 | 239 | // keepalive begin 240 | async function keep_web_alive() { 241 | try { 242 | const response = await fetch(`http://${SERVER}:${PORT}`); 243 | const body = await response.text(); 244 | if (!response.ok) { 245 | throw new Error(`保活-请求主页-命令行执行错误: ${response.status}`); 246 | } 247 | console.log(`保活-请求主页-命令行执行成功,响应报文: ${body}`); 248 | } catch (error) { 249 | console.error(`保活-请求主页-命令行执行错误: ${error.message}`); 250 | } 251 | } 252 | 253 | async function getEarn() { 254 | console.log("开始执行服务器获取积分任务..."); 255 | await post("/api/client/store/creditearning"); 256 | } 257 | 258 | //--—-----------------任务定时配置---—-------------- 259 | //定时自动保活,每10秒执行一次 260 | setInterval(keep_web_alive, 10e3); 261 | //定时自动获取积分,每分钟执行一次 262 | setInterval(getEarn, 6e4); 263 | 264 | //--—-----------------辅助函数区域---—-------------- 265 | //封装请求方法 266 | async function get(api) { 267 | const res = await fetch(`https://panel.sillydevelopment.co.uk${api}`, { 268 | headers: { 269 | Accept: "application/json", 270 | "Accept-Encoding": "gzip, deflate, br", 271 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 272 | Connection: "keep-alive", 273 | Cookie: ck, 274 | Host: "panel.sillydevelopment.co.uk", 275 | Referer: "https://panel.sillydevelopment.co.uk/", 276 | "Sec-Fetch-Mode": "cors", 277 | "Sec-Fetch-Dest": "empty", 278 | "Sec-Fetch-Site": "same-origin", 279 | "X-Requested-With": "XMLHttpRequest", 280 | "X-XSRF-TOKEN": token, 281 | "User-Agent": 282 | ua || 283 | "Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/111.0.5563.101 Mobile/15E148 Safari/604.1", 284 | }, 285 | }).then((res) => res.json()); 286 | return res; 287 | } 288 | 289 | async function post(api, data = {}) { 290 | const url = `https://panel.sillydevelopment.co.uk${api}`; 291 | const headers = { 292 | Accept: "application/json", 293 | "Accept-Encoding": "gzip, deflate, br", 294 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 295 | Connection: "keep-alive", 296 | "Content-Type": "application/json", 297 | Cookie: ck, 298 | Host: "panel.sillydevelopment.co.uk", 299 | Origin: "https://panel.sillydevelopment.co.uk", 300 | "Sec-Fetch-Mode": "cors", 301 | "Sec-Fetch-Dest": "empty", 302 | "Sec-Fetch-Site": "same-origin", 303 | "User-Agent": 304 | ua || 305 | `Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/111.0.5563.101 Mobile/15E148 Safari/604.1`, 306 | "X-Requested-With": "XMLHttpRequest", 307 | "X-XSRF-TOKEN": token, 308 | }; 309 | 310 | try { 311 | const response = await fetch(url, { 312 | method: "POST", 313 | headers: headers, 314 | body: JSON.stringify(data), 315 | }); 316 | if (response.status === 204) { 317 | console.log(`Request successful: ${response.status} => 调用成功!`); 318 | } else if (response.status === 429) { 319 | console.error( 320 | `Error: ${response.status} => 请求过于频繁,请稍后再试!` 321 | ); 322 | } else { 323 | console.error(`Error: ${response.status} => 调用失败!`); 324 | } 325 | } catch (error) { 326 | console.error(`Fetch error: ${error.message}`); 327 | } 328 | } 329 | 330 | //--—-----------------服务器启动配置---—-------------- 331 | // 启动服务器 332 | app.listen(PORT, () => { 333 | console.log(`Server is listening on http://${SERVER}:${PORT}`); 334 | }); 335 | -------------------------------------------------------------------------------- /SillyDev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-hello-world", 3 | "version": "1.0.0", 4 | "description": "Express Hello World", 5 | "main": "index.js", 6 | "repository": "", 7 | "author": "sliverkiss", 8 | "license": "AGPL-3.0", 9 | "private": false, 10 | "scripts": { 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "express": "^4.18.2", 15 | "http-proxy-middleware": "^2.0.6", 16 | "node-telegram-bot-api": "^0.65.1", 17 | "request": "^2.88.2" 18 | }, 19 | "engines": { 20 | "node": ">=14" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bark-server/index.js: -------------------------------------------------------------------------------- 1 | const { APNS, Notification, Errors } = require("apns2"); 2 | const Koa = require("koa"); 3 | const Router = require("@koa/router"); 4 | const sqlite3 = require("sqlite3").verbose(); 5 | const bodyParser = require("koa-bodyparser"); 6 | const meta = require("./package.json"); 7 | 8 | /** 9 | * Cert info from: https://github.com/Finb/bark-server/blob/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8 10 | */ 11 | const APN_KEY = "LH4T9V5U4R"; 12 | const TEAM_ID = "5U8LBRXG3A"; 13 | const CERT = ` 14 | -----BEGIN PRIVATE KEY----- 15 | MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4vtC3g5L5HgKGJ2+ 16 | T1eA0tOivREvEAY2g+juRXJkYL2gCgYIKoZIzj0DAQehRANCAASmOs3JkSyoGEWZ 17 | sUGxFs/4pw1rIlSV2IC19M8u3G5kq36upOwyFWj9Gi3Ejc9d3sC7+SHRqXrEAJow 18 | 8/7tRpV+ 19 | -----END PRIVATE KEY----- 20 | `; 21 | 22 | const app = new Koa(); 23 | const router = new Router(); 24 | const db = new sqlite3.Database("devices.db"); 25 | const client = new APNS({ 26 | team: TEAM_ID, 27 | keyId: APN_KEY, 28 | signingKey: CERT, 29 | defaultTopic: "me.fin.bark" 30 | }); 31 | 32 | async function ensureInitDb() { 33 | db.run("CREATE TABLE IF NOT EXISTS token (key TEXT NOT NULL PRIMARY KEY, token TEXT)"); 34 | } 35 | 36 | async function putDeviceToken(key, deviceToken) { 37 | db.run("REPLACE INTO token (key, token) VALUES(?, ?)", key, deviceToken); 38 | } 39 | 40 | async function getDeviceToken(key) { 41 | return new Promise((resolve, reject) => { 42 | db.get("SELECT * FROM token WHERE key = ?", key, (err, row) => { 43 | if (err) { 44 | reject(err); 45 | } else if (row && row.token) { 46 | resolve(row.token); 47 | } else { 48 | resolve(null); 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | async function ping(ctx) { 55 | ctx.body = { 56 | "code": 200, 57 | "data": { 58 | "version": meta.version 59 | }, 60 | "message": "pong" 61 | }; 62 | } 63 | 64 | async function register(ctx) { 65 | let { key, deviceToken } = ctx.query; 66 | if (key && deviceToken) { 67 | await putDeviceToken(key, deviceToken); 68 | ctx.body = { 69 | "code": 200, 70 | "data": { 71 | key, deviceToken 72 | }, 73 | "message": "Registration successful" 74 | }; 75 | } else { 76 | ctx.body = { "code": 400, "message": "Wrong query params" }; 77 | } 78 | } 79 | 80 | /** 81 | * Support both query strings or request body 82 | */ 83 | function getParamCompat(ctx, paramName, fallback) { 84 | if (ctx.query[paramName]) { 85 | return ctx.query[paramName]; 86 | } 87 | if (ctx.request.body && ctx.request.body[paramName]) { 88 | return ctx.request.body[paramName]; 89 | } 90 | return fallback; 91 | } 92 | 93 | async function send(ctx) { 94 | let { key, title, body } = ctx.params; 95 | if (!key) { 96 | ctx.body = { "code": 400, "message": "Wrong path params" }; 97 | return; 98 | } 99 | let deviceToken = await getDeviceToken(key); 100 | if (!deviceToken) { 101 | ctx.body = { "code": 400, "message": `DeviceToken not found by the given key ${key}` }; 102 | return; 103 | } 104 | if (!title) { 105 | title = getParamCompat(ctx, "title"); 106 | } 107 | if (!body) { 108 | body = getParamCompat(ctx, "body"); 109 | } 110 | let url = getParamCompat(ctx, "url"); 111 | let copy = getParamCompat(ctx, "copy"); 112 | let sound = getParamCompat(ctx, "sound", "1107"); 113 | let isArchive = getParamCompat(ctx, "isArchive"); 114 | let automaticallyCopy = getParamCompat(ctx, "automaticallyCopy"); 115 | let category = "myNotificationCategory"; 116 | let payload = { 117 | aps: { 118 | sound, 119 | category, 120 | "mutable-content": 1, 121 | alert: { 122 | title, body 123 | }, 124 | }, 125 | data: { 126 | isArchive, url, copy, automaticallyCopy 127 | }, 128 | }; 129 | let response = await client.send(new Notification(deviceToken, payload)); 130 | console.log(response); 131 | ctx.body = { "code": 200, "message": response }; 132 | } 133 | 134 | router.get("/ping", ping); 135 | router.get("/register", register); 136 | router.get("/:key/:body", send); 137 | router.get("/:key/:title/:body", send); 138 | router.post("/:key/:body", send); 139 | router.post("/:key/:title/:body", send); 140 | 141 | client.on(Errors.error, (err) => { 142 | console.error(JSON.stringify(err)); 143 | }); 144 | 145 | ensureInitDb(); 146 | 147 | app.use(bodyParser()); 148 | app.use(router.routes()); 149 | app.listen(80); 150 | -------------------------------------------------------------------------------- /bark-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.dss886.bark-server", 3 | "version": "1.0.0", 4 | "description": "Node.js implementation of the bark-server", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "node index.js" 8 | }, 9 | "author": "dss886", 10 | "license": "Apache License 2.0", 11 | "dependencies": { 12 | "@koa/router": "^10.0.0", 13 | "apns2": "^9.3.0", 14 | "koa": "^2.13.1", 15 | "koa-bodyparser": "^4.3.0", 16 | "sqlite3": "^5.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ddddocr/README.md: -------------------------------------------------------------------------------- 1 | # ocr_api_server 2 | 使用ddddocr的最简api搭建项目 3 | **建议python版本3.7-3.9 64位** 4 | -------------------------------------------------------------------------------- /ddddocr/main.py: -------------------------------------------------------------------------------- 1 | # encoding=utf-8 2 | import argparse 3 | import base64 4 | import json 5 | 6 | import ddddocr 7 | from flask import Flask, request 8 | 9 | parser = argparse.ArgumentParser(description="使用ddddocr搭建的最简api服务") 10 | parser.add_argument("-p", "--port", type=int, default=6147) 11 | parser.add_argument("--ocr", action="store_true", help="开启ocr识别") 12 | parser.add_argument("--old", action="store_true", help="OCR是否启动旧模型") 13 | parser.add_argument("--det", action="store_true", help="开启目标检测") 14 | 15 | args = parser.parse_args() 16 | 17 | app = Flask(__name__) 18 | 19 | 20 | class Server(object): 21 | def __init__(self, ocr=True, det=False, old=False): 22 | self.ocr_option = ocr 23 | self.det_option = det 24 | self.old_option = old 25 | self.ocr = None 26 | self.det = None 27 | if self.ocr_option: 28 | print("ocr模块开启") 29 | if self.old_option: 30 | print("使用OCR旧模型启动") 31 | self.ocr = ddddocr.DdddOcr(old=True) 32 | else: 33 | print("使用OCR新模型启动,如需要使用旧模型,请额外添加参数 --old开启") 34 | self.ocr = ddddocr.DdddOcr() 35 | else: 36 | print("ocr模块未开启,如需要使用,请使用参数 --ocr开启") 37 | if self.det_option: 38 | print("目标检测模块开启") 39 | self.det = ddddocr.DdddOcr(det=True) 40 | else: 41 | print("目标检测模块未开启,如需要使用,请使用参数 --det开启") 42 | 43 | def classification(self, img: bytes): 44 | if self.ocr_option: 45 | return self.ocr.classification(img) 46 | else: 47 | raise Exception("ocr模块未开启") 48 | 49 | def detection(self, img: bytes): 50 | if self.det_option: 51 | return self.det.detection(img) 52 | else: 53 | raise Exception("目标检测模块模块未开启") 54 | 55 | def slide(self, target_img: bytes, bg_img: bytes, algo_type: str): 56 | dddd = self.ocr or self.det or ddddocr.DdddOcr(ocr=False) 57 | if algo_type == 'match': 58 | return dddd.slide_match(target_img, bg_img) 59 | elif algo_type == 'compare': 60 | return dddd.slide_comparison(target_img, bg_img) 61 | else: 62 | raise Exception(f"不支持的滑块算法类型: {algo_type}") 63 | 64 | server = Server(ocr=args.ocr, det=args.det, old=args.old) 65 | 66 | 67 | def get_img(request, img_type='file', img_name='image'): 68 | if img_type == 'b64': 69 | img = base64.b64decode(request.get_data()) # 70 | try: # json str of multiple images 71 | dic = json.loads(img) 72 | img = base64.b64decode(dic.get(img_name).encode()) 73 | except Exception as e: # just base64 of single image 74 | pass 75 | else: 76 | img = request.files.get(img_name).read() 77 | return img 78 | 79 | 80 | def set_ret(result, ret_type='text'): 81 | if ret_type == 'json': 82 | if isinstance(result, Exception): 83 | return json.dumps({"status": 200, "result": "", "msg": str(result)}) 84 | else: 85 | return json.dumps({"status": 200, "result": result, "msg": ""}) 86 | # return json.dumps({"succ": isinstance(result, str), "result": str(result)}) 87 | else: 88 | if isinstance(result, Exception): 89 | return '' 90 | else: 91 | return str(result).strip() 92 | 93 | 94 | @app.route('//', methods=['POST']) 95 | @app.route('///', methods=['POST']) 96 | def ocr(opt, img_type='file', ret_type='text'): 97 | try: 98 | img = get_img(request, img_type) 99 | if opt == 'ocr': 100 | result = server.classification(img) 101 | elif opt == 'det': 102 | result = server.detection(img) 103 | else: 104 | raise f" is invalid" 105 | return set_ret(result, ret_type) 106 | except Exception as e: 107 | return set_ret(e, ret_type) 108 | 109 | @app.route('/slide//', methods=['POST']) 110 | @app.route('/slide///', methods=['POST']) 111 | def slide(algo_type='compare', img_type='file', ret_type='text'): 112 | try: 113 | target_img = get_img(request, img_type, 'target_img') 114 | bg_img = get_img(request, img_type, 'bg_img') 115 | result = server.slide(target_img, bg_img, algo_type) 116 | return set_ret(result, ret_type) 117 | except Exception as e: 118 | return set_ret(e, ret_type) 119 | 120 | @app.route('/ping', methods=['GET']) 121 | def ping(): 122 | return "pong" 123 | 124 | 125 | if __name__ == '__main__': 126 | app.run(host="0.0.0.0", port=args.port) 127 | -------------------------------------------------------------------------------- /ddddocr/requirements.txt: -------------------------------------------------------------------------------- 1 | ddddocr>=1.3.1 2 | flask 3 | --------------------------------------------------------------------------------