mirror of
https://github.com/PaiGramTeam/Gift-Code-Web.git
synced 2024-11-24 00:21:36 +00:00
Support show gift code
This commit is contained in:
parent
01fb1f9e16
commit
d034fe0a72
2
.gitignore
vendored
2
.gitignore
vendored
@ -157,4 +157,4 @@ cython_debug/
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
.idea/
|
||||
|
55
add.py
Normal file
55
add.py
Normal file
@ -0,0 +1,55 @@
|
||||
from pathlib import Path
|
||||
from sys import argv
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from models.code import CodeList, Code, Reward
|
||||
|
||||
data_path = Path("data")
|
||||
custom_path = data_path / "custom.json"
|
||||
|
||||
|
||||
def add(code: str, expire_str: str, rewards_str: List[str]) -> Code:
|
||||
expire = datetime.strptime(expire_str, "%Y-%m-%d")
|
||||
expire = expire.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||
rewards = []
|
||||
for reward_str in rewards_str:
|
||||
reward_list = reward_str.split(":")
|
||||
rewards.append(
|
||||
Reward(
|
||||
name=reward_list[0],
|
||||
cnt=int(reward_list[1])
|
||||
)
|
||||
)
|
||||
return Code(
|
||||
code=code,
|
||||
expire=int(expire.timestamp() * 1000),
|
||||
reward=rewards,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
add_type = argv[1]
|
||||
if add_type not in ["main", "over"]:
|
||||
raise IndexError
|
||||
code = add(argv[2], argv[3], argv[4:])
|
||||
with open(custom_path, "r", encoding="utf-8") as f:
|
||||
custom: CodeList = CodeList.parse_raw(f.read())
|
||||
if add_type == "main":
|
||||
main_codes = [i.code for i in custom.main]
|
||||
if code.code in main_codes:
|
||||
raise ValueError("Duplicate code")
|
||||
custom.main.append(code)
|
||||
else:
|
||||
over_codes = [i.code for i in custom.over]
|
||||
if code.code in over_codes:
|
||||
raise ValueError("Duplicate code")
|
||||
custom.over.append(code)
|
||||
custom.main.sort(key=lambda x: x.expire, reverse=True)
|
||||
custom.over.sort(key=lambda x: x.expire, reverse=True)
|
||||
with open(custom_path, "w", encoding="utf-8") as f:
|
||||
f.write(custom.json(indent=4, ensure_ascii=False))
|
||||
except IndexError:
|
||||
print("Usage: python add.py [main/over] [code] [expire] [rewards...]")
|
||||
print("Example: python add.py main code 2023-11-1 星琼:1 信用点:1000")
|
189
data/code.html
Normal file
189
data/code.html
Normal file
@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dark Responsive Page with Tabs</title>
|
||||
|
||||
<!-- 新 Bootstrap5 核心 CSS 文件 -->
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
|
||||
|
||||
<!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
|
||||
<script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- custom styles -->
|
||||
<style>
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #eee;
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #333;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
color: #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>《崩坏:星穹铁道》兑换码</h1>
|
||||
</header>
|
||||
<div style="height: 30px;"></div>
|
||||
<main class="container">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#tab1">国服(官服)</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#tab2">国际服</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tab1">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<h2 class="table-title">未到期</h2>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>兑换码</th>
|
||||
<th>奖励</th>
|
||||
<th>到期时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="main-true">
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 class="table-title">已到期</h2>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>兑换码</th>
|
||||
<th>奖励</th>
|
||||
<th>到期时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="main-false">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab2">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<h2 class="table-title">未到期</h2>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>兑换码</th>
|
||||
<th>奖励</th>
|
||||
<th>到期时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="over-true">
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 class="table-title">已到期</h2>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>兑换码</th>
|
||||
<th>奖励</th>
|
||||
<th>到期时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="over-false">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div style="height: 100px;"></div>
|
||||
|
||||
<footer>
|
||||
<nav class="navbar">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">PaiGramTeam</a>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function addRow(tableId, data) {
|
||||
let now = new Date();
|
||||
let expire_time = new Date(data.expire);
|
||||
if (now > expire_time) {
|
||||
tableId += "-false";
|
||||
} else {
|
||||
tableId += "-true";
|
||||
}
|
||||
let table = document.getElementById(tableId);
|
||||
let new_tr = document.createElement("tr");
|
||||
let code = document.createElement("td");
|
||||
code.innerHTML = data.code;
|
||||
let reward = document.createElement("td");
|
||||
data.reward.forEach(function (item) {
|
||||
let p = document.createElement("p");
|
||||
p.innerHTML = item.name + " x" + item.cnt;
|
||||
reward.appendChild(p);
|
||||
});
|
||||
let expire = document.createElement("td");
|
||||
expire.innerHTML = expire_time.toLocaleString();
|
||||
new_tr.appendChild(code);
|
||||
new_tr.appendChild(reward);
|
||||
new_tr.appendChild(expire);
|
||||
table.appendChild(new_tr);
|
||||
}
|
||||
|
||||
fetch('code.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
for (let key in data) {
|
||||
for (let value in data[key]) {
|
||||
addRow(key, data[key][value]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
214
data/code.json
Normal file
214
data/code.json
Normal file
@ -0,0 +1,214 @@
|
||||
{
|
||||
"main": [
|
||||
{
|
||||
"code": "MIYOUSHE2023",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 60
|
||||
}
|
||||
],
|
||||
"expire": 1686402166000
|
||||
}
|
||||
],
|
||||
"over": [
|
||||
{
|
||||
"code": "BSN2EWMHA4RP",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 10000
|
||||
}
|
||||
],
|
||||
"expire": 4102415999999
|
||||
},
|
||||
{
|
||||
"code": "HSRVER10JYTGHC",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 10000
|
||||
}
|
||||
],
|
||||
"expire": 4102415999999
|
||||
},
|
||||
{
|
||||
"code": "STARRAILGIFT",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 50
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 10000
|
||||
}
|
||||
],
|
||||
"expire": 4102415999999
|
||||
},
|
||||
{
|
||||
"code": "SURPRISE1024",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "冒险记录",
|
||||
"cnt": 3
|
||||
},
|
||||
{
|
||||
"name": "疾速粉尘",
|
||||
"cnt": 2
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 5000
|
||||
}
|
||||
],
|
||||
"expire": 1686070799999
|
||||
},
|
||||
{
|
||||
"code": "ZTPTNMTX8LUF",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 50000
|
||||
}
|
||||
],
|
||||
"expire": 1685206799999
|
||||
},
|
||||
{
|
||||
"code": "8A6T6LBFQ4D3",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "漫游指南",
|
||||
"cnt": 5
|
||||
}
|
||||
],
|
||||
"expire": 1685206799999
|
||||
},
|
||||
{
|
||||
"code": "DB7A64BW8LC7",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "提纯以太",
|
||||
"cnt": 4
|
||||
}
|
||||
],
|
||||
"expire": 1685206799999
|
||||
},
|
||||
{
|
||||
"code": "CS75WMP976AK",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
}
|
||||
],
|
||||
"expire": 1685206799999
|
||||
},
|
||||
{
|
||||
"code": "HSRVER10XEDLFE",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 50
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 10000
|
||||
},
|
||||
{
|
||||
"name": "漫游指南",
|
||||
"cnt": 2
|
||||
}
|
||||
],
|
||||
"expire": 1683565199999
|
||||
},
|
||||
{
|
||||
"code": "2T7BP4JVEBT7",
|
||||
"reward": [
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 5000
|
||||
},
|
||||
{
|
||||
"name": "冒险记录",
|
||||
"cnt": 3
|
||||
},
|
||||
{
|
||||
"name": "凝缩以太",
|
||||
"cnt": 2
|
||||
},
|
||||
{
|
||||
"name": "大宇宙炒饭",
|
||||
"cnt": 3
|
||||
}
|
||||
],
|
||||
"expire": 1683392399999
|
||||
},
|
||||
{
|
||||
"code": "HSRGRANDOPEN1",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "信用点",
|
||||
"cnt": 5000
|
||||
}
|
||||
],
|
||||
"expire": 1682787599999
|
||||
},
|
||||
{
|
||||
"code": "HSRGRANDOPEN2",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "漫游指南",
|
||||
"cnt": 5
|
||||
}
|
||||
],
|
||||
"expire": 1682787599999
|
||||
},
|
||||
{
|
||||
"code": "HSRGRANDOPEN3",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 100
|
||||
},
|
||||
{
|
||||
"name": "提纯以太",
|
||||
"cnt": 4
|
||||
}
|
||||
],
|
||||
"expire": 1682787599999
|
||||
}
|
||||
]
|
||||
}
|
15
data/custom.json
Normal file
15
data/custom.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"main": [
|
||||
{
|
||||
"code": "MIYOUSHE2023",
|
||||
"reward": [
|
||||
{
|
||||
"name": "星琼",
|
||||
"cnt": 60
|
||||
}
|
||||
],
|
||||
"expire": 1689695999999
|
||||
}
|
||||
],
|
||||
"over": []
|
||||
}
|
33
main.py
Normal file
33
main.py
Normal file
@ -0,0 +1,33 @@
|
||||
from typing import List
|
||||
|
||||
from models.code import CodeList, Code
|
||||
from models.honkai import get_code
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
data_path = Path("data")
|
||||
custom_path = data_path / "custom.json"
|
||||
code_path = data_path / "code.json"
|
||||
|
||||
|
||||
def merge_code(over: List[Code], custom: CodeList) -> CodeList:
|
||||
over_codes = [i for i in over]
|
||||
custom_over_codes = [i.code for i in custom.over]
|
||||
for code in over_codes:
|
||||
if code.code in custom_over_codes:
|
||||
continue
|
||||
custom.over.append(code)
|
||||
return custom
|
||||
|
||||
|
||||
def main():
|
||||
over = get_code()
|
||||
with open(custom_path, "r", encoding="utf-8") as f:
|
||||
custom = CodeList.parse_raw(f.read())
|
||||
custom = merge_code(over, custom)
|
||||
with open(code_path, "w", encoding="utf-8") as f:
|
||||
f.write(custom.json(indent=4, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
models/__init__.py
Normal file
0
models/__init__.py
Normal file
19
models/code.py
Normal file
19
models/code.py
Normal file
@ -0,0 +1,19 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Reward(BaseModel):
|
||||
name: str
|
||||
cnt: int
|
||||
|
||||
|
||||
class Code(BaseModel):
|
||||
code: str
|
||||
reward: List[Reward]
|
||||
expire: int
|
||||
|
||||
|
||||
class CodeList(BaseModel):
|
||||
main: List[Code]
|
||||
over: List[Code]
|
81
models/honkai.py
Normal file
81
models/honkai.py
Normal file
@ -0,0 +1,81 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from httpx import get
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
from .code import Code, Reward
|
||||
|
||||
|
||||
url = "https://honkai.gg/cn/codes"
|
||||
reward_map = {
|
||||
"Stellar Jade": "星琼",
|
||||
"Credit": "信用点",
|
||||
"Credits": "信用点",
|
||||
"Traveler's Guide": "漫游指南",
|
||||
"Refined Aether": "提纯以太",
|
||||
"Adventure Log": "冒险记录",
|
||||
"Dust of Alacrity": "疾速粉尘",
|
||||
"Condensed Aether": "凝缩以太",
|
||||
"Cosmic Fried Rice": "大宇宙炒饭",
|
||||
}
|
||||
|
||||
|
||||
def parse_reward(reward: List[str]) -> Reward:
|
||||
try:
|
||||
name = reward_map.get(reward[0])
|
||||
if not name:
|
||||
print("Unknown reward: ", reward[0])
|
||||
name = reward[0]
|
||||
return Reward(
|
||||
name=name,
|
||||
cnt=int(reward[1]),
|
||||
)
|
||||
except ValueError:
|
||||
print("Bad reward data: ", reward)
|
||||
|
||||
|
||||
def parse_code(tr: Tag) -> Code:
|
||||
tds = tr.find_all("td")
|
||||
code = tds[0].text.strip()
|
||||
expire = tds[2].text.strip()
|
||||
if expire.endswith("?"):
|
||||
expire = datetime(2099, 12, 31, 23, 59, 59, 999999)
|
||||
else:
|
||||
expires = expire.split(" - ")
|
||||
day = expires[1].split(" ")[-1]
|
||||
month = expires[0].split(" ")[0]
|
||||
try:
|
||||
if " " not in expires[1]:
|
||||
raise ValueError
|
||||
month = expires[1].split(" ")[0]
|
||||
except ValueError:
|
||||
pass
|
||||
now = datetime.now()
|
||||
expire = datetime.strptime(f"{day} {month}", "%d %b")
|
||||
expire = expire.replace(year=now.year, hour=23, minute=59, second=59, microsecond=999999)
|
||||
expire = int(expire.timestamp() * 1000)
|
||||
rewards = []
|
||||
for reward in tds[1].find_all("div", {"class": "flex"}):
|
||||
reward_div = reward.text.strip().split("\xa0x ")
|
||||
parsed_reward = parse_reward(reward_div)
|
||||
if parsed_reward:
|
||||
rewards.append(parsed_reward)
|
||||
for reward in tds[1].find_all("a"):
|
||||
reward_a = reward.text.strip().split(" x ")
|
||||
parsed_reward = parse_reward(reward_a)
|
||||
if parsed_reward:
|
||||
rewards.append(parsed_reward)
|
||||
return Code(code=code, reward=rewards, expire=expire)
|
||||
|
||||
|
||||
def get_code():
|
||||
html = get(url).text
|
||||
soup = BeautifulSoup(html, "lxml")
|
||||
tables = soup.find_all("table")
|
||||
codes = []
|
||||
for table in tables:
|
||||
trs = table.find_all("tr")[1:]
|
||||
for tr in trs:
|
||||
codes.append(parse_code(tr))
|
||||
codes.sort(key=lambda x: x.expire, reverse=True)
|
||||
return codes
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
pydantic
|
||||
httpx
|
||||
beautifulsoup4
|
||||
lxml
|
Loading…
Reference in New Issue
Block a user