Commit 1006a158 authored by nanahira's avatar nanahira

rework

parent 1e98a3df
Pipeline #5728 passed with stages
in 33 minutes and 18 seconds
FROM node:buster-slim
FROM node:bullseye-slim
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/src/app
COPY ./package*.json ./
......
This diff is collapsed.
......@@ -21,25 +21,24 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/axios": "^0.0.1",
"@nestjs/common": "^7.5.1",
"@nestjs/config": "^1.0.1",
"@nestjs/core": "^7.5.1",
"@nestjs/platform-express": "^7.5.1",
"@nestjs/swagger": "^4.8.0",
"@nestjs/typeorm": "^7.1.5",
"@types/cookie": "^0.4.0",
"axios": "^0.21.1",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"cookie": "^0.4.1",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"mysql": "^2.18.1",
"p-queue": "^6.6.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.3",
"swagger-ui-express": "^4.1.6",
"typeorm": "^0.2.32"
"swagger-ui-express": "^4.1.6"
},
"devDependencies": {
"@nestjs/cli": "^7.5.1",
......
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller()
@ApiTags('tips')
......@@ -12,6 +12,7 @@ export class AppController {
summary: '获取提示',
description: '获取 SRVPro 需要的 tips.json 文件。',
})
@ApiOkResponse({ type: [String] })
async getTips(): Promise<string[]> {
return await this.appService.getTips();
}
......
import { Injectable, Scope, Logger } from '@nestjs/common';
@Injectable()
export class AppLogger extends Logger {}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppLogger } from './app.logger';
import { HttpModule } from '@nestjs/axios';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [],
imports: [HttpModule, ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService, AppLogger],
providers: [AppService],
})
export class AppModule {}
import { Injectable } from '@nestjs/common';
import { AppLogger } from './app.logger';
import { Injectable, Logger } from '@nestjs/common';
import moment from 'moment';
import PQueue from 'p-queue';
import axios from 'axios';
import _ from 'lodash';
import qs from 'qs';
import cookie from 'cookie';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
const blacklistedWords = ['', '', '', '', '[Vv]ocal', ':', ''];
const blacklistedRegex = blacklistedWords.map((word) => new RegExp(word));
......@@ -16,55 +16,43 @@ interface LoginInfo {
}
@Injectable()
export class AppService {
export class AppService extends Logger {
cache: string[];
lastFetchTime: moment.Moment;
queue: PQueue;
queue = new PQueue({ concurrency: 1 });
loginInfo: LoginInfo;
constructor(private log: AppLogger) {
this.log.setContext('ygopro-tips-biu-generator');
this.queue = new PQueue({ concurrency: 1 });
if (process.env.BIU_EMAIL && process.env.BIU_PASSWORD) {
constructor(config: ConfigService, private http: HttpService) {
super('ygopro-tips-biu-generator');
if (config.get<string>('BIU_EMAIL') && config.get<string>('BIU_PASSWORD')) {
this.loginInfo = {
email: process.env.BIU_EMAIL,
password: process.env.BIU_PASSWORD,
email: config.get<string>('BIU_EMAIL'),
password: config.get<string>('BIU_PASSWORD'),
};
}
}
async getTips(): Promise<string[]> {
if (
this.cache &&
this.lastFetchTime &&
moment().diff(this.lastFetchTime) <= 3600000
) {
return this.cache;
}
this.log.log(`Queue size: ${this.queue.size}`);
if (this.queue.size > 0) {
await this.queue.onEmpty();
return this.cache;
} else {
return await this.fetchTips();
}
return this.fetchTips();
}
async fetchTips(): Promise<string[]> {
return await this.queue.add(async () => await this.fetchTipsTask());
return await this.queue.add(async () => this.fetchTipsTask());
}
async getLyricsOfSong(id: number): Promise<string[]> {
try {
this.log.log(`Fetching lyrics of song ${id}.`);
const { data } = await axios.get(`https://web.biu.moe/s${id}`, {
responseType: 'document',
timeout: 30000,
});
this.log(`Fetching lyrics of song ${id}.`);
const { data } = await this.http
.get(`https://web.biu.moe/s${id}`, {
responseType: 'document',
timeout: 30000,
})
.toPromise();
const lyricMatches = (data as string).match(
/\[\d+:\d+[:\.]\d+\]([^<]+)/g,
);
if (!lyricMatches) {
this.log.log(`Song ${id} has no lyrics.`);
this.log(`Song ${id} has no lyrics.`);
return [];
}
const lyrics = lyricMatches
......@@ -74,10 +62,10 @@ export class AppService {
.filter((lyric) =>
blacklistedRegex.every((regex) => !lyric.match(regex)),
);
this.log.log(`${lyrics.length} lines of lyrics found in song ${id}.`);
this.log(`${lyrics.length} lines of lyrics found in song ${id}.`);
return lyrics;
} catch (e) {
this.log.error(`Failed fetching lyrics of ${id}: ${e.toString()}`);
this.error(`Failed fetching lyrics of ${id}: ${e.toString()}`);
return [];
}
}
......@@ -87,17 +75,19 @@ export class AppService {
return undefined;
}
try {
this.log.log(`Logging in.`);
const ret = await axios.post(
'https://web.biu.moe/User/doLogin',
qs.stringify(this.loginInfo),
{
responseType: 'json',
timeout: 30000,
},
);
this.log(`Logging in.`);
const ret = await this.http
.post(
'https://web.biu.moe/User/doLogin',
qs.stringify(this.loginInfo),
{
responseType: 'json',
timeout: 30000,
},
)
.toPromise();
if (!ret.data.status) {
this.log.error(`Login failed: ${ret.data.message}`);
this.error(`Login failed: ${ret.data.message}`);
return undefined;
}
const setCookieContent = ret.headers['set-cookie'] as string[];
......@@ -109,45 +99,54 @@ export class AppService {
return undefined;
}
const token = cookie.parse(contentHits).biuInfo as string;
this.log.log(`Login success: ${token}`);
this.log(`Login success: ${token}`);
return cookie.serialize('biuInfo', token);
} catch (e) {
this.log.error(`Login error: ${e.toString()}`);
this.error(`Login error: ${e.toString()}`);
return undefined;
}
}
async fetchTipsTask(): Promise<string[]> {
if (
this.cache &&
this.lastFetchTime &&
moment().diff(this.lastFetchTime) <= 3600000
) {
return this.cache;
}
try {
this.log.log(`Fetching song list.`);
const { data } = await axios.get('https://web.biu.moe/Index/home', {
responseType: 'document',
headers: {
Cookie: (await this.getLoginCookie()) || '',
},
timeout: 30000,
});
this.log(`Fetching song list.`);
const { data } = await this.http
.get('https://web.biu.moe/Index/home', {
responseType: 'document',
headers: {
Cookie: (await this.getLoginCookie()) || '',
},
timeout: 30000,
})
.toPromise();
const urls = (data as string).match(/href="\/s(\d+)"/g);
if (!urls) {
this.log.error(`No songs found, retrying.`);
this.error(`No songs found, retrying.`);
return await this.fetchTipsTask();
}
this.log.log(`${urls.length} songs found.`);
this.log(`${urls.length} songs found.`);
const songIds = urls.map((m) => parseInt(m.match(/href="\/s(\d+)"/)[1]));
const lyrics = _.flatten(
await Promise.all(songIds.map((id) => this.getLyricsOfSong(id))),
);
this.log.log(`${lyrics.length} lines of lyrics got.`);
this.log(`${lyrics.length} lines of lyrics got.`);
if (lyrics.length) {
this.cache = lyrics;
this.lastFetchTime = moment();
return lyrics;
} else {
this.log.log(`No lyrics found, retrying.`);
this.log(`No lyrics found, retrying.`);
return await this.fetchTipsTask();
}
} catch (e) {
this.log.error(`Errored fetching song list, retrying: ${e.toString()}`);
this.error(`Errored fetching song list, retrying: ${e.toString()}`);
return await this.fetchTipsTask();
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment