feat: 完成了基于 FastAPI 的重制,重新写了 web 页面
This commit is contained in:
commit
a6efc64896
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
venv
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# MobaGenkey
|
||||
|
||||
你应该知道这是干啥的
|
||||
|
||||
## 开始使用
|
||||
|
||||
先安装环境
|
||||
|
||||
```shell
|
||||
$ pip install -r requirements.txt
|
||||
```
|
||||
|
||||
然后直接运行
|
||||
|
||||
```shell
|
||||
$ python app.py
|
||||
```
|
||||
|
||||
打开浏览器访问控制台展示的地址(例如[http://127.0.0.1:8000](http://127.0.0.1:8000)),按照网页的指示自己玩
|
155
app.py
Normal file
155
app.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.responses import FileResponse, StreamingResponse, HTMLResponse
|
||||
import os
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
VariantBase64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||||
VariantBase64Dict = {i: VariantBase64Table[i] for i in range(len(VariantBase64Table))}
|
||||
VariantBase64ReverseDict = {
|
||||
VariantBase64Table[i]: i for i in range(len(VariantBase64Table))
|
||||
}
|
||||
|
||||
|
||||
def VariantBase64Encode(bs: bytes):
|
||||
result = b""
|
||||
blocks_count, left_bytes = divmod(len(bs), 3)
|
||||
|
||||
for i in range(blocks_count):
|
||||
coding_int = int.from_bytes(bs[3 * i : 3 * i + 3], "little")
|
||||
block = VariantBase64Dict[coding_int & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 6) & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 12) & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 18) & 0x3F]
|
||||
result += block.encode()
|
||||
|
||||
if left_bytes == 0:
|
||||
return result
|
||||
elif left_bytes == 1:
|
||||
coding_int = int.from_bytes(bs[3 * blocks_count :], "little")
|
||||
block = VariantBase64Dict[coding_int & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 6) & 0x3F]
|
||||
result += block.encode()
|
||||
return result
|
||||
else:
|
||||
coding_int = int.from_bytes(bs[3 * blocks_count :], "little")
|
||||
block = VariantBase64Dict[coding_int & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 6) & 0x3F]
|
||||
block += VariantBase64Dict[(coding_int >> 12) & 0x3F]
|
||||
result += block.encode()
|
||||
return result
|
||||
|
||||
|
||||
def VariantBase64Decode(s: str):
|
||||
result = b""
|
||||
blocks_count, left_bytes = divmod(len(s), 4)
|
||||
|
||||
for i in range(blocks_count):
|
||||
block = VariantBase64ReverseDict[s[4 * i]]
|
||||
block += VariantBase64ReverseDict[s[4 * i + 1]] << 6
|
||||
block += VariantBase64ReverseDict[s[4 * i + 2]] << 12
|
||||
block += VariantBase64ReverseDict[s[4 * i + 3]] << 18
|
||||
result += block.to_bytes(3, "little")
|
||||
|
||||
if left_bytes == 0:
|
||||
return result
|
||||
elif left_bytes == 2:
|
||||
block = VariantBase64ReverseDict[s[4 * blocks_count]]
|
||||
block += VariantBase64ReverseDict[s[4 * blocks_count + 1]] << 6
|
||||
result += block.to_bytes(1, "little")
|
||||
return result
|
||||
elif left_bytes == 3:
|
||||
block = VariantBase64ReverseDict[s[4 * blocks_count]]
|
||||
block += VariantBase64ReverseDict[s[4 * blocks_count + 1]] << 6
|
||||
block += VariantBase64ReverseDict[s[4 * blocks_count + 2]] << 12
|
||||
result += block.to_bytes(2, "little")
|
||||
return result
|
||||
else:
|
||||
raise ValueError("Invalid encoding.")
|
||||
|
||||
|
||||
def EncryptBytes(key: int, bs: bytes):
|
||||
result = bytearray()
|
||||
for i in range(len(bs)):
|
||||
result.append(bs[i] ^ ((key >> 8) & 0xFF))
|
||||
key = result[-1] & key | 0x482D
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def DecryptBytes(key: int, bs: bytes):
|
||||
result = bytearray()
|
||||
for i in range(len(bs)):
|
||||
result.append(bs[i] ^ ((key >> 8) & 0xFF))
|
||||
key = bs[i] & key | 0x482D
|
||||
return bytes(result)
|
||||
|
||||
|
||||
class LicenseType:
|
||||
Professional = 1
|
||||
Educational = 3
|
||||
Persional = 4
|
||||
|
||||
|
||||
class LicenseType:
|
||||
Professional = 1
|
||||
Educational = 3
|
||||
Persional = 4
|
||||
|
||||
|
||||
def generate_license_bytes(
|
||||
Type: LicenseType, Count: int, UserName: str, MajorVersion: int, MinorVersion: int
|
||||
):
|
||||
assert Count >= 0
|
||||
LicenseString = f"{Type}#{UserName}|{MajorVersion}{MinorVersion}#{Count}#{MajorVersion}3{MinorVersion}6{MinorVersion}#0#0#0#"
|
||||
EncodedLicenseString = VariantBase64Encode(
|
||||
EncryptBytes(0x787, LicenseString.encode())
|
||||
).decode()
|
||||
FileName = EncodedLicenseString.replace("/", "").replace("\\", "")
|
||||
|
||||
# 使用内存文件
|
||||
zip_buffer = BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
||||
zip_file.writestr("Pro.key", EncodedLicenseString)
|
||||
|
||||
zip_buffer.seek(0)
|
||||
return (FileName, zip_buffer)
|
||||
|
||||
|
||||
@app.get("/api/generate")
|
||||
async def generate_license(
|
||||
name: str = Query(..., min_length=1),
|
||||
ver: str = Query(..., regex=r"^\d+\.\d+$"),
|
||||
count: int = Query(1, ge=1),
|
||||
):
|
||||
try:
|
||||
MajorVersion, MinorVersion = map(int, ver.split(".")[:2])
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid version format")
|
||||
|
||||
try:
|
||||
filename, file_data = generate_license_bytes(
|
||||
LicenseType.Professional, count, name, MajorVersion, MinorVersion
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
return StreamingResponse(
|
||||
content=file_data,
|
||||
media_type="application/zip",
|
||||
headers={
|
||||
"Content-Disposition": f"attachment; filename=Custom.mxtpro",
|
||||
"Content-Type": "application/zip",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index():
|
||||
return FileResponse("templates/index.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
uvicorn
|
||||
fastapi
|
155
templates/index.html
Normal file
155
templates/index.html
Normal file
|
@ -0,0 +1,155 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>License Generator</title>
|
||||
<script src="https://unpkg.com/sober@latest/dist/sober.min.js"></script>
|
||||
<style>
|
||||
s-page {
|
||||
--s-color-primary: #5b53a8;
|
||||
--s-color-on-primary: #ffffff;
|
||||
--s-color-primary-container: #e4dfff;
|
||||
--s-color-on-primary-container: #150362;
|
||||
--s-color-secondary: #5e5c71;
|
||||
--s-color-on-secondary: #ffffff;
|
||||
--s-color-secondary-container: #e4dff9;
|
||||
--s-color-on-secondary-container: #1b192c;
|
||||
--s-color-tertiary: #7b5266;
|
||||
--s-color-on-tertiary: #ffffff;
|
||||
--s-color-tertiary-container: #ffd8e8;
|
||||
--s-color-on-tertiary-container: #2f1122;
|
||||
--s-color-error: #ba1a1a;
|
||||
--s-color-on-error: #ffffff;
|
||||
--s-color-error-container: #ffdad6;
|
||||
--s-color-on-error-container: #410002;
|
||||
--s-color-background: #fffbff;
|
||||
--s-color-on-background: #1c1b1f;
|
||||
--s-color-outline: #787680;
|
||||
--s-color-outline-variant: #c8c5d0;
|
||||
--s-color-surface: #fffbff;
|
||||
--s-color-on-surface: #1c1b1f;
|
||||
--s-color-surface-variant: #e5e1ec;
|
||||
--s-color-on-surface-variant: #47464f;
|
||||
--s-color-inverse-surface: #313034;
|
||||
--s-color-inverse-on-surface: #f4eff4;
|
||||
--s-color-inverse-primary: #c6c0ff;
|
||||
--s-color-surface-container: #f1ecf1;
|
||||
--s-color-surface-container-lowest: #e5e1e6;
|
||||
--s-color-surface-container-low: #ebe7ec;
|
||||
--s-color-surface-container-high: #f7f2f7;
|
||||
--s-color-surface-container-highest: #ffffff;
|
||||
--s-color-dark-primary: #c6c0ff;
|
||||
--s-color-dark-on-primary: #2c2276;
|
||||
--s-color-dark-primary-container: #433b8e;
|
||||
--s-color-dark-on-primary-container: #e4dfff;
|
||||
--s-color-dark-secondary: #c8c3dc;
|
||||
--s-color-dark-on-secondary: #302e41;
|
||||
--s-color-dark-secondary-container: #474459;
|
||||
--s-color-dark-on-secondary-container: #e4dff9;
|
||||
--s-color-dark-tertiary: #ebb8cf;
|
||||
--s-color-dark-on-tertiary: #482537;
|
||||
--s-color-dark-tertiary-container: #613b4e;
|
||||
--s-color-dark-on-tertiary-container: #ffd8e8;
|
||||
--s-color-dark-error: #ffb4ab;
|
||||
--s-color-dark-on-error: #690005;
|
||||
--s-color-dark-error-container: #93000a;
|
||||
--s-color-dark-on-error-container: #ffb4ab;
|
||||
--s-color-dark-background: #1c1b1f;
|
||||
--s-color-dark-on-background: #e5e1e6;
|
||||
--s-color-dark-outline: #928f99;
|
||||
--s-color-dark-outline-variant: #47464f;
|
||||
--s-color-dark-surface: #1c1b1f;
|
||||
--s-color-dark-on-surface: #e5e1e6;
|
||||
--s-color-dark-surface-variant: #47464f;
|
||||
--s-color-dark-on-surface-variant: #c8c5d0;
|
||||
--s-color-dark-inverse-surface: #e5e1e6;
|
||||
--s-color-dark-inverse-on-surface: #313034;
|
||||
--s-color-dark-inverse-primary: #5b53a8;
|
||||
--s-color-dark-surface-container: #201f23;
|
||||
--s-color-dark-surface-container-lowest: #0e0e11;
|
||||
--s-color-dark-surface-container-low: #1c1b1f;
|
||||
--s-color-dark-surface-container-high: #2a292d;
|
||||
--s-color-dark-surface-container-highest: #353438;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<s-page theme="auto">
|
||||
<!--抽屉布局-->
|
||||
<s-drawer>
|
||||
<!--应用栏-->
|
||||
<s-appbar>
|
||||
<!--应用栏-标题-->
|
||||
<div slot="headline"> MobaXterm Key Generator </div>
|
||||
</s-appbar>
|
||||
<!--主视图-->
|
||||
<s-scroll-view style="flex-grow: 1">
|
||||
<main style="height: 100%;display: flex;">
|
||||
<s-card
|
||||
style="margin-left: 15%; max-width: 70%; width: 100%; padding: 16px; margin-top: 16px; margin-bottom: 16px; ">
|
||||
<div slot="headline">欢迎使用许可证生成器</div>
|
||||
<div slot="text">
|
||||
<p>请填写以下信息以生成许可证。</p>
|
||||
<p>注意:生成的许可证仅供学习和测试使用,请勿用于商业目的。</p>
|
||||
<a href="https://github.com/MeowCracker/MobaGenkey">https://github.com/MeowCracker/MobaGenkey</a>
|
||||
<s-text-field label="授权用户" id="licenseUser" style="width: 100%; margin-top: 16px;">
|
||||
</s-text-field>
|
||||
<s-text-field label="授权版本 (如 25.1)" id="licenseVersion"
|
||||
style="width: 100%; margin-top: 16px;"></s-text-field>
|
||||
<s-text-field label="授权用户数量(默认为 1)" id="licenseUserCount" type="number"
|
||||
style="width: 100%; margin-top: 16px;"></s-text-field>
|
||||
|
||||
<div align="right" style="margin-top: 16px;">
|
||||
<s-button id="generateBtn">生成并保存</s-button>
|
||||
</div>
|
||||
</div>
|
||||
</s-card>
|
||||
</main>
|
||||
</s-scroll-view>
|
||||
</s-drawer>
|
||||
</s-page>
|
||||
<script>
|
||||
document.getElementById("generateBtn").addEventListener("click", async () => {
|
||||
const licenseUser = document.getElementById("licenseUser").value.trim();
|
||||
const licenseVersion = document.getElementById("licenseVersion").value.trim();
|
||||
const licenseUserCount = document.getElementById("licenseUserCount").value.trim() || 1;
|
||||
|
||||
if(!licenseUser || !licenseVersion || !licenseUserCount) {
|
||||
alert("你需要正确填写授权用户和授权版本!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/generate?name=${encodeURIComponent(licenseUser)}&ver=${encodeURIComponent(licenseVersion)}&count=${encodeURIComponent(licenseUserCount)}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
alert(`Error: ${error.detail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "Custom.mxtpro";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Error generating license:", error);
|
||||
alert("在生成许可证的时候出现了问题,请检查网络连接或者查看日志!");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user