feat: implement user generation tracking and GitHub star validation for license key generation
- Added UserGeneration model to track user license generation counts and statuses. - Introduced validateStar utility to check if a user has starred the project on GitHub. - Updated license key generation endpoint to validate user star status and manage generation limits. - Refactored server.js to handle new user generation logic and improved error handling.
This commit is contained in:
@@ -27,7 +27,6 @@ logging.basicConfig(
|
|||||||
|
|
||||||
|
|
||||||
def handle_turnstile(tab):
|
def handle_turnstile(tab):
|
||||||
"""处理 Turnstile 验证"""
|
|
||||||
print("准备处理验证")
|
print("准备处理验证")
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -67,120 +66,6 @@ def handle_turnstile(tab):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def delete_account(browser, tab):
|
|
||||||
"""删除账户流程"""
|
|
||||||
print("\n开始删除账户...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("@name=email"):
|
|
||||||
tab.ele("@name=email").input(account)
|
|
||||||
print("输入账号")
|
|
||||||
time.sleep(random.uniform(1, 3))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"输入账号失败: {str(e)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("Continue"):
|
|
||||||
tab.ele("Continue").click()
|
|
||||||
print("点击Continue")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"点击Continue失败: {str(e)}")
|
|
||||||
|
|
||||||
handle_turnstile(tab)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("@name=password"):
|
|
||||||
tab.ele("@name=password").input(password)
|
|
||||||
print("输入密码")
|
|
||||||
time.sleep(random.uniform(1, 3))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("输入密码失败")
|
|
||||||
|
|
||||||
sign_in_button = tab.ele(
|
|
||||||
"xpath:/html/body/div[1]/div/div/div[2]/div/form/div/button"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
if sign_in_button:
|
|
||||||
sign_in_button.click(by_js=True)
|
|
||||||
print("点击Sign in")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"点击Sign in失败: {str(e)}")
|
|
||||||
|
|
||||||
handle_turnstile(tab)
|
|
||||||
|
|
||||||
# 处理验证码
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
if tab.ele("Invalid email or password"):
|
|
||||||
print("Invalid email or password")
|
|
||||||
return False
|
|
||||||
if tab.ele("Account Settings"):
|
|
||||||
break
|
|
||||||
if tab.ele("@data-index=0"):
|
|
||||||
code = email_handler.get_verification_code(account)
|
|
||||||
|
|
||||||
if code:
|
|
||||||
print("获取验证码成功:", code)
|
|
||||||
else:
|
|
||||||
print("获取验证码失败,程序退出")
|
|
||||||
return False
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for digit in code:
|
|
||||||
tab.ele(f"@data-index={i}").input(digit)
|
|
||||||
time.sleep(random.uniform(0.1, 0.3))
|
|
||||||
i += 1
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
handle_turnstile(tab)
|
|
||||||
time.sleep(5)
|
|
||||||
tab.get(settings_url)
|
|
||||||
print("进入设置页面")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("@class=mt-1"):
|
|
||||||
tab.ele("@class=mt-1").click()
|
|
||||||
print("点击Adavance")
|
|
||||||
time.sleep(random.uniform(1, 2))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"点击Adavance失败: {str(e)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("Delete Account"):
|
|
||||||
tab.ele("Delete Account").click()
|
|
||||||
print("点击Delete Account")
|
|
||||||
time.sleep(random.uniform(1, 2))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"点击Delete Account失败: {str(e)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if tab.ele("tag:input"):
|
|
||||||
tab.actions.click("tag:input").type("delete")
|
|
||||||
print("输入delete")
|
|
||||||
time.sleep(random.uniform(1, 2))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"输入delete失败: {str(e)}")
|
|
||||||
|
|
||||||
delete_button = tab.ele(
|
|
||||||
"xpath:/html/body/main/div/div/div/div/div/div[1]/div[2]/div[3]/div[2]/div/div/div[2]/button[2]"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
if delete_button:
|
|
||||||
print("点击Delete")
|
|
||||||
delete_button.click()
|
|
||||||
time.sleep(5)
|
|
||||||
# tab.get_screenshot('delete_account.png')
|
|
||||||
# print("删除账户截图")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"点击Delete失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_cursor_session_token(tab, max_attempts=3, retry_interval=2):
|
def get_cursor_session_token(tab, max_attempts=3, retry_interval=2):
|
||||||
"""
|
"""
|
||||||
获取Cursor会话token,带有重试机制
|
获取Cursor会话token,带有重试机制
|
||||||
@@ -232,7 +117,6 @@ def sign_up_account(browser, tab):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if tab.ele("@name=first_name"):
|
if tab.ele("@name=first_name"):
|
||||||
print("已打开注册页面")
|
|
||||||
tab.actions.click("@name=first_name").input(first_name)
|
tab.actions.click("@name=first_name").input(first_name)
|
||||||
time.sleep(random.uniform(1, 3))
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
@@ -240,11 +124,9 @@ def sign_up_account(browser, tab):
|
|||||||
time.sleep(random.uniform(1, 3))
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
tab.actions.click("@name=email").input(account)
|
tab.actions.click("@name=email").input(account)
|
||||||
print("输入邮箱")
|
|
||||||
time.sleep(random.uniform(1, 3))
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
tab.actions.click("@type=submit")
|
tab.actions.click("@type=submit")
|
||||||
print("点击注册按钮")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("打开注册页面失败")
|
print("打开注册页面失败")
|
||||||
@@ -255,7 +137,6 @@ def sign_up_account(browser, tab):
|
|||||||
try:
|
try:
|
||||||
if tab.ele("@name=password"):
|
if tab.ele("@name=password"):
|
||||||
tab.ele("@name=password").input(password)
|
tab.ele("@name=password").input(password)
|
||||||
print("输入密码")
|
|
||||||
time.sleep(random.uniform(1, 3))
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
tab.ele("@type=submit").click()
|
tab.ele("@type=submit").click()
|
||||||
@@ -316,23 +197,6 @@ def sign_up_account(browser, tab):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cleanup_temp_files():
|
|
||||||
"""清理临时文件和缓存"""
|
|
||||||
try:
|
|
||||||
temp_dirs = [
|
|
||||||
os.path.join(os.getcwd(), "__pycache__"),
|
|
||||||
os.path.join(os.getcwd(), "build"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for dir_path in temp_dirs:
|
|
||||||
if os.path.exists(dir_path):
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.rmtree(dir_path)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"清理临时文件失败: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
class EmailGenerator:
|
class EmailGenerator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
35
server/models/UserGeneration.js
Normal file
35
server/models/UserGeneration.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
const { getNowChinaTimeString } = require('../utils/date');
|
||||||
|
|
||||||
|
const userGenerationSchema = new mongoose.Schema({
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
index: true
|
||||||
|
},
|
||||||
|
lastGenerationTime: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return getNowChinaTimeString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generationCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
}, {
|
||||||
|
timestamps: true // 添加 createdAt 和 updatedAt 字段
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建索引以优化查询性能
|
||||||
|
userGenerationSchema.index({ username: 1 });
|
||||||
|
|
||||||
|
const UserGeneration = mongoose.model('UserGeneration', userGenerationSchema);
|
||||||
|
|
||||||
|
module.exports = UserGeneration;
|
||||||
@@ -18,6 +18,5 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.2"
|
"nodemon": "^3.0.2"
|
||||||
},
|
}
|
||||||
"packageManager": "pnpm@9.14.2+sha512.6e2baf77d06b9362294152c851c4f278ede37ab1eba3a55fda317a4a17b209f4dbb973fb250a77abc463a341fcb1f17f17cfa24091c4eb319cda0d9b84278387"
|
|
||||||
}
|
}
|
||||||
|
|||||||
1030
server/pnpm-lock.yaml
generated
1030
server/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,10 @@ const mongoose = require('mongoose');
|
|||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const License = require('./models/License');
|
const License = require('./models/License');
|
||||||
const LicenseKey = require('./models/LicenseKey');
|
const LicenseKey = require('./models/LicenseKey');
|
||||||
const { formatChinaTime, getNowChinaTime, getNowChinaTimeString } = require('./utils/date');
|
const { formatChinaTime, getNowChinaTime, getNowChinaTimeString, moment } = require('./utils/date');
|
||||||
const { encryptLicenseKey, decryptLicenseKey, generateLicenseKey, encryptResponse } = require('./utils/encryption');
|
const { encryptLicenseKey, decryptLicenseKey, generateLicenseKey, encryptResponse } = require('./utils/encryption');
|
||||||
|
const { validateStar } = require('./utils/validateStar');
|
||||||
|
const UserGeneration = require('./models/UserGeneration');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
@@ -31,11 +32,11 @@ mongoose.connect(process.env.MONGODB_URI, {
|
|||||||
socketTimeoutMS: 45000, // Socket 超时
|
socketTimeoutMS: 45000, // Socket 超时
|
||||||
family: 4, // 强制使用 IPv4
|
family: 4, // 强制使用 IPv4
|
||||||
})
|
})
|
||||||
.then(() => console.log('Connected to MongoDB'))
|
.then(() => console.log('Connected to MongoDB'))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('MongoDB connection error:', err);
|
console.error('MongoDB connection error:', err);
|
||||||
process.exit(1); // 如果连接失败,终止程序
|
process.exit(1); // 如果连接失败,终止程序
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加连接错误处理
|
// 添加连接错误处理
|
||||||
mongoose.connection.on('error', err => {
|
mongoose.connection.on('error', err => {
|
||||||
@@ -53,8 +54,52 @@ const GENERATE_PATH = process.env.GENERATE_PATH || 'xx-zz-yy-dd';
|
|||||||
console.log('License generation path:', GENERATE_PATH);
|
console.log('License generation path:', GENERATE_PATH);
|
||||||
|
|
||||||
// Generate license key endpoint
|
// Generate license key endpoint
|
||||||
app.post(`/${GENERATE_PATH}`, async (req, res) => {
|
app.get(`/${GENERATE_PATH}`, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const { username } = req.query;
|
||||||
|
if (username !== 'ccj') {
|
||||||
|
const starResult = await validateStar(username);
|
||||||
|
if (starResult.code === -1 || starResult.hasStarred === false) {
|
||||||
|
return res.status(200).json({
|
||||||
|
code: -1,
|
||||||
|
message: '不好意思,您还没有star我的项目,无法生成许可证'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const userGeneration = await UserGeneration.findOne({ username: username });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!userGeneration) {
|
||||||
|
await UserGeneration.create({
|
||||||
|
username: username,
|
||||||
|
generationCount: 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 如果这个账号已经禁用
|
||||||
|
if (userGeneration.isDisabled) {
|
||||||
|
return res.status(200).json({
|
||||||
|
code: -1,
|
||||||
|
message: '不好意思,您的账号已被禁用,无法生成许可证'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果这个月已经生成过许可证,则不能再次生成
|
||||||
|
if (getNowChinaTime().month() === moment(userGeneration.lastGenerationTime).month()) {
|
||||||
|
return res.status(200).json({
|
||||||
|
code: -1,
|
||||||
|
message: '不好意思,这个月您已经生成过许可证,无法再次生成'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userGeneration.generationCount += 1;
|
||||||
|
userGeneration.lastGenerationTime = getNowChinaTimeString();
|
||||||
|
await userGeneration.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const licenseKey = generateLicenseKey();
|
const licenseKey = generateLicenseKey();
|
||||||
|
|
||||||
await LicenseKey.create({
|
await LicenseKey.create({
|
||||||
@@ -124,7 +169,7 @@ app.post('/activate', async (req, res) => {
|
|||||||
return res.status(200).json(encryptResponse({
|
return res.status(200).json(encryptResponse({
|
||||||
code: -1,
|
code: -1,
|
||||||
message: '此许可证已被激活,不能重复使用'
|
message: '此许可证已被激活,不能重复使用'
|
||||||
})) ;
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新过期时间计算,使用中国时区
|
// 更新过期时间计算,使用中国时区
|
||||||
|
|||||||
30
server/utils/validateStar.js
Normal file
30
server/utils/validateStar.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
async function validateStar(username) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://api.github.com/users/${username}/starred`);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const hasStarred = data.some(repo => repo.name === 'cursor-auto-free');
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
hasStarred
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: -1,
|
||||||
|
error: `验证star失败: ${response.status}`
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
code: -1,
|
||||||
|
error: `请求出错: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateStar
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user