-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbootstrap.ts
153 lines (136 loc) · 5.17 KB
/
bootstrap.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import axios, { AxiosError, AxiosInstance, AxiosPromise } from 'axios'
import * as fs from 'fs'
import { JSDOM } from 'jsdom'
import { flatten } from 'lodash'
import * as path from 'path'
import { URL } from 'url'
import { delay, ObjectMap, Unpacked } from './libs/helper'
import { IApplication, IRequireStatus } from './typings/application'
import { IBaseConfig } from './typings/config'
export interface IDownloadToRetn {
configName: string
filename: string
path: string
s: IRequireStatus
config: IBaseConfig
}
export interface IDownloadRetn {
configName: string
result: AxiosPromise<ArrayBuffer>
s: IRequireStatus
to: (savePath: string) => Promise<IDownloadToRetn>
}
export default class Bootstrap {
public static async initialize(apps: ObjectMap<IApplication>) {
const instances: ObjectMap<Bootstrap> = {}
for (const [name, app] of Object.entries(apps)) instances[name] = new this(app, name)
let delayCount = 0
const delayStep = 3000
for (const instance of Object.values(instances)) {
instance.run()
await delay(delayCount++ * delayStep)
}
}
private readonly config: IBaseConfig
private readonly httpClient: AxiosInstance
private readonly logFile: fs.PathLike
public get http() { return this.httpClient }
public get configs() { return this.config }
public constructor(readonly app: IApplication, readonly configName: string) {
this.config = app.config
const { origin } = new URL(
Array.isArray(this.config.url) ? this.config.url[0] : this.config.url,
)
this.httpClient = axios.create({
baseURL: origin,
headers: {
...this.config.headers,
cookie: this.config.cookie,
},
timeout: this.config.timeout,
})
this.logFile = path.join(__dirname, `${configName}-id.log`)
// create an empty file if logFile not exist
fs.closeSync(fs.openSync(this.logFile, 'a'))
}
public async fetchTorrentsStatus(specUrls?: string[]) {
const urls = specUrls ?? (Array.isArray(this.config.url) ? this.config.url : [this.config.url])
const responses = await Promise.all(urls.map(url => this.httpClient.get<string>(url)))
const torrentsStatusQueue = responses.map(async response => {
const DOMTree = new JSDOM(response.data)
const { document } = DOMTree.window
return await this.app.getTorrentsStatus(document)
})
const torrentsStatus = flatten(await Promise.all(torrentsStatusQueue))
return torrentsStatus
}
public async filterTorrentStatus(
torrentsStatus: Unpacked<Unpacked<Bootstrap['fetchTorrentsStatus']>>,
) {
const logStream = fs.readFileSync(this.logFile).toString().split('\n')
const filteredStatus = torrentsStatus.filter((torrent) => {
if (logStream.includes(torrent.id.toString())) return false
fs.writeFileSync(this.logFile, torrent.id + '\n', {
flag: 'a',
})
return true
})
return filteredStatus
}
public async runOnce() {
console.log('Start task ', this.configName)
await this.app.preFetch.call(this)
const torrentsStatus = await this.fetchTorrentsStatus()
await this.app.afterFetch.call(this)
const filteredStatus = await this.filterTorrentStatus(torrentsStatus)
const customFilteredStatus = filteredStatus.filter(s => this.app.filter.call(this, s))
console.log(
`Detected new torrents on ${this.configName} - ${JSON.stringify(
customFilteredStatus.map(s => s.id),
)}`,
)
for (const status of filteredStatus) {
console.log(`Excute success function with ${this.configName} ${status.id}`)
await this.app.success.call(this, status)
}
console.log(`Complete task ${this.configName}, wait for ${this.config.interval}ms`)
}
public async run() {
const isAxiosError = (error: AxiosError | Error): error is AxiosError =>
typeof (error as AxiosError).response !== 'undefined'
while (true) {
await this.runOnce().catch((error: AxiosError | Error) => {
if (isAxiosError(error))
console.error('Error ', this.configName, ' - got ', error.response.status)
else console.error('Error ', this.configName, ' - ', error.message)
console.error(error.stack)
})
await delay(this.config.interval)
}
}
public download(s: IRequireStatus): IDownloadRetn {
console.log(`Downloading torrent ${s.id} from ${this.configName} ...`)
const result = this.httpClient.get<ArrayBuffer>(s.downLink, {
responseType: 'arraybuffer',
})
const to = async (savePath: string): Promise<IDownloadToRetn> => {
const outputFileName = `${this.configName} - ${s.id}.torrent`
const outputFullPath = path.join(savePath, outputFileName)
fs.writeFileSync(outputFullPath, (await result).data as NodeJS.ArrayBufferView)
console.log(
`Download torrent ${s.id} from ${this.configName} completed. Save to ${outputFullPath}.`,
)
return {
configName: this.configName,
filename: outputFileName,
path: outputFullPath,
s,
config: this.config,
}
}
return { configName: this.configName, result, s, to }
}
public async downloadTo(s: IRequireStatus, savePath: string) {
return this.download(s).to(savePath)
}
}