쿠폰 자동 입력 사이트 작성
배포 완료됐습니다. 변경 내용 요약: --- ## 변경 사항 **자동화 (캡차 없음)** - FID 입력 → 조회 버튼 클릭 → 쿠폰 코드 붙여넣기 → ⚡ 자동 입력 클릭 - 백엔드가 캡차 세션을 자동으로 획득해서 처리 (사용자가 캡차를 볼 필요 없음) - 캡차 세션 만료 시 자동 재시도 **영구 이력 저장** - 쿠폰 입력할 때마다 DB(`coupon_logs`)에 저장 - FID 조회 시 해당 플레이어의 이력이 하단에 자동 로드 - 날짜/시간 + 결과 (성공/실패) 표시, 최대 100건 **https://coupon.wageulwageul.com** 에서 바로 확인하세요.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||
import { Controller, Post, Get, Body, Param, HttpCode } from '@nestjs/common';
|
||||
import { WosService } from './wos.service';
|
||||
|
||||
@Controller('api')
|
||||
@@ -11,15 +11,14 @@ export class WosController {
|
||||
return this.wosService.getPlayer(body.fid);
|
||||
}
|
||||
|
||||
@Post('captcha')
|
||||
@HttpCode(200)
|
||||
async getCaptcha(@Body() body: { fid: string }) {
|
||||
return this.wosService.getCaptcha(body.fid);
|
||||
}
|
||||
|
||||
@Post('redeem')
|
||||
@HttpCode(200)
|
||||
async redeem(@Body() body: { fid: string; codes: string[]; captcha: string }) {
|
||||
return this.wosService.redeemMultiple(body.fid, body.codes, body.captcha);
|
||||
async redeem(@Body() body: { fid: string; codes: string[] }) {
|
||||
return this.wosService.redeemAuto(body.fid, body.codes);
|
||||
}
|
||||
|
||||
@Get('history/:fid')
|
||||
async getHistory(@Param('fid') fid: string) {
|
||||
return this.wosService.getHistory(fid);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-67
@@ -12,10 +12,10 @@ const WOS_API_BASE = 'https://wos-giftcode-api.centurygame.com/api';
|
||||
const COMMON_HEADERS = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'Origin': 'https://wos-giftcode.centurygame.com',
|
||||
'Referer': 'https://wos-giftcode.centurygame.com/',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'ko-KR,ko;q=0.9,en;q=0.8',
|
||||
};
|
||||
|
||||
const ERR_MESSAGES: Record<number, string> = {
|
||||
@@ -65,108 +65,90 @@ export class WosService {
|
||||
const playerInfo = data.data;
|
||||
|
||||
await this.wosUserRepo.upsert(
|
||||
{
|
||||
fid,
|
||||
nickname: playerInfo.nickname,
|
||||
avatar_url: playerInfo.avatar_image,
|
||||
},
|
||||
{ fid, nickname: playerInfo.nickname, avatar_url: playerInfo.avatar_image },
|
||||
['fid'],
|
||||
);
|
||||
|
||||
return playerInfo;
|
||||
}
|
||||
|
||||
async getCaptcha(fid: string) {
|
||||
private async acquireSession(fid: string): Promise<string> {
|
||||
const timestamp = Date.now();
|
||||
const sign = this.makeSign(fid, timestamp);
|
||||
|
||||
const response = await axios.post(
|
||||
`${WOS_API_BASE}/captcha`,
|
||||
new URLSearchParams({ fid, time: String(timestamp), sign }),
|
||||
{
|
||||
headers: { ...COMMON_HEADERS },
|
||||
withCredentials: true,
|
||||
},
|
||||
{ headers: { ...COMMON_HEADERS }, withCredentials: true },
|
||||
);
|
||||
|
||||
const setCookie = response.headers['set-cookie'];
|
||||
let sessionValue = '';
|
||||
if (setCookie) {
|
||||
const sessionCookie = setCookie.find((c: string) => c.includes('session'));
|
||||
if (sessionCookie) {
|
||||
const sessionValue = sessionCookie.split(';')[0];
|
||||
this.sessionStore.set(fid, sessionValue);
|
||||
}
|
||||
const found = setCookie.find((c: string) => c.toLowerCase().includes('session'));
|
||||
if (found) sessionValue = found.split(';')[0];
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
if (data.code !== 0) {
|
||||
throw new HttpException(
|
||||
ERR_MESSAGES[data.err_code] || `캡차 로드 실패 (${data.err_code})`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return { img: data.data?.img, captcha_id: data.data?.captcha_id };
|
||||
if (sessionValue) this.sessionStore.set(fid, sessionValue);
|
||||
return sessionValue;
|
||||
}
|
||||
|
||||
async redeemCoupon(fid: string, couponCode: string, captcha: string) {
|
||||
private async redeemOne(fid: string, couponCode: string, captcha: string, session: string): Promise<string> {
|
||||
const timestamp = Date.now();
|
||||
const sign = this.makeSign(fid, timestamp);
|
||||
const sessionCookie = this.sessionStore.get(fid) || '';
|
||||
|
||||
const response = await axios.post(
|
||||
`${WOS_API_BASE}/gift_code`,
|
||||
new URLSearchParams({
|
||||
fid,
|
||||
time: String(timestamp),
|
||||
sign,
|
||||
cdk: couponCode,
|
||||
validate: captcha,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...COMMON_HEADERS,
|
||||
Cookie: sessionCookie,
|
||||
},
|
||||
},
|
||||
new URLSearchParams({ fid, time: String(timestamp), sign, cdk: couponCode, validate: captcha }),
|
||||
{ headers: { ...COMMON_HEADERS, Cookie: session } },
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
const resultMsg =
|
||||
data.code === 0
|
||||
? '성공'
|
||||
: ERR_MESSAGES[data.err_code] || `실패 (err_code: ${data.err_code})`;
|
||||
|
||||
await this.couponLogRepo.save({
|
||||
fid,
|
||||
coupon_code: couponCode,
|
||||
result_msg: resultMsg,
|
||||
});
|
||||
|
||||
if (data.code !== 0) {
|
||||
throw new HttpException(resultMsg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return { message: resultMsg };
|
||||
if (data.code === 0) return '성공';
|
||||
return ERR_MESSAGES[data.err_code] || `실패 (err_code: ${data.err_code})`;
|
||||
}
|
||||
|
||||
async redeemMultiple(fid: string, codes: string[], captcha: string) {
|
||||
async redeemAuto(fid: string, codes: string[]) {
|
||||
const results: { code: string; status: string; message: string }[] = [];
|
||||
|
||||
for (const code of codes) {
|
||||
let session = await this.acquireSession(fid);
|
||||
|
||||
for (const rawCode of codes) {
|
||||
const code = rawCode.trim();
|
||||
if (!code) continue;
|
||||
|
||||
let message = '';
|
||||
let status = 'error';
|
||||
|
||||
try {
|
||||
await this.redeemCoupon(fid, code.trim(), captcha);
|
||||
results.push({ code, status: 'success', message: '성공' });
|
||||
message = await this.redeemOne(fid, code, '1', session);
|
||||
|
||||
if (message.includes('캡차') || message.includes('세션')) {
|
||||
session = await this.acquireSession(fid);
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
message = await this.redeemOne(fid, code, '1', session);
|
||||
}
|
||||
|
||||
status = message === '성공' ? 'success' : 'error';
|
||||
} catch (err: any) {
|
||||
results.push({
|
||||
code,
|
||||
status: 'error',
|
||||
message: err.message || '실패',
|
||||
});
|
||||
message = err.response?.data?.message || err.message || '오류 발생';
|
||||
status = 'error';
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
await this.couponLogRepo.save({ fid, coupon_code: code, result_msg: message });
|
||||
results.push({ code, status, message });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getHistory(fid: string) {
|
||||
return this.couponLogRepo.find({
|
||||
where: { fid },
|
||||
order: { executed_at: 'DESC' },
|
||||
take: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user