diff --git a/Server/Source/Process.ts b/Server/Source/Process.ts index e666c2a..dd7fefa 100644 --- a/Server/Source/Process.ts +++ b/Server/Source/Process.ts @@ -1,9 +1,10 @@ import { Result, ThrowErrorIfFailed } from "./Result"; import { Database } from "./Database"; import { Output } from "./Output"; -import { CaptchaSecretKey } from "./Secret"; +import { CaptchaSecretKey, GithubImagePAT } from "./Secret"; import { CheerioAPI, load } from "cheerio"; import CryptoJS from "crypto-js"; +import md5 from "crypto-js/md5"; export class Process { private AdminUserList: Array = ["chenlangning", "zhuchenrui2", "shanwenxiao"]; @@ -1017,6 +1018,68 @@ export class Process { return new Result(true, "获得板块列表成功", { "Boards": Boards }); + }, + UploadImage: async (Data: object): Promise => { + const GithubImageRepo = "langningchen/XMOJ-Script-Pictures"; + ThrowErrorIfFailed(this.CheckParams(Data, { + "Image": "string" + })); + let Image: String = Data["Image"]; + let ImageID: String = ""; + for (let i = 0; i < 32; i++) { + ImageID += String.fromCharCode(Math.floor(Math.random() * 26) + 97); + } + let ImageData = Image.replace(/^data:image\/\w+;base64,/, ""); + await fetch(new URL("https://api.github.com/repos/" + GithubImageRepo + "/contents/" + ImageID), { + method: "PUT", + headers: { + "Authorization": "Bearer " + GithubImagePAT, + "Content-Type": "application/json", + "User-Agent": "XMOJ-Script-Server" + }, + body: JSON.stringify({ + message: `${this.Username} ${new Date().getFullYear()}/${new Date().getMonth() + 1}/${new Date().getDate()} ${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}`, + content: ImageData + }) + }).then((Response) => { + return Response.json(); + }).then((Response) => { + if (Response["content"]["name"] !== ImageID) { + Output.Error("Upload image failed\n" + + "Username: \"" + this.Username + "\"\n" + + "ImageID : \"" + ImageID + "\"\n" + + "Response: \"" + JSON.stringify(Response) + "\"\n"); + ThrowErrorIfFailed(new Result(false, "上传图片失败")); + } + }).catch((Error) => { + Output.Error("Upload image failed: " + Error + "\n" + + "Username: \"" + this.Username + "\"\n" + + "ImageID : \"" + ImageID + "\"\n"); + ThrowErrorIfFailed(new Result(false, "上传图片失败")); + }); + return new Result(true, "上传图片成功", { + ImageID: ImageID + }); + }, + GetImage: async (Data: object): Promise => { + const GithubImageRepo = "langningchen/XMOJ-Script-Pictures"; + ThrowErrorIfFailed(this.CheckParams(Data, { + "ImageID": "string" + })); + return await fetch(new URL("https://api.github.com/repos/" + GithubImageRepo + "/contents/" + Data["ImageID"] + "?1=1"), { + method: "GET", + headers: { + "Authorization": "Bearer " + GithubImagePAT, + "Accept": "application/vnd.github.v3.raw", + "User-Agent": "XMOJ-Script-Server" + } + }).then((Response) => { + return Response.blob(); + }).catch((Error) => { + Output.Error("Get image failed: " + Error + "\n" + + "ImageID : \"" + Data["ImageID"] + "\"\n"); + return new Blob(); + }); } }; constructor(RequestData: Request, Environment) { @@ -1024,7 +1087,7 @@ export class Process { this.RequestData = RequestData; this.RemoteIP = RequestData.headers.get("CF-Connecting-IP") || ""; } - public async Process(): Promise { + public async Process(): Promise { try { let PathName = new URL(this.RequestData.url).pathname; PathName = PathName === "/" ? "/index" : PathName; @@ -1032,6 +1095,15 @@ export class Process { if (this.ProcessFunctions[PathName] === undefined) { throw new Result(false, "访问的页面不存在"); } + if (this.RequestData.method === "GET" && PathName === "GetImage") { + return new Response(await this.ProcessFunctions[PathName]({ + ImageID: new URL(this.RequestData.url).searchParams.get("ImageID") + }), { + headers: { + "content-type": "image/png" + } + }); + } if (this.RequestData.method !== "POST") { throw new Result(false, "不允许此请求方式"); } @@ -1067,7 +1139,11 @@ export class Process { Output.Error(ResponseData); ResponseData = new Result(false, "服务器运行错误:" + String(ResponseData).split("\n")[0]); } - return ResponseData; + return new Response(JSON.stringify(ResponseData), { + headers: { + "content-type": "application/json;charset=UTF-8" + } + }); } } } diff --git a/Server/Source/index.ts b/Server/Source/index.ts index 0983476..737c90d 100644 --- a/Server/Source/index.ts +++ b/Server/Source/index.ts @@ -4,11 +4,7 @@ import { Database } from "./Database"; export default { async fetch(RequestData: Request, Environment, Context) { let Processor = new Process(RequestData, Environment); - return new Response(JSON.stringify(await Processor.Process()), { - headers: { - "content-type": "application/json;charset=UTF-8" - } - }); + return await Processor.Process(); }, async scheduled(Event, Environment, Context) { let XMOJDatabase = new Database(Environment.DB); diff --git a/Update.json b/Update.json index 0e37846..a718806 100644 --- a/Update.json +++ b/Update.json @@ -705,6 +705,16 @@ "Description": "修复关闭时左上角仍显示为高老师的OJ" } ] + }, + "0.3.192": { + "UpdateDate": 1696222135180, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 583, + "Description": "自制图床,讨论区 Ctrl+V 上传图片" + } + ] } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 042d285..3d9e8f9 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 0.3.191 +// @version 0.3.192 // @description XMOJ增强脚本 // @author @langningchen // @namespace https://github/langningchen @@ -47,17 +47,22 @@ let GetRelativeTime = (Input) => { else { RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 365) + "年前"; } return "" + RelativeName + ""; }; -let RenderMathJax = () => { - var ScriptElement = document.createElement("script"); - ScriptElement.id = "MathJax-script"; - ScriptElement.type = "text/javascript"; - ScriptElement.src = "https://cdn.bootcdn.net/ajax/libs/mathjax/3.0.5/es5/tex-chtml.js"; - document.body.appendChild(ScriptElement); - ScriptElement.onload = () => { - MathJax.startup.input[0].findTeX.options.inlineMath.push(["$", "$"]); - MathJax.startup.input[0].findTeX.getPatterns(); - MathJax.typeset(); - }; +let RenderMathJax = async () => { + if (document.getElementById("MathJax-script") === null) { + var ScriptElement = document.createElement("script"); + ScriptElement.id = "MathJax-script"; + ScriptElement.type = "text/javascript"; + ScriptElement.src = "https://cdn.bootcdn.net/ajax/libs/mathjax/3.0.5/es5/tex-chtml.js"; + document.body.appendChild(ScriptElement); + await new Promise((Resolve) => { + ScriptElement.onload = () => { + Resolve(); + }; + }); + } + MathJax.startup.input[0].findTeX.options.inlineMath.push(["$", "$"]); + MathJax.startup.input[0].findTeX.getPatterns(); + MathJax.typeset(); }; let GetUserInfo = async (Username) => { if (localStorage.getItem("UserScript-User-" + Username + "-UserRating") != null && @@ -3697,14 +3702,33 @@ int main() } }); ContentElement.addEventListener("input", () => { + ContentElement.classList.remove("is-invalid"); PreviewTab.innerHTML = DOMPurify.sanitize(marked.parse(ContentElement.value)); RenderMathJax(); }); TitleElement.addEventListener("input", () => { TitleElement.classList.remove("is-invalid"); }); - ContentElement.addEventListener("input", () => { - ContentElement.classList.remove("is-invalid"); + ContentElement.addEventListener("paste", (Event) => { + let Items = Event.clipboardData.items; + if (Items.length !== 0) { + for (let i = 0; i < Items.length; i++) { + if (Items[i].type.indexOf("image") != -1) { + let Reader = new FileReader(); + Reader.readAsDataURL(Items[i].getAsFile()); + Reader.onload = () => { + RequestAPI("UploadImage", { + "Image": Reader.result + }, (ResponseData) => { + if (ResponseData.Success) { + ContentElement.value += `![](https://api.xmoj-bbs.tech/GetImage?ImageID=${ResponseData.Data.ImageID})`; + ContentElement.dispatchEvent(new Event("input")); + } + }); + }; + } + } + } }); SubmitElement.addEventListener("click", async () => { ErrorElement.style.display = "none"; @@ -3853,6 +3877,27 @@ int main() PreviewTab.innerHTML = DOMPurify.sanitize(marked.parse(ContentElement.value)); RenderMathJax(); }); + ContentElement.addEventListener("paste", (Event) => { + let Items = Event.clipboardData.items; + if (Items.length !== 0) { + for (let i = 0; i < Items.length; i++) { + if (Items[i].type.indexOf("image") != -1) { + let Reader = new FileReader(); + Reader.readAsDataURL(Items[i].getAsFile()); + Reader.onload = () => { + RequestAPI("UploadImage", { + "Image": Reader.result + }, (ResponseData) => { + if (ResponseData.Success) { + ContentElement.value += `![](https://api.xmoj-bbs.tech/GetImage?ImageID=${ResponseData.Data.ImageID})`; + ContentElement.dispatchEvent(new Event("input")); + } + }); + }; + } + } + } + }); let RefreshReply = (Silent = true) => { if (!Silent) { PostTitle.innerHTML = ``; @@ -4057,6 +4102,27 @@ int main() PreviewTab.innerHTML = DOMPurify.sanitize(marked.parse(ContentEditor.value)); RenderMathJax(); }); + ContentEditor.addEventListener("paste", (Event) => { + let Items = Event.clipboardData.items; + if (Items.length !== 0) { + for (let i = 0; i < Items.length; i++) { + if (Items[i].type.indexOf("image") != -1) { + let Reader = new FileReader(); + Reader.readAsDataURL(Items[i].getAsFile()); + Reader.onload = () => { + RequestAPI("UploadImage", { + "Image": Reader.result + }, (ResponseData) => { + if (ResponseData.Success) { + ContentEditor.value += `![](https://api.xmoj-bbs.tech/GetImage?ImageID=${ResponseData.Data.ImageID})`; + ContentEditor.dispatchEvent(new Event("input")); + } + }); + }; + } + } + } + }); } let UsernameElements = document.getElementsByClassName("Usernames");