mirror of
https://github.com/earthjasonlin/star-rail-warp-export.git
synced 2026-10-16 02:50:12 +08:00
Departure commit
This commit is contained in:
commit
92cafdadb8
61 files changed
+9597
No files matched your search
@@ -0,0 +1,89 @@
|
||||
const { app, ipcMain, dialog } = require('electron')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const getData = require('./getData').getData
|
||||
const { version } = require('../../package.json')
|
||||
|
||||
const getTimeString = () => {
|
||||
return new Date().toLocaleString('sv').replace(/[- :]/g, '').slice(0, -2)
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
let y = date.getFullYear()
|
||||
let m = `${date.getMonth()+1}`.padStart(2, '0')
|
||||
let d = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${y}-${m}-${d} ${date.toLocaleString('zh-cn', { hour12: false }).slice(-8)}`
|
||||
}
|
||||
|
||||
const fakeIdFn = () => {
|
||||
let id = 1000000000000000000n
|
||||
return () => {
|
||||
id = id + 1n
|
||||
return id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
const shouldBeString = (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
return ''
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
const { dataMap, current } = await getData()
|
||||
const data = dataMap.get(current)
|
||||
if (!data.result.size) {
|
||||
throw new Error('数据为空')
|
||||
}
|
||||
const fakeId = fakeIdFn()
|
||||
const result = {
|
||||
info: {
|
||||
uid: data.uid,
|
||||
lang: data.lang,
|
||||
export_time: formatDate(new Date()),
|
||||
export_timestamp: Date.now(),
|
||||
export_app: 'genshin-wish-export',
|
||||
export_app_version: `v${version}`,
|
||||
uigf_version: 'v2.2'
|
||||
},
|
||||
list: []
|
||||
}
|
||||
const listTemp = []
|
||||
for (let [type, arr] of data.result) {
|
||||
arr.forEach(item => {
|
||||
listTemp.push({
|
||||
gacha_type: shouldBeString(item[4]) || type,
|
||||
time: item[0],
|
||||
timestamp: new Date(item[0]).getTime(),
|
||||
name: item[1],
|
||||
item_type: item[2],
|
||||
rank_type: `${item[3]}`,
|
||||
id: shouldBeString(item[5]) || '',
|
||||
uigf_gacha_type: type
|
||||
})
|
||||
})
|
||||
}
|
||||
listTemp.sort((a, b) => a.timestamp - b.timestamp)
|
||||
listTemp.forEach(item => {
|
||||
delete item.timestamp
|
||||
result.list.push({
|
||||
...item,
|
||||
id: item.id || fakeId()
|
||||
})
|
||||
})
|
||||
const filePath = dialog.showSaveDialogSync({
|
||||
defaultPath: path.join(app.getPath('downloads'), `UIGF_${data.uid}_${getTimeString()}`),
|
||||
filters: [
|
||||
{ name: 'JSON文件', extensions: ['json'] }
|
||||
]
|
||||
})
|
||||
if (filePath) {
|
||||
await fs.ensureFile(filePath)
|
||||
await fs.writeFile(filePath, JSON.stringify(result))
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle('EXPORT_UIGF_JSON', async () => {
|
||||
await start()
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
const { readJSON, saveJSON, decipherAes, cipherAes, detectLocale } = require('./utils')
|
||||
|
||||
const config = {
|
||||
urls: [],
|
||||
logType: 0,
|
||||
lang: detectLocale(),
|
||||
current: 0,
|
||||
proxyPort: 8325,
|
||||
proxyMode: false,
|
||||
autoUpdate: true,
|
||||
fetchFullHistory: false,
|
||||
hideNovice: false
|
||||
}
|
||||
|
||||
const getLocalConfig = async () => {
|
||||
const localConfig = await readJSON('config.json')
|
||||
if (!localConfig) return
|
||||
const configTemp = {}
|
||||
for (let key in localConfig) {
|
||||
if (typeof config[key] !== 'undefined') {
|
||||
configTemp[key] = localConfig[key]
|
||||
}
|
||||
}
|
||||
configTemp.urls.forEach(item => {
|
||||
try {
|
||||
item[1] = decipherAes(item[1])
|
||||
} catch (e) {
|
||||
item[1] = ''
|
||||
}
|
||||
})
|
||||
Object.assign(config, configTemp)
|
||||
}
|
||||
|
||||
getLocalConfig()
|
||||
|
||||
let urlsMap = null
|
||||
const setConfig = (key, value) => {
|
||||
Reflect.set(config, key, value)
|
||||
}
|
||||
|
||||
const saveConfig = async () => {
|
||||
let configTemp = config
|
||||
if (urlsMap) {
|
||||
const urls = [...urlsMap]
|
||||
urls.forEach(item => {
|
||||
try {
|
||||
item[1] = cipherAes(item[1])
|
||||
} catch (e) {
|
||||
item[1] = ''
|
||||
}
|
||||
})
|
||||
configTemp = Object.assign({}, config, { urls })
|
||||
}
|
||||
await saveJSON('config.json', configTemp)
|
||||
}
|
||||
|
||||
const getPlainConfig = () => config
|
||||
|
||||
const configProxy = new Proxy(config, {
|
||||
get: function (obj, prop) {
|
||||
if (prop === 'urls') {
|
||||
if (!urlsMap) {
|
||||
urlsMap = new Map(obj[prop])
|
||||
}
|
||||
return urlsMap
|
||||
} else if (prop === 'set') {
|
||||
return setConfig
|
||||
} else if (prop === 'save') {
|
||||
return saveConfig
|
||||
} else if (prop === 'value') {
|
||||
return getPlainConfig
|
||||
}
|
||||
return obj[prop]
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = configProxy
|
||||
@@ -0,0 +1,167 @@
|
||||
const ExcelJS = require('./module/exceljs.min.js')
|
||||
const getData = require('./getData').getData
|
||||
const { app, ipcMain, dialog } = require('electron')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const i18n = require('./i18n')
|
||||
const cloneDeep = require('lodash-es/cloneDeep').default
|
||||
|
||||
function pad(num) {
|
||||
return `${num}`.padStart(2, "0");
|
||||
}
|
||||
|
||||
function getTimeString() {
|
||||
const d = new Date();
|
||||
const YYYY = d.getFullYear();
|
||||
const MM = pad(d.getMonth() + 1);
|
||||
const DD = pad(d.getDate());
|
||||
const HH = pad(d.getHours());
|
||||
const mm = pad(d.getMinutes());
|
||||
const ss = pad(d.getSeconds());
|
||||
return `${YYYY}${MM}${DD}_${HH}${mm}${ss}`;
|
||||
}
|
||||
|
||||
const addRawSheet = (workbook, data) => {
|
||||
const sheet = workbook.addWorksheet('rawData', {views: [{state: 'frozen', ySplit: 1}]})
|
||||
const excelKeys = ['gacha_id', 'gacha_type', 'id', 'item_id', 'item_type', 'lang', 'name', 'rank_type', 'time', 'uid']
|
||||
sheet.columns = excelKeys.map((key, index) => {
|
||||
return {
|
||||
header: key,
|
||||
key,
|
||||
}
|
||||
})
|
||||
const temp = []
|
||||
for (let [key, value] of data.result) {
|
||||
for (let log of value){
|
||||
const arr = []
|
||||
arr.push(log.gacha_id)
|
||||
arr.push(log.gacha_type)
|
||||
arr.push(log.id)
|
||||
arr.push(log.item_id)
|
||||
arr.push(log.item_type)
|
||||
arr.push(data.lang)
|
||||
arr.push(log.name)
|
||||
arr.push(log.rank_type)
|
||||
arr.push(log.time)
|
||||
arr.push(data.uid)
|
||||
temp.push(arr)
|
||||
}
|
||||
}
|
||||
sheet.addRows(temp)
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
const { header, customFont, filePrefix, fileType, wish2 } = i18n.excel
|
||||
const { dataMap, current } = await getData()
|
||||
const data = dataMap.get(current)
|
||||
// https://github.com/sunfkny/genshin-gacha-export-js/blob/main/index.js
|
||||
const workbook = new ExcelJS.Workbook()
|
||||
for (let [key, value] of data.result) {
|
||||
const name = data.typeMap.get(key)
|
||||
const sheet = workbook.addWorksheet(name, {views: [{state: 'frozen', ySplit: 1}]})
|
||||
let width = [24, 14, 8, 8, 8, 8, 8]
|
||||
if (!data.lang.includes('zh-')) {
|
||||
width = [24, 32, 16, 12, 12, 12, 8]
|
||||
}
|
||||
const excelKeys = ['time', 'name', 'type', 'rank', 'total', 'pity', 'remark']
|
||||
sheet.columns = excelKeys.map((key, index) => {
|
||||
return {
|
||||
header: header[key],
|
||||
key,
|
||||
width: width[index]
|
||||
}
|
||||
})
|
||||
// get gacha logs
|
||||
const logs = value
|
||||
let total = 0
|
||||
let pity = 0
|
||||
const temp = []
|
||||
for (let log of logs) {
|
||||
const arr = []
|
||||
total += 1
|
||||
pity += 1
|
||||
arr.push(log.time)
|
||||
arr.push(log.name)
|
||||
arr.push(log.item_type)
|
||||
arr.push(log.rank_type)
|
||||
arr.push(total)
|
||||
arr.push(pity)
|
||||
temp.push(arr)
|
||||
if (log.rank_type === 5) {
|
||||
pity = 0
|
||||
}
|
||||
// if (key === '301') {
|
||||
// if (log.gacha_type === '400') {
|
||||
// log.push(wish2)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
sheet.addRows(temp)
|
||||
// set xlsx hearer style
|
||||
;(["A", "B", "C", "D","E","F", "G"]).forEach((v) => {
|
||||
sheet.getCell(`${v}1`).border = {
|
||||
top: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
left: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
bottom: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
right: {style:'thin', color: {argb:'ffc4c2bf'}}
|
||||
}
|
||||
sheet.getCell(`${v}1`).fill = {
|
||||
type: 'pattern',
|
||||
pattern:'solid',
|
||||
fgColor:{argb:'ffdbd7d3'},
|
||||
}
|
||||
sheet.getCell(`${v}1`).font ={
|
||||
name: customFont,
|
||||
color: { argb: "ff757575" },
|
||||
bold : true
|
||||
}
|
||||
|
||||
})
|
||||
// set xlsx cell style
|
||||
logs.forEach((v, i) => {
|
||||
;(["A", "B", "C", "D","E","F", "G"]).forEach((c) => {
|
||||
sheet.getCell(`${c}${i + 2}`).border = {
|
||||
top: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
left: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
bottom: {style:'thin', color: {argb:'ffc4c2bf'}},
|
||||
right: {style:'thin', color: {argb:'ffc4c2bf'}}
|
||||
}
|
||||
sheet.getCell(`${c}${i + 2}`).fill = {
|
||||
type: 'pattern',
|
||||
pattern:'solid',
|
||||
fgColor:{argb:'ffebebeb'},
|
||||
}
|
||||
// rare rank background color
|
||||
const rankColor = {
|
||||
3: "ff8e8e8e",
|
||||
4: "ffa256e1",
|
||||
5: "ffbd6932",
|
||||
}
|
||||
sheet.getCell(`${c}${i + 2}`).font = {
|
||||
name: customFont,
|
||||
color: { argb: rankColor[v.rank_type] },
|
||||
bold : v.rank_type != "3"
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
addRawSheet(workbook, data)
|
||||
|
||||
const buffer = await workbook.xlsx.writeBuffer()
|
||||
const filePath = dialog.showSaveDialogSync({
|
||||
defaultPath: path.join(app.getPath('downloads'), `${filePrefix}_${getTimeString()}`),
|
||||
filters: [
|
||||
{ name: fileType, extensions: ['xlsx'] }
|
||||
]
|
||||
})
|
||||
if (filePath) {
|
||||
await fs.ensureFile(filePath)
|
||||
await fs.writeFile(filePath, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle('SAVE_EXCEL', async () => {
|
||||
await start()
|
||||
})
|
||||
@@ -0,0 +1,495 @@
|
||||
const fs = require('fs-extra')
|
||||
const util = require('util')
|
||||
const path = require('path')
|
||||
const { URL } = require('url')
|
||||
const { app, ipcMain, shell } = require('electron')
|
||||
const { sleep, request, sendMsg, readJSON, saveJSON, detectLocale, userDataPath, userPath, localIp, langMap } = require('./utils')
|
||||
const config = require('./config')
|
||||
const i18n = require('./i18n')
|
||||
const { enableProxy, disableProxy } = require('./module/system-proxy')
|
||||
const mitmproxy = require('./module/node-mitmproxy')
|
||||
const { mergeData } = require('./utils/mergeData')
|
||||
|
||||
const dataMap = new Map()
|
||||
const order = ['11', '12', '1', '2']
|
||||
let apiDomain = 'https://api-takumi.mihoyo.com'
|
||||
|
||||
const saveData = async (data, url) => {
|
||||
const obj = Object.assign({}, data)
|
||||
obj.result = [...obj.result]
|
||||
obj.typeMap = [...obj.typeMap]
|
||||
config.urls.set(data.uid, url)
|
||||
await config.save()
|
||||
await saveJSON(`gacha-list-${data.uid}.json`, obj)
|
||||
}
|
||||
|
||||
const defaultTypeMap = new Map([
|
||||
['11', '角色活动跃迁'],
|
||||
['12', '光锥活动跃迁'],
|
||||
['1', '群星跃迁'],
|
||||
['2', '始发跃迁']
|
||||
])
|
||||
|
||||
let localDataReaded = false
|
||||
const readdir = util.promisify(fs.readdir)
|
||||
const readData = async () => {
|
||||
if (localDataReaded) return
|
||||
localDataReaded = true
|
||||
await fs.ensureDir(userDataPath)
|
||||
const files = await readdir(userDataPath)
|
||||
for (let name of files) {
|
||||
if (/^gacha-list-\d+\.json$/.test(name)) {
|
||||
try {
|
||||
const data = await readJSON(name)
|
||||
data.typeMap = new Map(data.typeMap) || defaultTypeMap
|
||||
data.result = new Map(data.result)
|
||||
if (data.uid) {
|
||||
dataMap.set(data.uid, data)
|
||||
}
|
||||
} catch (e) {
|
||||
sendMsg(e, 'ERROR')
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((!config.current && dataMap.size) || (config.current && dataMap.size && !dataMap.has(config.current))) {
|
||||
await changeCurrent(dataMap.keys().next().value)
|
||||
}
|
||||
}
|
||||
|
||||
const changeCurrent = async (uid) => {
|
||||
config.current = uid
|
||||
await config.save()
|
||||
}
|
||||
|
||||
const detectGameLocale = async (userPath) => {
|
||||
let list = []
|
||||
const lang = app.getLocale()
|
||||
const arr = ['/miHoYo/崩坏:星穹铁道/', '/Cognosphere/Star Rail/']
|
||||
arr.forEach(str => {
|
||||
try {
|
||||
const pathname = path.join(userPath, '/AppData/LocalLow/', str, 'Player-prev.log')
|
||||
fs.accessSync(pathname, fs.constants.F_OK)
|
||||
list.push(pathname)
|
||||
} catch (e) {}
|
||||
})
|
||||
if (config.logType) {
|
||||
if (config.logType === 2) {
|
||||
list.reverse()
|
||||
}
|
||||
list = list.slice(0, 1)
|
||||
} else if (lang !== 'zh-CN') {
|
||||
list.reverse()
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
const getLatestUrl = (list) => {
|
||||
let result = list[list.length - 1]
|
||||
let time = 0
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const tsMch = list[i].match(/timestamp=(\d+)/)
|
||||
if (tsMch?.[1]) {
|
||||
const ts = parseInt(tsMch[1])
|
||||
if (time < parseInt(tsMch[1])) {
|
||||
time = ts
|
||||
result = list[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
let cacheFolder = null
|
||||
const readLog = async () => {
|
||||
const text = i18n.log
|
||||
try {
|
||||
let userPath
|
||||
if (!process.env.WINEPREFIX) {
|
||||
userPath = app.getPath('home')
|
||||
} else {
|
||||
userPath = path.join(process.env.WINEPREFIX, 'drive_c/users', process.env.USER)
|
||||
}
|
||||
const logPaths = await detectGameLocale(userPath)
|
||||
if (!logPaths.length) {
|
||||
sendMsg(text.file.notFound)
|
||||
return false
|
||||
}
|
||||
const promises = logPaths.map(async logpath => {
|
||||
const logText = await fs.readFile(logpath, 'utf8')
|
||||
const gamePathMch = logText.match(/\w:\/.+(Star\sRail\/Game\/StarRail_Data)/)
|
||||
if (gamePathMch) {
|
||||
const cacheText = await fs.readFile(path.join(gamePathMch[0], '/webCaches/Cache/Cache_Data/data_2'), 'utf8')
|
||||
const urlMch = cacheText.match(/https.+?&auth_appid=webview_gacha&.+?authkey=.+?&game_biz=hkrpg_.+/g)
|
||||
if (urlMch) {
|
||||
cacheFolder = path.join(gamePathMch[0], '/webCaches/Cache/')
|
||||
return getLatestUrl(urlMch)
|
||||
}
|
||||
}
|
||||
})
|
||||
const result = await Promise.all(promises)
|
||||
for (let url of result) {
|
||||
if (url) {
|
||||
return url
|
||||
}
|
||||
}
|
||||
sendMsg(text.url.notFound)
|
||||
return false
|
||||
} catch (e) {
|
||||
sendMsg(text.file.readFailed)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const getGachaLog = async ({ key, page, name, retryCount, url, endId }) => {
|
||||
const text = i18n.log
|
||||
try {
|
||||
const res = await request(`${url}&gacha_type=${key}&page=${page}&size=${20}${endId ? '&end_id=' + endId : ''}`)
|
||||
return res.data.list
|
||||
} catch (e) {
|
||||
if (retryCount) {
|
||||
sendMsg(i18n.parse(text.fetch.retry, { name, page, count: 6 - retryCount }))
|
||||
await sleep(5)
|
||||
retryCount--
|
||||
return await getGachaLog({ key, page, name, retryCount, url, endId })
|
||||
} else {
|
||||
sendMsg(i18n.parse(text.fetch.retryFailed, { name, page }))
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getGachaLogs = async ({ name, key }, queryString) => {
|
||||
const text = i18n.log
|
||||
let page = 1
|
||||
let list = []
|
||||
let res = []
|
||||
let uid = ''
|
||||
let region = ''
|
||||
let region_time_zone = ''
|
||||
let endId = '0'
|
||||
const url = `${apiDomain}/common/gacha_record/api/getGachaLog?${queryString}`
|
||||
do {
|
||||
if (page % 10 === 0) {
|
||||
sendMsg(i18n.parse(text.fetch.interval, { name, page }))
|
||||
await sleep(1)
|
||||
}
|
||||
sendMsg(i18n.parse(text.fetch.current, { name, page }))
|
||||
res = await getGachaLog({ key, page, name, url, endId, retryCount: 5 })
|
||||
await sleep(0.3)
|
||||
if (!uid && res.length) {
|
||||
uid = res[0].uid
|
||||
}
|
||||
if (!region) {
|
||||
region = res.region
|
||||
}
|
||||
if (!region_time_zone) {
|
||||
region_time_zone = res.region_time_zone
|
||||
}
|
||||
list.push(...res)
|
||||
page += 1
|
||||
|
||||
if (res.length) {
|
||||
endId = res[res.length - 1].id
|
||||
}
|
||||
|
||||
if (!config.fetchFullHistory && res.length && uid && dataMap.has(uid)) {
|
||||
const result = dataMap.get(uid).result
|
||||
if (result.has(key)) {
|
||||
const arr = result.get(key)
|
||||
if (arr.length) {
|
||||
const localLatestId = arr[arr.length - 1].id
|
||||
if (localLatestId) {
|
||||
let shouldBreak = false
|
||||
res.forEach(item => {
|
||||
if (item.id === localLatestId) {
|
||||
shouldBreak = true
|
||||
}
|
||||
})
|
||||
if (shouldBreak) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (res.length > 0)
|
||||
return { list, uid, region, region_time_zone }
|
||||
}
|
||||
|
||||
const checkResStatus = (res) => {
|
||||
const text = i18n.log
|
||||
if (res.retcode !== 0) {
|
||||
let message = res.message
|
||||
if (res.message === 'authkey timeout') {
|
||||
message = text.fetch.authTimeout
|
||||
sendMsg(true, 'AUTHKEY_TIMEOUT')
|
||||
}
|
||||
sendMsg(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
sendMsg(false, 'AUTHKEY_TIMEOUT')
|
||||
return res
|
||||
}
|
||||
|
||||
const tryGetUid = async (queryString) => {
|
||||
const url = `${apiDomain}/common/gacha_record/api/getGachaLog?${queryString}`
|
||||
try {
|
||||
for (let [key] of defaultTypeMap) {
|
||||
const res = await request(`${url}&gacha_type=${key}&page=1&size=6`)
|
||||
checkResStatus(res)
|
||||
if (res.data.list && res.data.list.length) {
|
||||
return res.data.list[0].uid
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return config.current
|
||||
}
|
||||
|
||||
const gachaTypeMap = new Map(JSON.parse('[["de-de",[{"key":"11","name":"Figuren-Aktionswarp"},{"key":"12","name":"Lichtkegel-Aktionswarp"},{"key":"1","name":"Stellarwarp"},{"key":"2","name":"Startwarp"}]],["ru-ru",[{"key":"11","name":"Прыжок события: Персонаж"},{"key":"12","name":"Прыжок события: Световой конус"},{"key":"1","name":"Звёздный Прыжок"},{"key":"2","name":"Отправной Прыжок"}]],["th-th",[{"key":"11","name":"กิจกรรมวาร์ปตัวละคร"},{"key":"12","name":"กิจกรรมวาร์ป Light Cone"},{"key":"1","name":"วาร์ปสู่ดวงดาว"},{"key":"2","name":"ก้าวแรกแห่งการวาร์ป"}]],["zh-cn",[{"key":"11","name":"角色活动跃迁"},{"key":"12","name":"光锥活动跃迁"},{"key":"1","name":"群星跃迁"},{"key":"2","name":"始发跃迁"}]],["zh-tw",[{"key":"11","name":"角色活動躍遷"},{"key":"12","name":"光錐活動躍遷"},{"key":"1","name":"群星躍遷"},{"key":"2","name":"始發躍遷"}]],["en-us",[{"key":"11","name":"Character Event Warp"},{"key":"12","name":"Light Cone Event Warp"},{"key":"1","name":"Stellar Warp"},{"key":"2","name":"Departure Warp"}]],["es-es",[{"key":"11","name":"Salto de evento de personaje"},{"key":"12","name":"Salto de evento de cono de luz"},{"key":"1","name":"Salto estelar"},{"key":"2","name":"Salto de partida"}]],["fr-fr",[{"key":"11","name":"Saut hyperespace événement de personnage"},{"key":"12","name":"Saut hyperespace événement de cônes de lumière"},{"key":"1","name":"Saut stellaire"},{"key":"2","name":"Saut hyperespace de départ"}]],["id-id",[{"key":"11","name":"Event Warp Karakter"},{"key":"12","name":"Event Warp Light Cone"},{"key":"1","name":"Warp Bintang-Bintang"},{"key":"2","name":"Warp Keberangkatan"}]],["ja-jp",[{"key":"11","name":"イベント跳躍・キャラクター"},{"key":"12","name":"イベント跳躍・光円錐"},{"key":"1","name":"群星跳躍"},{"key":"2","name":"始発跳躍"}]],["ko-kr",[{"key":"11","name":"캐릭터 이벤트 워프"},{"key":"12","name":"광추 이벤트 워프"},{"key":"1","name":"뭇별의 워프"},{"key":"2","name":"초행길 워프"}]],["pt-pt",[{"key":"11","name":"Salto Hiperespacial de Evento de Personagem"},{"key":"12","name":"Salto Hiperespacial de Evento de Cone de Luz"},{"key":"1","name":"Salto Hiperespacial Estelar"},{"key":"2","name":"Salto Hiperespacial de Novatos"}]],["vi-vn",[{"key":"11","name":"Bước Nhảy Sự Kiện Nhân Vật"},{"key":"12","name":"Bước Nhảy Sự Kiện Nón Ánh Sáng"},{"key":"1","name":"Bước Nhảy Chòm Sao"},{"key":"2","name":"Bước Nhảy Đầu Tiên"}]]]'))
|
||||
const getGachaType = (lang) => {
|
||||
const locale = detectLocale(lang)
|
||||
return gachaTypeMap.get(locale || lang)
|
||||
}
|
||||
|
||||
const fixAuthkey = (url) => {
|
||||
const mr = url.match(/authkey=([^&]+)/)
|
||||
if (mr && mr[1] && mr[1].includes('=') && !mr[1].includes('%')) {
|
||||
return url.replace(/authkey=([^&]+)/, `authkey=${encodeURIComponent(mr[1])}`)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const getQuerystring = (url) => {
|
||||
const text = i18n.log
|
||||
const { searchParams, host } = new URL(fixAuthkey(url))
|
||||
if (host.includes('webstatic-sea') || host.includes('hkrpg-api-os') || host.includes("api-os-takumi")) {
|
||||
apiDomain = 'https://api-os-takumi.mihoyo.com'
|
||||
} else {
|
||||
apiDomain = 'https://api-takumi.mihoyo.com'
|
||||
}
|
||||
const authkey = searchParams.get('authkey')
|
||||
if (!authkey) {
|
||||
sendMsg(text.url.lackAuth)
|
||||
return false
|
||||
}
|
||||
searchParams.delete('page')
|
||||
searchParams.delete('size')
|
||||
searchParams.delete('gacha_type')
|
||||
searchParams.delete('end_id')
|
||||
return searchParams
|
||||
}
|
||||
|
||||
const proxyServer = (port) => {
|
||||
return new Promise((rev) => {
|
||||
mitmproxy.createProxy({
|
||||
sslConnectInterceptor: (req, cltSocket, head) => {
|
||||
if (/webstatic([^\.]{2,10})?\.(mihoyo|hoyoverse)\.com/.test(req.url)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
requestInterceptor: (rOptions, req, res, ssl, next) => {
|
||||
next()
|
||||
if (/webstatic([^\.]{2,10})?\.(mihoyo|hoyoverse)\.com/.test(rOptions.hostname)) {
|
||||
if (/authkey=[^&]+/.test(rOptions.path)) {
|
||||
rev(`${rOptions.protocol}//${rOptions.hostname}${rOptions.path}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => {
|
||||
next()
|
||||
},
|
||||
getPath: () => path.join(userPath, 'node-mitmproxy'),
|
||||
port
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let proxyServerPromise
|
||||
const useProxy = async () => {
|
||||
const text = i18n.log
|
||||
const ip = localIp()
|
||||
const port = config.proxyPort
|
||||
sendMsg(i18n.parse(text.proxy.hint, { ip, port }))
|
||||
await enableProxy('127.0.0.1', port)
|
||||
if (!proxyServerPromise) {
|
||||
proxyServerPromise = proxyServer(port)
|
||||
}
|
||||
const url = await proxyServerPromise
|
||||
await disableProxy()
|
||||
return url
|
||||
}
|
||||
|
||||
const getUrlFromConfig = () => {
|
||||
if (config.urls.size) {
|
||||
if (config.current && config.urls.has(config.current)) {
|
||||
const url = config.urls.get(config.current)
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tryRequest = async (url, retry = false) => {
|
||||
const queryString = getQuerystring(url)
|
||||
if (!queryString) return false
|
||||
const gachaTypeUrl = `${apiDomain}/common/gacha_record/api/getGachaLog?${queryString}&page=1&size=5&gacha_type=1&end_id=0`
|
||||
try {
|
||||
const res = await request(gachaTypeUrl)
|
||||
if (res.retcode !== 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
if (e.code === 'ERR_PROXY_CONNECTION_FAILED' && !retry) {
|
||||
await disableProxy()
|
||||
return await tryRequest(url, true)
|
||||
}
|
||||
sendMsg(e.message.replace(url, '***'), 'ERROR')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const getUrl = async () => {
|
||||
let url = await readLog()
|
||||
if (!url && config.proxyMode) {
|
||||
url = await useProxy()
|
||||
} else if (url) {
|
||||
const result = await tryRequest(url)
|
||||
if (!result && config.proxyMode) {
|
||||
url = await useProxy()
|
||||
}
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const fetchData = async (urlOverride) => {
|
||||
const text = i18n.log
|
||||
await readData()
|
||||
let url = urlOverride
|
||||
if (!url) {
|
||||
url = await getUrl()
|
||||
}
|
||||
if (!url) {
|
||||
const message = text.url.notFound2
|
||||
sendMsg(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
const searchParams = getQuerystring(url)
|
||||
if (!searchParams) {
|
||||
const message = text.url.incorrect
|
||||
sendMsg(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
let queryString = searchParams.toString()
|
||||
const vUid = await tryGetUid(queryString)
|
||||
const localLang = dataMap.has(vUid) ? dataMap.get(vUid).lang : ''
|
||||
if (localLang) {
|
||||
searchParams.set('lang', localLang)
|
||||
}
|
||||
queryString = searchParams.toString()
|
||||
const gachaType = await getGachaType(searchParams.get('lang'))
|
||||
|
||||
const result = new Map()
|
||||
const typeMap = new Map()
|
||||
const lang = searchParams.get('lang')
|
||||
let originUid = ''
|
||||
let originRegion = ''
|
||||
let originTimeZone = ''
|
||||
for (const type of gachaType) {
|
||||
const { list, uid, region, region_time_zone } = await getGachaLogs(type, queryString)
|
||||
const logs = list.map((item) => {
|
||||
const { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type } = item
|
||||
return { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type }
|
||||
})
|
||||
logs.reverse()
|
||||
typeMap.set(type.key, type.name)
|
||||
result.set(type.key, logs)
|
||||
if (!originUid) {
|
||||
originUid = uid
|
||||
}
|
||||
if (!originRegion) {
|
||||
originRegion = region
|
||||
}
|
||||
if (!originTimeZone) {
|
||||
originTimeZone = region_time_zone
|
||||
}
|
||||
}
|
||||
const data = { result, time: Date.now(), typeMap, uid: originUid, lang, region: originRegion, region_time_zone: originTimeZone }
|
||||
const localData = dataMap.get(originUid)
|
||||
const mergedResult = mergeData(localData, data)
|
||||
data.result = mergedResult
|
||||
dataMap.set(originUid, data)
|
||||
await changeCurrent(originUid)
|
||||
await saveData(data, url)
|
||||
}
|
||||
|
||||
let proxyStarted = false
|
||||
const fetchDataByProxy = async () => {
|
||||
if (proxyStarted) return
|
||||
proxyStarted = true
|
||||
const url = await useProxy()
|
||||
await fetchData(url)
|
||||
}
|
||||
|
||||
ipcMain.handle('FETCH_DATA', async (event, param) => {
|
||||
try {
|
||||
if (param === 'proxy') {
|
||||
await fetchDataByProxy()
|
||||
} else {
|
||||
await fetchData(param)
|
||||
}
|
||||
return {
|
||||
dataMap,
|
||||
current: config.current
|
||||
}
|
||||
} catch (e) {
|
||||
sendMsg(e, 'ERROR')
|
||||
console.error(e)
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
ipcMain.handle('READ_DATA', async () => {
|
||||
await readData()
|
||||
return {
|
||||
dataMap,
|
||||
current: config.current
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('CHANGE_UID', (event, uid) => {
|
||||
config.current = uid
|
||||
})
|
||||
|
||||
ipcMain.handle('GET_CONFIG', () => {
|
||||
return config.value()
|
||||
})
|
||||
|
||||
ipcMain.handle('LANG_MAP', () => {
|
||||
return langMap
|
||||
})
|
||||
|
||||
ipcMain.handle('SAVE_CONFIG', (event, [key, value]) => {
|
||||
config[key] = value
|
||||
config.save()
|
||||
})
|
||||
|
||||
ipcMain.handle('DISABLE_PROXY', async () => {
|
||||
await disableProxy()
|
||||
})
|
||||
|
||||
ipcMain.handle('I18N_DATA', () => {
|
||||
return i18n.data
|
||||
})
|
||||
|
||||
ipcMain.handle('OPEN_CACHE_FOLDER', () => {
|
||||
if (cacheFolder) {
|
||||
shell.openPath(cacheFolder)
|
||||
}
|
||||
})
|
||||
|
||||
exports.getData = () => {
|
||||
return {
|
||||
dataMap,
|
||||
current: config.current
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
const raw = {
|
||||
'zh-cn': require('../i18n/简体中文.json'),
|
||||
'zh-tw': require('../i18n/繁體中文.json'),
|
||||
'de-de': require('../i18n/Deutsch.json'),
|
||||
'en-us': require('../i18n/English.json'),
|
||||
'es-es': require('../i18n/Español.json'),
|
||||
'fr-fr': require('../i18n/Français.json'),
|
||||
'id-id': require('../i18n/Indonesia.json'),
|
||||
'ja-jp': require('../i18n/日本語.json'),
|
||||
'ko-kr': require('../i18n/한국어.json'),
|
||||
'pt-pt': require('../i18n/Português.json'),
|
||||
'ru-ru': require('../i18n/Pусский.json'),
|
||||
'th-th': require('../i18n/ภาษาไทย.json'),
|
||||
'vi-vn': require('../i18n/Tiếng Việt.json')
|
||||
}
|
||||
const config = require('./config')
|
||||
const isPlainObject = require('lodash/isPlainObject')
|
||||
|
||||
const addProp = (obj, key) => {
|
||||
if (isPlainObject(obj[key])) {
|
||||
return obj[key]
|
||||
} else if (typeof obj[key] === 'undefined') {
|
||||
let temp = {}
|
||||
obj[key] = temp
|
||||
return temp
|
||||
}
|
||||
}
|
||||
|
||||
const parseData = (data) => {
|
||||
const result = {}
|
||||
for (let key in data) {
|
||||
let temp = result
|
||||
const arr = key.split('.')
|
||||
arr.forEach((prop, index) => {
|
||||
if (index === arr.length - 1) {
|
||||
temp[prop] = data[key]
|
||||
} else {
|
||||
temp = addProp(temp, prop)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const i18nMap = new Map()
|
||||
const prepareData = () => {
|
||||
for (let key in raw) {
|
||||
let temp = {}
|
||||
if (key === 'zh-tw') {
|
||||
Object.assign(temp, raw['zh-cn'], raw[key])
|
||||
} else {
|
||||
Object.assign(temp, raw['zh-cn'], raw['en-us'], raw[key])
|
||||
}
|
||||
i18nMap.set(key, parseData(temp))
|
||||
}
|
||||
}
|
||||
|
||||
prepareData()
|
||||
|
||||
const parseText = (text, data) => {
|
||||
return text.replace(/(\${.+?})/g, function (...args) {
|
||||
const key = args[0].slice(2, args[0].length - 1)
|
||||
if (data[key]) return data[key]
|
||||
return args[0]
|
||||
})
|
||||
}
|
||||
|
||||
const mainProps = [
|
||||
'symbol', 'ui', 'log', 'excel'
|
||||
]
|
||||
|
||||
const i18n = new Proxy(raw, {
|
||||
get (obj, prop) {
|
||||
if (prop === 'data') {
|
||||
return i18nMap.get(config.lang)
|
||||
} else if (mainProps.includes(prop)) {
|
||||
return i18nMap.get(config.lang)[prop]
|
||||
} else if (prop === 'parse') {
|
||||
return parseText
|
||||
}
|
||||
return obj[prop]
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = i18n
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
const { initWindow } = require('./utils')
|
||||
const { disableProxy, proxyStatus } = require('./module/system-proxy')
|
||||
require('./getData')
|
||||
require('./excel')
|
||||
require('./UIGFJson')
|
||||
const { getUpdateInfo } = require('./update/index')
|
||||
|
||||
const isDev = !app.isPackaged
|
||||
let win = null
|
||||
|
||||
function createWindow() {
|
||||
win = initWindow()
|
||||
win.setMenuBarVisibility(false)
|
||||
isDev ? win.loadURL(`http://localhost:${process.env.PORT}`) : win.loadFile('dist/electron/renderer/index.html')
|
||||
if (isDev) {
|
||||
win.webContents.openDevTools({ mode: 'undocked', activate: true })
|
||||
}
|
||||
}
|
||||
|
||||
const isFirstInstance = app.requestSingleInstanceLock()
|
||||
|
||||
if (!isFirstInstance) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
if (win) {
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
}
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
ipcMain.handle('RELAUNCH', async () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', (e) => {
|
||||
if (proxyStatus.started) {
|
||||
disableProxy()
|
||||
}
|
||||
if (getUpdateInfo().status === 'moving') {
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
app.quit()
|
||||
}, 3000)
|
||||
}
|
||||
})
|
||||
|
||||
app.on('quit', () => {
|
||||
if (proxyStatus.started) {
|
||||
disableProxy()
|
||||
}
|
||||
})
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
/*! ExcelJS 27-10-2020 */
|
||||
|
||||
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).ExcelJS=t()}}((function(){return function t(e,r,n){function i(a,s){if(!r[a]){if(!e[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(o)return o(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var f=r[a]={exports:{}};e[a][0].call(f.exports,(function(t){return i(e[a][1][t]||t)}),f,f.exports,t,e,r,n)}return r[a].exports}for(var o="function"==typeof require&&require,a=0;a<n.length;a++)i(n[a]);return i}({1:[function(t,e,r){"use strict";function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function i(t,e,r,n,i,o,a){try{var s=t[o](a),u=s.value}catch(t){return void r(t)}s.done?e(u):Promise.resolve(u).then(n,i)}function o(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var a=t.apply(e,r);function s(t){i(a,n,o,s,u,"next",t)}function u(t){i(a,n,o,s,u,"throw",t)}s(void 0)}))}}function a(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}var s=t("fs"),u=t("fast-csv"),c=t("dayjs/plugin/customParseFormat"),f=t("dayjs/plugin/utc"),l=t("dayjs").extend(c).extend(f),h=t("../utils/stream-buf"),d=t("../utils/utils").fs.exists,p={true:!0,false:!1,"#N/A":{error:"#N/A"},"#REF!":{error:"#REF!"},"#NAME?":{error:"#NAME?"},"#DIV/0!":{error:"#DIV/0!"},"#NULL!":{error:"#NULL!"},"#VALUE!":{error:"#VALUE!"},"#NUM!":{error:"#NUM!"}},m=function(){function t(e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.workbook=e,this.worksheet=null}var e,r,i,c,f;return e=t,(r=[{key:"readFile",value:(f=o(regeneratorRuntime.mark((function t(e,r){var n,i;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return r=r||{},t.next=3,d(e);case 3:if(t.sent){t.next=5;break}throw new Error("File not found: ".concat(e));case 5:return n=s.createReadStream(e),t.next=8,this.read(n,r);case 8:return i=t.sent,n.close(),t.abrupt("return",i);case 11:case"end":return t.stop()}}),t,this)}))),function(t,e){return f.apply(this,arguments)})},{key:"read",value:function(t,e){var r=this;return e=e||{},new Promise((function(n,i){var o=r.workbook.addWorksheet(e.sheetName),a=e.dateFormats||["YYYY-MM-DD[T]HH:mm:ssZ","YYYY-MM-DD[T]HH:mm:ss","MM-DD-YYYY","YYYY-MM-DD"],s=e.map||function(t){if(""===t)return null;var e=Number(t);if(!Number.isNaN(e)&&e!==1/0)return e;var r=a.reduce((function(e,r){if(e)return e;var n=l(t,r,!0);return n.isValid()?n:null}),null);if(r)return new Date(r.valueOf());var n=p[t];return void 0!==n?n:t},c=u.parse(e.parserOptions).on("data",(function(t){o.addRow(t.map(s))})).on("end",(function(){c.emit("worksheet",o)}));c.on("worksheet",n).on("error",i),t.pipe(c)}))}},{key:"createInputStream",value:function(){throw new Error("`CSV#createInputStream` is deprecated. You should use `CSV#read` instead. This method will be removed in version 5.0. Please follow upgrade instruction: https://github.com/exceljs/exceljs/blob/master/UPGRADE-4.0.md")}},{key:"write",value:function(t,e){var r=this;return new Promise((function(i,o){e=e||{};var a=r.workbook.getWorksheet(e.sheetName||e.sheetId),s=u.format(e.formatterOptions);t.on("finish",(function(){i()})),s.on("error",o),s.pipe(t);var c=e,f=c.dateFormat,h=c.dateUTC,d=e.map||function(t){if(t){if(t.text||t.hyperlink)return t.hyperlink||t.text||"";if(t.formula||t.result)return t.result||"";if(t instanceof Date)return f?h?l.utc(t).format(f):l(t).format(f):h?l.utc(t).format():l(t).format();if(t.error)return t.error;if("object"===n(t))return JSON.stringify(t)}return t},p=void 0===e.includeEmptyRows||e.includeEmptyRows,m=1;a&&a.eachRow((function(t,e){if(p)for(;m++<e-1;)s.write([]);var r=t.values;r.shift(),s.write(r.map(d)),m=e})),s.end()}))}},{key:"writeFile",value:function(t,e){var r={encoding:(e=e||{}).encoding||"utf8"},n=s.createWriteStream(t,r);return this.write(n,e)}},{key:"writeBuffer",value:(c=o(regeneratorRuntime.mark((function t(e){var r;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return r=new h,t.next=3,this.write(r,e);case 3:return t.abrupt("return",r.read());case 4:case"end":return t.stop()}}),t,this)}))),function(t){return c.apply(this,arguments)})}])&&a(e.prototype,r),i&&a(e,i),t}();e.exports=m},{"../utils/stream-buf":23,"../utils/utils":26,dayjs:336,"dayjs/plugin/customParseFormat":337,"dayjs/plugin/utc":338,"fast-csv":369,fs:215}],2:[function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){for(var r=0;r<e.Line truncated
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */var n=t("buffer"),i=n.Buffer;function o(t,e){for(var r in t)e[r]=t[r]}function a(t,e,r){return i(t,e,r)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=n:(o(n,r),r.Buffer=a),a.prototype=Object.create(i.prototype),o(i,a),a.from=function(t,e,r){if("number"==typeof t)throw new TypeError("Argument must not be a number");return i(t,e,r)},a.alloc=function(t,e,r){if("number"!=typeof t)throw new TypeError("Argument must be a number");var n=i(t);return void 0!==e?"string"==typeof r?n.fill(e,r):n.fill(e):n.fill(0),n},a.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return i(t)},a.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return n.SlowBuffer(t)}},{buffer:216}],215:[function(t,e,r){arguments[4][185][0].apply(r,arguments)},{dup:185}],216:[function(t,e,r){(function(e){
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
"use strict";function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var i=t("base64-js"),o=t("ieee754");r.Buffer=e,r.SlowBuffer=function(t){+t!=t&&(t=0);return e.alloc(+t)},r.INSPECT_MAX_BYTES=50;function a(t){if(t>2147483647)throw new RangeError('The value "'+t+'" is invalid for option "size"');var r=new Uint8Array(t);return r.__proto__=e.prototype,r}function e(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return c(t)}return s(t,e,r)}function s(t,r,i){if("string"==typeof t)return function(t,r){"string"==typeof r&&""!==r||(r="utf8");if(!e.isEncoding(r))throw new TypeError("Unknown encoding: "+r);var n=0|h(t,r),i=a(n),o=i.write(t,r);o!==n&&(i=i.slice(0,o));return i}(t,r);if(ArrayBuffer.isView(t))return f(t);if(null==t)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+n(t));if(L(t,ArrayBuffer)||t&&L(t.buffer,ArrayBuffer))return function(t,r,n){if(r<0||t.byteLength<r)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<r+(n||0))throw new RangeError('"length" is outside of buffer bounds');var i;i=void 0===r&&void 0===n?new Uint8Array(t):void 0===n?new Uint8Array(t,r):new Uint8Array(t,r,n);return i.__proto__=e.prototype,i}(t,r,i);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');var o=t.valueOf&&t.valueOf();if(null!=o&&o!==t)return e.from(o,r,i);var s=function(t){if(e.isBuffer(t)){var r=0|l(t.length),n=a(r);return 0===n.length||t.copy(n,0,0,r),n}if(void 0!==t.length)return"number"!=typeof t.length||z(t.length)?a(0):f(t);if("Buffer"===t.type&&Array.isArray(t.data))return f(t.data)}(t);if(s)return s;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return e.from(t[Symbol.toPrimitive]("string"),r,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+n(t))}function u(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function c(t){return u(t),a(t<0?0:0|l(t))}function f(t){for(var e=t.length<0?0:0|l(t.length),r=a(e),n=0;n<e;n+=1)r[n]=255&t[n];return r}function l(t){if(t>=2147483647)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+2147483647..toString(16)+" bytes");return 0|t}function h(t,r){if(e.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||L(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+n(t));var i=t.length,o=arguments.length>2&&!0===arguments[2];if(!o&&0===i)return 0;for(var a=!1;;)switch(r){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":return D(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return B(t).length;default:if(a)return o?-1:D(t).length;r=(""+r).toLowerCase(),a=!0}}function d(t,e,r){var n=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return E(this,e,r);case"utf8":case"utf-8":return S(this,e,r);case"ascii":return O(this,e,r);case"latin1":case"binary":return j(this,e,r);case"base64":return x(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}function p(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}function m(t,r,n,i,o){if(0===t.length)return-1;if("string"==typeof n?(i=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),z(n=+n)&&(n=o?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(o)return-1;n=t.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof r&&(r=e.from(r,i)),e.isBuffer(r))return 0===r.length?-1:y(t,r,n,i,o);if("number"==typeof r)return r&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,r,n):Uint8Array.prototype.lastIndexOf.call(t,r,n):y(t,[r],n,i,o);throw new TypeError("val must be string, number or Buffer")}function y(t,e,r,n,i){var o,a=1,s=t.length,u=e.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||e.length<2)return-1;a=2,s/=2,u/=2,r/=2}function c(t,e){return 1===a?t[e]:t.readUInt16BE(e*a)}if(i){var f=-1;for(o=r;o<s;o++)if(c(t,o)===c(e,-1===f?0:o-f)){if(-1===f&&(f=o),o-f+1===u)return f*a}else-1!==f&&(o-=o-f),f=-1}else for(r+u>s&&(r=s-u),o=r;o>=0;o--){for(var l=!0,h=0;h<u;h++)if(c(t,o+h)!==c(e,h)){l=!1;break}iLine truncated
|
||||
/*!
|
||||
* Determine if an object is a Buffer
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/function n(t){return!!t.constructor&&"function"==typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}e.exports=function(t){return null!=t&&(n(t)||function(t){return"function"==typeof t.readFloatLE&&"function"==typeof t.slice&&n(t.slice(0,0))}(t)||!!t._isBuffer)}},{}],389:[function(t,e,r){"use strict";var n={}.toString;e.exports=Array.isArray||function(t){return"[object Array]"==n.call(t)}},{}],390:[function(t,e,r){"use strict";var n=t("./utils"),i=t("./support"),o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(t){for(var e,r,i,a,s,u,c,f=[],l=0,h=t.length,d=h,p="string"!==n.getTypeOf(t);l<t.length;)d=h-l,p?(e=t[l++],r=l<h?t[l++]:0,i=l<h?t[l++]:0):(e=t.charCodeAt(l++),r=l<h?t.charCodeAt(l++):0,i=l<h?t.charCodeAt(l++):0),a=e>>2,s=(3&e)<<4|r>>4,u=d>1?(15&r)<<2|i>>6:64,c=d>2?63&i:64,f.push(o.charAt(a)+o.charAt(s)+o.charAt(u)+o.charAt(c));return f.join("")},r.decode=function(t){var e,r,n,a,s,u,c=0,f=0;if("data:"===t.substr(0,"data:".length))throw new Error("Invalid base64 input, it looks like a data url.");var l,h=3*(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"")).length/4;if(t.charAt(t.length-1)===o.charAt(64)&&h--,t.charAt(t.length-2)===o.charAt(64)&&h--,h%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=i.uint8array?new Uint8Array(0|h):new Array(0|h);c<t.length;)e=o.indexOf(t.charAt(c++))<<2|(a=o.indexOf(t.charAt(c++)))>>4,r=(15&a)<<4|(s=o.indexOf(t.charAt(c++)))>>2,n=(3&s)<<6|(u=o.indexOf(t.charAt(c++))),l[f++]=e,64!==s&&(l[f++]=r),64!==u&&(l[f++]=n);return l}},{"./support":419,"./utils":421}],391:[function(t,e,r){"use strict";var n=t("./external"),i=t("./stream/DataWorker"),o=t("./stream/DataLengthProbe"),a=t("./stream/Crc32Probe");o=t("./stream/DataLengthProbe");function s(t,e,r,n,i){this.compressedSize=t,this.uncompressedSize=e,this.crc32=r,this.compression=n,this.compressedContent=i}s.prototype={getContentWorker:function(){var t=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new o("data_length")),e=this;return t.on("end",(function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")})),t},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},s.createWorkerFrom=function(t,e,r){return t.pipe(new a).pipe(new o("uncompressedSize")).pipe(e.compressWorker(r)).pipe(new o("compressedSize")).withStreamInfo("compression",e)},e.exports=s},{"./external":395,"./stream/Crc32Probe":414,"./stream/DataLengthProbe":415,"./stream/DataWorker":416}],392:[function(t,e,r){"use strict";var n=t("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(t){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=t("./flate")},{"./flate":396,"./stream/GenericWorker":417}],393:[function(t,e,r){"use strict";var n=t("./utils");var i=function(){for(var t,e=[],r=0;r<256;r++){t=r;for(var n=0;n<8;n++)t=1&t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e){return void 0!==t&&t.length?"string"!==n.getTypeOf(t)?function(t,e,r,n){var o=i,a=n+r;t^=-1;for(var s=n;s<a;s++)t=t>>>8^o[255&(t^e[s])];return-1^t}(0|e,t,t.length,0):function(t,e,r,n){var o=i,a=n+r;t^=-1;for(var s=n;s<a;s++)t=t>>>8^o[255&(t^e.charCodeAt(s))];return-1^t}(0|e,t,t.length,0):0}},{"./utils":421}],394:[function(t,e,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],395:[function(t,e,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:t("lie"),e.exports={Promise:n}},{lie:425}],396:[function(t,e,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=t("pako"),o=t("./utils"),a=t("./stream/GenericWorker"),s=n?"uint8array":"array";function u(t,e){a.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}r.magic="\b\0",o.inherits(u,a),u.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(o.transformTo(s,t.data),!1)},u.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},u.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},u.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(t){return new u("Deflate",t)},r.uncompressWorker=function(){return new u("Inflate",{})}},{"./stream/GenericWorker":417,"./utils":421,pako:439}],397:[function(t,e,r){"use strict";var n=t("../uLine truncated
|
||||
/**
|
||||
* Character classes and associated utilities for the 5th edition of XML 1.0.
|
||||
*
|
||||
* @author Louis-Dominique Dubeau
|
||||
* @license MIT
|
||||
* @copyright Louis-Dominique Dubeau
|
||||
*/Object.defineProperty(r,"__esModule",{value:!0}),r.CHAR="\t\n\r -\ud7ff\ue000-\ufffd\ud800\udc00-\udbff\udfff",r.S=" \t\r\n",r.NAME_START_CHAR=":A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\ud800\udc00-\udb7f\udfff",r.NAME_CHAR="-"+r.NAME_START_CHAR+".0-9\xb7\u0300-\u036f\u203f-\u2040",r.CHAR_RE=new RegExp("^["+r.CHAR+"]$","u"),r.S_RE=new RegExp("^["+r.S+"]+$","u"),r.NAME_START_CHAR_RE=new RegExp("^["+r.NAME_START_CHAR+"]$","u"),r.NAME_CHAR_RE=new RegExp("^["+r.NAME_CHAR+"]$","u"),r.NAME_RE=new RegExp("^["+r.NAME_START_CHAR+"]["+r.NAME_CHAR+"]*$","u"),r.NMTOKEN_RE=new RegExp("^["+r.NAME_CHAR+"]+$","u");function n(t){return t>=65&&t<=90||t>=97&&t<=122||58===t||95===t||8204===t||8205===t||t>=192&&t<=214||t>=216&&t<=246||t>=248&&t<=767||t>=880&&t<=893||t>=895&&t<=8191||t>=8304&&t<=8591||t>=11264&&t<=12271||t>=12289&&t<=55295||t>=63744&&t<=64975||t>=65008&&t<=65533||t>=65536&&t<=983039}r.S_LIST=[32,10,13,9],r.isChar=function(t){return t>=32&&t<=55295||10===t||13===t||9===t||t>=57344&&t<=65533||t>=65536&&t<=1114111},r.isS=function(t){return 32===t||10===t||13===t||9===t},r.isNameStartChar=n,r.isNameChar=function(t){return n(t)||t>=48&&t<=57||45===t||46===t||183===t||t>=768&&t<=879||t>=8255&&t<=8256}},{}],542:[function(t,e,r){"use strict";
|
||||
/**
|
||||
* Character classes and associated utilities for the 2nd edition of XML 1.1.
|
||||
*
|
||||
* @author Louis-Dominique Dubeau
|
||||
* @license MIT
|
||||
* @copyright Louis-Dominique Dubeau
|
||||
*/Object.defineProperty(r,"__esModule",{value:!0}),r.CHAR="\x01-\ud7ff\ue000-\ufffd\ud800\udc00-\udbff\udfff",r.RESTRICTED_CHAR="\x01-\b\v\f\x0e-\x1f\x7f-\x84\x86-\x9f",r.S=" \t\r\n",r.NAME_START_CHAR=":A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\ud800\udc00-\udb7f\udfff",r.NAME_CHAR="-"+r.NAME_START_CHAR+".0-9\xb7\u0300-\u036f\u203f-\u2040",r.CHAR_RE=new RegExp("^["+r.CHAR+"]$","u"),r.RESTRICTED_CHAR_RE=new RegExp("^["+r.RESTRICTED_CHAR+"]$","u"),r.S_RE=new RegExp("^["+r.S+"]+$","u"),r.NAME_START_CHAR_RE=new RegExp("^["+r.NAME_START_CHAR+"]$","u"),r.NAME_CHAR_RE=new RegExp("^["+r.NAME_CHAR+"]$","u"),r.NAME_RE=new RegExp("^["+r.NAME_START_CHAR+"]["+r.NAME_CHAR+"]*$","u"),r.NMTOKEN_RE=new RegExp("^["+r.NAME_CHAR+"]+$","u");function n(t){return t>=65&&t<=90||t>=97&&t<=122||58===t||95===t||8204===t||8205===t||t>=192&&t<=214||t>=216&&t<=246||t>=248&&t<=767||t>=880&&t<=893||t>=895&&t<=8191||t>=8304&&t<=8591||t>=11264&&t<=12271||t>=12289&&t<=55295||t>=63744&&t<=64975||t>=65008&&t<=65533||t>=65536&&t<=983039}r.S_LIST=[32,10,13,9],r.isChar=function(t){return t>=1&&t<=55295||t>=57344&&t<=65533||t>=65536&&t<=1114111},r.isRestrictedChar=function(t){return t>=1&&t<=8||11===t||12===t||t>=14&&t<=31||t>=127&&t<=132||t>=134&&t<=159},r.isCharAndNotRestricted=function(t){return 9===t||10===t||13===t||t>31&&t<127||133===t||t>159&&t<=55295||t>=57344&&t<=65533||t>=65536&&t<=1114111},r.isS=function(t){return 32===t||10===t||13===t||9===t},r.isNameStartChar=n,r.isNameChar=function(t){return n(t)||t>=48&&t<=57||45===t||46===t||183===t||t>=768&&t<=879||t>=8255&&t<=8256}},{}],543:[function(t,e,r){"use strict";
|
||||
/**
|
||||
* Character class utilities for XML NS 1.0 edition 3.
|
||||
*
|
||||
* @author Louis-Dominique Dubeau
|
||||
* @license MIT
|
||||
* @copyright Louis-Dominique Dubeau
|
||||
*/function n(t){return t>=65&&t<=90||95===t||t>=97&&t<=122||t>=192&&t<=214||t>=216&&t<=246||t>=248&&t<=767||t>=880&&t<=893||t>=895&&t<=8191||t>=8204&&t<=8205||t>=8304&&t<=8591||t>=11264&&t<=12271||t>=12289&&t<=55295||t>=63744&&t<=64975||t>=65008&&t<=65533||t>=65536&&t<=983039}Object.defineProperty(r,"__esModule",{value:!0}),r.NC_NAME_START_CHAR="A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\ud800\udc00-\udb7f\udfff",r.NC_NAME_CHAR="-"+r.NC_NAME_START_CHAR+".0-9\xb7\u0300-\u036f\u203f-\u2040",r.NC_NAME_START_CHAR_RE=new RegExp("^["+r.NC_NAME_START_CHAR+"]$","u"),r.NC_NAME_CHAR_RE=new RegExp("^["+r.NC_NAME_CHAR+"]$","u"),r.NC_NAME_RE=new RegExp("^["+r.NC_NAME_START_CHAR+"]["+r.NC_NAME_CHAR+"]*$","u"),r.isNCNameStartChar=n,r.isNCNameChar=function(t){return n(t)||45===t||46===t||t>=48&&t<=57||183===t||t>=768&&t<=879||t>=8255&&t<=8256}},{}]},{},[15])(15)}));
|
||||
//# sourceMappingURL=exceljs.min.js.map
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2014 Max Ogden and other contributors
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// https://github.com/maxogden/extract-zip
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const { createWriteStream, promises: fs } = require('original-fs')
|
||||
const getStream = require('get-stream')
|
||||
const path = require('path')
|
||||
const { promisify } = require('util')
|
||||
const stream = require('stream')
|
||||
const yauzl = require('yauzl')
|
||||
|
||||
const openZip = promisify(yauzl.open)
|
||||
const pipeline = promisify(stream.pipeline)
|
||||
|
||||
class Extractor {
|
||||
constructor (zipPath, opts) {
|
||||
this.zipPath = zipPath
|
||||
this.opts = opts
|
||||
}
|
||||
|
||||
async extract () {
|
||||
|
||||
this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
|
||||
this.canceled = false
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.zipfile.on('error', err => {
|
||||
this.canceled = true
|
||||
reject(err)
|
||||
})
|
||||
this.zipfile.readEntry()
|
||||
|
||||
this.zipfile.on('close', () => {
|
||||
if (!this.canceled) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
this.zipfile.on('entry', async entry => {
|
||||
/* istanbul ignore if */
|
||||
if (this.canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (entry.fileName.startsWith('__MACOSX/')) {
|
||||
this.zipfile.readEntry()
|
||||
return
|
||||
}
|
||||
|
||||
const destDir = path.dirname(path.join(this.opts.dir, entry.fileName))
|
||||
|
||||
try {
|
||||
await fs.mkdir(destDir, { recursive: true })
|
||||
|
||||
const canonicalDestDir = await fs.realpath(destDir)
|
||||
const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir)
|
||||
|
||||
if (relativeDestDir.split(path.sep).includes('..')) {
|
||||
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`)
|
||||
}
|
||||
|
||||
await this.extractEntry(entry)
|
||||
this.zipfile.readEntry()
|
||||
} catch (err) {
|
||||
this.canceled = true
|
||||
this.zipfile.close()
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async extractEntry (entry) {
|
||||
/* istanbul ignore if */
|
||||
if (this.canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.opts.onEntry) {
|
||||
this.opts.onEntry(entry, this.zipfile)
|
||||
}
|
||||
|
||||
const dest = path.join(this.opts.dir, entry.fileName)
|
||||
|
||||
// convert external file attr int into a fs stat mode int
|
||||
const mode = (entry.externalFileAttributes >> 16) & 0xFFFF
|
||||
// check if it's a symlink or dir (using stat mode constants)
|
||||
const IFMT = 61440
|
||||
const IFDIR = 16384
|
||||
const IFLNK = 40960
|
||||
const symlink = (mode & IFMT) === IFLNK
|
||||
let isDir = (mode & IFMT) === IFDIR
|
||||
|
||||
// Failsafe, borrowed from jsZip
|
||||
if (!isDir && entry.fileName.endsWith('/')) {
|
||||
isDir = true
|
||||
}
|
||||
|
||||
// check for windows weird way of specifying a directory
|
||||
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
|
||||
const madeBy = entry.versionMadeBy >> 8
|
||||
if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16)
|
||||
|
||||
|
||||
const procMode = this.getExtractedMode(mode, isDir) & 0o777
|
||||
|
||||
// always ensure folders are created
|
||||
const destDir = isDir ? dest : path.dirname(dest)
|
||||
|
||||
const mkdirOptions = { recursive: true }
|
||||
if (isDir) {
|
||||
mkdirOptions.mode = procMode
|
||||
}
|
||||
await fs.mkdir(destDir, mkdirOptions)
|
||||
if (isDir) return
|
||||
|
||||
const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry)
|
||||
|
||||
if (symlink) {
|
||||
const link = await getStream(readStream)
|
||||
await fs.symlink(link, dest)
|
||||
} else {
|
||||
await pipeline(readStream, createWriteStream(dest, { mode: procMode }))
|
||||
}
|
||||
}
|
||||
|
||||
getExtractedMode (entryMode, isDir) {
|
||||
let mode = entryMode
|
||||
// Set defaults, if necessary
|
||||
if (mode === 0) {
|
||||
if (isDir) {
|
||||
if (this.opts.defaultDirMode) {
|
||||
mode = parseInt(this.opts.defaultDirMode, 10)
|
||||
}
|
||||
|
||||
if (!mode) {
|
||||
mode = 0o755
|
||||
}
|
||||
} else {
|
||||
if (this.opts.defaultFileMode) {
|
||||
mode = parseInt(this.opts.defaultFileMode, 10)
|
||||
}
|
||||
|
||||
if (!mode) {
|
||||
mode = 0o644
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function (zipPath, opts) {
|
||||
|
||||
if (!path.isAbsolute(opts.dir)) {
|
||||
throw new Error('Target directory is expected to be absolute')
|
||||
}
|
||||
|
||||
await fs.mkdir(opts.dir, { recursive: true })
|
||||
opts.dir = await fs.realpath(opts.dir)
|
||||
return new Extractor(zipPath, opts).extract()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// The MIT License (MIT)
|
||||
|
||||
// Copyright (c) 2016 wuchangming
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
var t=require("fs"),e=require("path"),r=require("crypto"),n=require("http"),i=require("util"),a=require("os"),o=require("https"),s=require("url"),u=require("net"),c=require("events"),f=require("tls"),l=require("assert");function h(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var p=h(t),d=h(e),y=h(r),g=h(n),v=h(i),m=h(a),C=h(o),E=h(s),S=h(u),b=h(c),T=h(f),I=h(l),A="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function w(t){var e={exports:{}};return t(e,e.exports),e.exports}var _=w((function(t){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)})),B={}.hasOwnProperty,N=function(t,e){return B.call(t,e)},k=function(t){try{return!!t()}catch(t){return!0}},R=!k((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})),O=w((function(t){var e=t.exports={version:"2.6.12"};"number"==typeof __e&&(__e=e)})),L=function(t){return"object"==typeof t?null!==t:"function"==typeof t},P=function(t){if(!L(t))throw TypeError(t+" is not an object!");return t},U=_.document,x=L(U)&&L(U.createElement),D=function(t){return x?U.createElement(t):{}},V=!R&&!k((function(){return 7!=Object.defineProperty(D("div"),"a",{get:function(){return 7}}).a})),M=function(t,e){if(!L(t))return t;var r,n;if(e&&"function"==typeof(r=t.toString)&&!L(n=r.call(t)))return n;if("function"==typeof(r=t.valueOf)&&!L(n=r.call(t)))return n;if(!e&&"function"==typeof(r=t.toString)&&!L(n=r.call(t)))return n;throw TypeError("Can't convert object to primitive value")},j=Object.defineProperty,F={f:R?Object.defineProperty:function(t,e,r){if(P(t),e=M(e,!0),P(r),V)try{return j(t,e,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[e]=r.value),t}},K=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},q=R?function(t,e,r){return F.f(t,e,K(1,r))}:function(t,e,r){return t[e]=r,t},G=0,z=Math.random(),W=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++G+z).toString(36))},H=w((function(t){var e="__core-js_shared__",r=_[e]||(_[e]={});(t.exports=function(t,e){return r[t]||(r[t]=void 0!==e?e:{})})("versions",[]).push({version:O.version,mode:"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),Q=H("native-function-to-string",Function.toString),Y=w((function(t){var e=W("src"),r="toString",n=(""+Q).split(r);O.inspectSource=function(t){return Q.call(t)},(t.exports=function(t,r,i,a){var o="function"==typeof i;o&&(N(i,"name")||q(i,"name",r)),t[r]!==i&&(o&&(N(i,e)||q(i,e,t[r]?""+t[r]:n.join(String(r)))),t===_?t[r]=i:a?t[r]?t[r]=i:q(t,r,i):(delete t[r],q(t,r,i)))})(Function.prototype,r,(function(){return"function"==typeof this&&this[e]||Q.call(this)}))})),X=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t},$=function(t,e,r){if(X(t),void 0===e)return t;switch(r){case 1:return function(r){return t.call(e,r)};case 2:return function(r,n){return t.call(e,r,n)};case 3:return function(r,n,i){return t.call(e,r,n,i)}}return function(){return t.apply(e,arguments)}},Z=function(t,e,r){var n,i,a,o,s=t&Z.F,u=t&Z.G,c=t&Z.S,f=t&Z.P,l=t&Z.B,h=u?_:c?_[e]||(_[e]={}):(_[e]||{}).prototype,p=u?O:O[e]||(O[e]={}),d=p.prototype||(p.prototype={});for(n in u&&(r=e),r)a=((i=!s&&h&&void 0!==h[n])?h:r)[n],o=l&&i?$(a,_):f&&"function"==typeof a?$(Function.call,a):a,h&&Y(h,n,a,t&Z.U),p[n]!=a&&q(p,n,o),f&&d[n]!=a&&(d[n]=a)};_.core=O,Z.F=1,Z.G=2,Z.S=4,Z.P=8,Z.B=16,Z.W=32,Z.U=64,Z.R=128;var J=Z,tt=w((function(t){var e=W("meta"),r=F.f,n=0,i=Object.isExtensible||function(){return!0},a=!k((function(){return i(Object.preventExtensions({}))})),o=function(t){r(t,e,{value:{i:"O"+ ++n,w:{}}})},s=t.exports={KEY:e,NEED:!1,fastKey:function(t,r){if(!L(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!N(t,e)){if(!i(t))return"F";if(!r)return"E";o(t)}return t[e].i},getWeak:function(t,r){if(!N(t,e)){if(!i(t))return!0;if(!r)return!1;o(t)}return t[e].w},onFreeze:function(t){return a&&s.NEED&&i(t)&&!N(t,e)&&o(t),t}}})),et=w((function(t){var e=H("wks"),r=_.Symbol,n="function"==typeof r;(t.exports=function(t){return e[t]||(e[t]=n&&r[t]||(n?r:W)("Symbol."+t))}).store=e})),rt=F.f,nt=et("toStringTag"),it=function(t,e,r){t&&!N(t=r?t:t.prototype,nt)&&rt(t,nt,{configurable:!0,value:e})},at={f:et},ot=F.f,st=function(t){var e=O.Symbol||(O.Symbol=_.Symbol||{});"_"==t.charAt(0)||t in e||ot(e,t,{value:at.f(t)})},ut={}.toString,ct=function(t){return ut.call(t).slice(8,-1)},ft=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==ct(t)?t.split(""):Object(t)},lt=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},ht=function(t){return ft(lt(t))},pt=Math.ceil,dt=Math.floor,yt=function(t){return isNaN(t=+t)?0:(t>0?dt:pt)(t)},gt=Math.min,vt=function(t){return t>0?gt(yt(t),9007199254740991):0},mt=Math.max,Ct=Math.mLine truncated
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/function Uu(t,e,r,n){"function"==typeof e?(r=e,e={}):e&&"object"==typeof e||(e={mode:e});var i=e.mode,a=e.fs||p.default;void 0===i&&(i=Lu),n||(n=null);var o=r||function(){};t=d.default.resolve(t),a.mkdir(t,i,(function(r){if(!r)return o(null,n=n||t);switch(r.code){case"ENOENT":if(d.default.dirname(t)===t)return o(r);Uu(d.default.dirname(t),e,(function(r,n){r?o(r,n):Uu(t,e,o,n)}));break;default:a.stat(t,(function(t,e){t||!e.isDirectory()?o(r,n):o(null,n)}))}}))}Uu.sync=function t(e,r,n){r&&"object"==typeof r||(r={mode:r});var i=r.mode,a=r.fs||p.default;void 0===i&&(i=Lu),n||(n=null),e=d.default.resolve(e);try{a.mkdirSync(e,i),n=n||e}catch(i){switch(i.code){case"ENOENT":n=t(d.default.dirname(e),r,n),t(e,r,n);break;default:var o;try{o=a.statSync(e)}catch(t){throw i}if(!o.isDirectory())throw i}}return n};var xu={options:{usePureJavaScript:!1}},Du={},Vu=Du,Mu={};Du.encode=function(t,e,r){if("string"!=typeof e)throw new TypeError('"alphabet" must be a string.');if(void 0!==r&&"number"!=typeof r)throw new TypeError('"maxline" must be a number.');var n="";if(t instanceof Uint8Array){var i=0,a=e.length,o=e.charAt(0),s=[0];for(i=0;i<t.length;++i){for(var u=0,c=t[i];u<s.length;++u)c+=s[u]<<8,s[u]=c%a,c=c/a|0;for(;c>0;)s.push(c%a),c=c/a|0}for(i=0;0===t[i]&&i<t.length-1;++i)n+=o;for(i=s.length-1;i>=0;--i)n+=e[s[i]]}else n=function(t,e){var r=0,n=e.length,i=e.charAt(0),a=[0];for(r=0;r<t.length();++r){for(var o=0,s=t.at(r);o<a.length;++o)s+=a[o]<<8,a[o]=s%n,s=s/n|0;for(;s>0;)a.push(s%n),s=s/n|0}var u="";for(r=0;0===t.at(r)&&r<t.length()-1;++r)u+=i;for(r=a.length-1;r>=0;--r)u+=e[a[r]];return u}(t,e);if(r){var f=new RegExp(".{1,"+r+"}","g");n=n.match(f).join("\r\n")}return n},Du.decode=function(t,e){if("string"!=typeof t)throw new TypeError('"input" must be a string.');if("string"!=typeof e)throw new TypeError('"alphabet" must be a string.');var r=Mu[e];if(!r){r=Mu[e]=[];for(var n=0;n<e.length;++n)r[e.charCodeAt(n)]=n}t=t.replace(/\s/g,"");var i=e.length,a=e.charAt(0),o=[0];for(n=0;n<t.length;n++){var s=r[t.charCodeAt(n)];if(void 0===s)return;for(var u=0,c=s;u<o.length;++u)c+=o[u]*i,o[u]=255&c,c>>=8;for(;c>0;)o.push(255&c),c>>=8}for(var f=0;t[f]===a&&f<t.length-1;++f)o.push(0);return"undefined"!=typeof Buffer?Buffer.from(o.reverse()):new Uint8Array(o.reverse())},w((function(t){var e=t.exports=xu.util=xu.util||{};function r(t){if(8!==t&&16!==t&&24!==t&&32!==t)throw new Error("Only 8, 16, 24, or 32 bits supported: "+t)}function n(t){if(this.data="",this.read=0,"string"==typeof t)this.data=t;else if(e.isArrayBuffer(t)||e.isArrayBufferView(t))if("undefined"!=typeof Buffer&&t instanceof Buffer)this.data=t.toString("binary");else{var r=new Uint8Array(t);try{this.data=String.fromCharCode.apply(null,r)}catch(t){for(var i=0;i<r.length;++i)this.putByte(r[i])}}else(t instanceof n||"object"==typeof t&&"string"==typeof t.data&&"number"==typeof t.read)&&(this.data=t.data,this.read=t.read);this._constructedStringLength=0}!function(){if("undefined"!=typeof process&&process.nextTick&&!process.browser)return e.nextTick=process.nextTick,void("function"==typeof setImmediate?e.setImmediate=setImmediate:e.setImmediate=e.nextTick);if("function"==typeof setImmediate)return e.setImmediate=function(){return setImmediate.apply(void 0,arguments)},void(e.nextTick=function(t){return setImmediate(t)});if(e.setImmediate=function(t){setTimeout(t,0)},"undefined"!=typeof window&&"function"==typeof window.postMessage){var t="forge.setImmediate",r=[];e.setImmediate=function(e){r.push(e),1===r.length&&window.postMessage(t,"*")},window.addEventListener("message",(function(e){if(e.source===window&&e.data===t){e.stopPropagation();var n=r.slice();r.length=0,n.forEach((function(t){t()}))}}),!0)}if("undefined"!=typeof MutationObserver){var n=Date.now(),i=!0,a=document.createElement("div");r=[];new MutationObserver((function(){var t=r.slice();r.length=0,t.forEach((function(t){t()}))})).observe(a,{attributes:!0});var o=e.setImmediate;e.setImmediate=function(t){Date.now()-n>15?(n=Date.now(),o(t)):(r.push(t),1===r.length&&a.setAttribute("a",i=!i))}}e.nextTick=e.setImmediate}(),e.isNodejs="undefined"!=typeof process&&process.versions&&process.versions.node,e.globalScope=e.isNodejs?A:"undefined"==typeof self?window:self,e.isArray=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},e.isArrayBuffer=function(t){return"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer},e.isArrayBufferView=function(t){return t&&e.isArrayBuffer(t.buffer)&&void 0!==t.byteLength},e.ByteBuffer=n,e.ByteStringBuffer=n;e.ByteStringBuffer.prototype._optimizeConstructedString=function(t){this._constructedStringLength+=t,this._constructedStringLength>4096&&(this.data.substr(0,1),this._constructedStringLength=0)},e.ByteStringBuffer.prototype.length=function(){return this.data.length-this.read},e.ByteStringBuffer.prototype.isEmpty=function(){return this.length()<=0},e.ByteStringBuffer.prototype.putByte=function(t){return this.putBytes(String.fromCharCode(t))},e.Line truncated
|
||||
@@ -0,0 +1,39 @@
|
||||
const Registry = require('winreg')
|
||||
|
||||
const proxyStatus = {
|
||||
started: false
|
||||
}
|
||||
const setProxy = async (enable, proxyIp = '', ignoreIp = '') => {
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
|
||||
})
|
||||
|
||||
const regSet = function (key, type, value) {
|
||||
return new Promise((rev, rej) => {
|
||||
regKey.set(key, type, value, function (err) {
|
||||
if (err) rej(err)
|
||||
rev()
|
||||
})
|
||||
})
|
||||
}
|
||||
await regSet('ProxyEnable', Registry.REG_DWORD, enable)
|
||||
await regSet('ProxyServer', Registry.REG_SZ, proxyIp)
|
||||
await regSet('ProxyOverride', Registry.REG_SZ, ignoreIp)
|
||||
}
|
||||
|
||||
const enableProxy = async (ip, port) => {
|
||||
const proxyIp = `${ip}:${port}`
|
||||
const ignoreIp = 'localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*;<local>'
|
||||
await setProxy('1', proxyIp, ignoreIp)
|
||||
proxyStatus.started = true
|
||||
}
|
||||
|
||||
const disableProxy = async () => {
|
||||
await setProxy('0')
|
||||
proxyStatus.started = false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
enableProxy, disableProxy, proxyStatus
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
const { app } = require('electron')
|
||||
const fetch = require('electron-fetch').default
|
||||
const semver = require('semver')
|
||||
const util = require('util')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const extract = require('../module/extract-zip')
|
||||
const { version } = require('../../../package.json')
|
||||
const { hash, sendMsg } = require('../utils')
|
||||
const config = require('../config')
|
||||
const i18n = require('../i18n')
|
||||
const streamPipeline = util.promisify(require('stream').pipeline)
|
||||
|
||||
async function download(url, filePath) {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) throw new Error(`unexpected response ${response.statusText}`)
|
||||
await streamPipeline(response.body, fs.createWriteStream(filePath))
|
||||
}
|
||||
|
||||
const updateInfo = {
|
||||
status: 'init'
|
||||
}
|
||||
|
||||
const isDev = !app.isPackaged
|
||||
const appPath = isDev ? path.resolve(__dirname, '../../', 'update-dev/app'): app.getAppPath()
|
||||
const updatePath = isDev ? path.resolve(__dirname, '../../', 'update-dev/download') : path.resolve(appPath, '..', '..', 'update')
|
||||
|
||||
const update = async () => {
|
||||
if (isDev) return
|
||||
try {
|
||||
const url = 'https://star-rail-warp-export.css.moe/update'
|
||||
const res = await fetch(`${url}/manifest.json?t=${Math.floor(Date.now() / (1000 * 60 * 10))}`)
|
||||
const data = await res.json()
|
||||
if (!data.active) return
|
||||
if (semver.gt(data.version, version) && semver.gte(version, data.from)) {
|
||||
await fs.emptyDir(updatePath)
|
||||
const filePath = path.join(updatePath, data.name)
|
||||
if (!config.autoUpdate) {
|
||||
sendMsg(data.version, 'NEW_VERSION')
|
||||
return
|
||||
}
|
||||
updateInfo.status = 'downloading'
|
||||
await download(`${url}/${data.name}`, filePath)
|
||||
const buffer = await fs.readFile(filePath)
|
||||
const sha256 = hash(buffer)
|
||||
if (sha256 !== data.hash) return
|
||||
const appPathTemp = path.join(updatePath, 'app')
|
||||
await extract(filePath, { dir: appPathTemp })
|
||||
updateInfo.status = 'moving'
|
||||
await fs.emptyDir(appPath)
|
||||
await fs.copy(appPathTemp, appPath)
|
||||
updateInfo.status = 'finished'
|
||||
sendMsg(i18n.log.autoUpdate.success, 'UPDATE_HINT')
|
||||
}
|
||||
} catch (e) {
|
||||
updateInfo.status = 'failed'
|
||||
sendMsg(e, 'ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
const getUpdateInfo = () => updateInfo
|
||||
|
||||
setTimeout(update, 1000)
|
||||
|
||||
exports.getUpdateInfo = getUpdateInfo
|
||||
@@ -0,0 +1,207 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const fetch = require('electron-fetch').default
|
||||
const { BrowserWindow, app } = require('electron')
|
||||
const crypto = require('crypto')
|
||||
const unhandled = require('electron-unhandled')
|
||||
const windowStateKeeper = require('electron-window-state')
|
||||
const debounce = require('lodash/debounce')
|
||||
const Registry = require('winreg')
|
||||
|
||||
const isDev = !app.isPackaged
|
||||
|
||||
const userPath = app.getPath('userData')
|
||||
const appRoot = isDev ? path.resolve(__dirname, '..', '..') : userPath
|
||||
const userDataPath = path.resolve(appRoot, 'userData')
|
||||
|
||||
let win = null
|
||||
const initWindow = () => {
|
||||
let mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 888,
|
||||
defaultHeight: 550
|
||||
})
|
||||
win = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
webPreferences: {
|
||||
contextIsolation:false,
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
const saveState = debounce(mainWindowState.saveState, 500)
|
||||
win.on('resize', () => saveState(win))
|
||||
win.on('move', () => saveState(win))
|
||||
return win
|
||||
}
|
||||
|
||||
const getWin = () => win
|
||||
|
||||
const log = []
|
||||
const sendMsg = (text, type = 'LOAD_DATA_STATUS') => {
|
||||
if (win) {
|
||||
win.webContents.send(type, text)
|
||||
}
|
||||
if (type !== 'LOAD_DATA_STATUS') {
|
||||
log.push([Date.now(), type, text])
|
||||
saveLog()
|
||||
}
|
||||
}
|
||||
|
||||
const saveLog = () => {
|
||||
const text = log.map(item => {
|
||||
const time = new Date(item[0]).toLocaleString()
|
||||
const type = item[1] === 'LOAD_DATA_STATUS' ? 'INFO' : item[1]
|
||||
const text = item[2]
|
||||
return `[${type}][${time}]${text}`
|
||||
}).join('\r\n')
|
||||
fs.outputFileSync(path.join(userDataPath, 'log.txt'), text)
|
||||
}
|
||||
|
||||
const authkeyMask = (text = '') => {
|
||||
return text.replace(/authkey=[^&]+&/g, 'authkey=***&')
|
||||
}
|
||||
|
||||
unhandled({
|
||||
showDialog: false,
|
||||
logger: function (err) {
|
||||
log.push([Date.now(), 'ERROR', authkeyMask(err.stack)])
|
||||
saveLog()
|
||||
}
|
||||
})
|
||||
|
||||
const request = async (url) => {
|
||||
const res = await fetch(url, {
|
||||
timeout: 15 * 1000
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
const sleep = (sec = 1) => {
|
||||
return new Promise(rev => {
|
||||
setTimeout(rev, sec * 1000)
|
||||
})
|
||||
}
|
||||
|
||||
const sortData = (data) => {
|
||||
return data.map(item => {
|
||||
const [time, name, type, rank] = item
|
||||
return {
|
||||
time, name, type, rank,
|
||||
timestamp: new Date(time)
|
||||
}
|
||||
}).sort((a, b) => a.timestamp - b.timestamp)
|
||||
.map(item => {
|
||||
const { time, name, type, rank } = item
|
||||
return [time, name, type, rank]
|
||||
})
|
||||
}
|
||||
|
||||
const langMap = new Map([
|
||||
['zh-cn', '简体中文'],
|
||||
['zh-tw', '繁體中文'],
|
||||
['de-de', 'Deutsch'],
|
||||
['en-us', 'English'],
|
||||
['es-es', 'Español'],
|
||||
['fr-fr', 'Français'],
|
||||
['id-id', 'Indonesia'],
|
||||
['ja-jp', '日本語'],
|
||||
['ko-kr', '한국어'],
|
||||
['pt-pt', 'Português'],
|
||||
['ru-ru', 'Pусский'],
|
||||
['th-th', 'ภาษาไทย'],
|
||||
['vi-vn', 'Tiếng Việt']
|
||||
])
|
||||
|
||||
const localeMap = new Map([
|
||||
['zh-cn', ['zh', 'zh-CN']],
|
||||
['zh-tw', ['zh-TW']],
|
||||
['de-de', ['de-AT', 'de-CH', 'de-DE', 'de']],
|
||||
['en-us', ['en-AU', 'en-CA', 'en-GB', 'en-NZ', 'en-US', 'en-ZA', 'en']],
|
||||
['es-es', ['es', 'es-419']],
|
||||
['fr-fr', ['fr-CA', 'fr-CH', 'fr-FR', 'fr']],
|
||||
['id-id', ['id']],
|
||||
['ja-jp', ['ja']],
|
||||
['ko-kr', ['ko']],
|
||||
['pt-pt', ['pt-BR', 'pt-PT', 'pt']],
|
||||
['ru-ru', ['ru']],
|
||||
['th-th', ['th']],
|
||||
['vi-vn', ['vi']]
|
||||
])
|
||||
|
||||
const detectLocale = (value) => {
|
||||
const locale = value || app.getLocale()
|
||||
let result = 'zh-cn'
|
||||
for (let [key, list] of localeMap) {
|
||||
if (list.includes(locale)) {
|
||||
result = key
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const saveJSON = async (name, data) => {
|
||||
try {
|
||||
await fs.outputJSON(path.join(userDataPath, name), data, {
|
||||
spaces: 2
|
||||
})
|
||||
} catch (e) {
|
||||
sendMsg(e, 'ERROR')
|
||||
await sleep(3)
|
||||
}
|
||||
}
|
||||
|
||||
const readJSON = async (name) => {
|
||||
let data = null
|
||||
try {
|
||||
data = await fs.readJSON(path.join(userDataPath, name))
|
||||
} catch (e) {}
|
||||
return data
|
||||
}
|
||||
|
||||
const hash = (data, type = 'sha256') => {
|
||||
const hmac = crypto.createHmac(type, 'hk4e')
|
||||
hmac.update(data)
|
||||
return hmac.digest('hex')
|
||||
}
|
||||
|
||||
const scryptKey = crypto.scryptSync(userPath, 'hk4e', 24)
|
||||
const cipherAes = (data) => {
|
||||
const algorithm = 'aes-192-cbc'
|
||||
const iv = Buffer.alloc(16, 0)
|
||||
const cipher = crypto.createCipheriv(algorithm, scryptKey, iv)
|
||||
let encrypted = cipher.update(data, 'utf8', 'hex')
|
||||
encrypted += cipher.final('hex')
|
||||
return encrypted
|
||||
}
|
||||
|
||||
const decipherAes = (encrypted) => {
|
||||
const algorithm = 'aes-192-cbc'
|
||||
const iv = Buffer.alloc(16, 0)
|
||||
const decipher = crypto.createDecipheriv(algorithm, scryptKey, iv)
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
|
||||
decrypted += decipher.final('utf8')
|
||||
return decrypted
|
||||
}
|
||||
|
||||
const interfaces = require('os').networkInterfaces()
|
||||
const localIp = () => {
|
||||
for (var devName in interfaces) {
|
||||
var iface = interfaces[devName]
|
||||
|
||||
for (var i = 0; i < iface.length; i++) {
|
||||
var alias = iface[i]
|
||||
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal)
|
||||
return alias.address
|
||||
}
|
||||
}
|
||||
return '127.0.0.1'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep, request, hash, cipherAes, decipherAes, saveLog,
|
||||
sendMsg, readJSON, saveJSON, initWindow, getWin, localIp, userPath, detectLocale, langMap,
|
||||
appRoot, userDataPath
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
const mergeList = (a, b) => {
|
||||
if (!a || !a.length) return b || []
|
||||
if (!b || !b.length) return a
|
||||
const list = [...b, ...a]
|
||||
const result = []
|
||||
const idSet = new Set()
|
||||
list.forEach(item => {
|
||||
if (!idSet.has(item.id)) {
|
||||
result.push(item)
|
||||
}
|
||||
idSet.add(item.id)
|
||||
})
|
||||
return result.sort((m, n) => {
|
||||
const num = BigInt(m.id) - BigInt(n.id)
|
||||
if (num > 0) {
|
||||
return 1
|
||||
} else if (num < 0) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const mergeData = (local, origin) => {
|
||||
if (local && local.result) {
|
||||
const localResult = local.result
|
||||
const localUid = local.uid
|
||||
const originUid = origin.uid
|
||||
if (localUid !== originUid) return origin.result
|
||||
const originResult = new Map()
|
||||
for (let [key, value] of origin.result) {
|
||||
const newVal = mergeList(localResult.get(key), value)
|
||||
originResult.set(key, newVal)
|
||||
}
|
||||
return originResult
|
||||
}
|
||||
return origin.result
|
||||
}
|
||||
|
||||
module.exports = { mergeData, mergeList }
|
||||
@@ -0,0 +1,72 @@
|
||||
const { mergeList } = require('./mergeData')
|
||||
|
||||
test('mergeList successed', () => {
|
||||
const listA = [{
|
||||
"id": "1682521800010412850",
|
||||
},
|
||||
{
|
||||
"id": "1682521800010412950",
|
||||
}]
|
||||
|
||||
const listB = [{
|
||||
"id": "1682521800010412900",
|
||||
}]
|
||||
|
||||
expect(mergeList(listA, listB)).toEqual([
|
||||
{
|
||||
"id": "1682521800010412850",
|
||||
},
|
||||
{
|
||||
"id": "1682521800010412900",
|
||||
},
|
||||
{
|
||||
"id": "1682521800010412950",
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('mergeList with repeated data successed', () => {
|
||||
const listA = [{
|
||||
"id": "1682521800010412850",
|
||||
},
|
||||
{
|
||||
"id": "1682521800010412950",
|
||||
}]
|
||||
|
||||
const listB = [{
|
||||
"id": "1682521800010412950",
|
||||
}]
|
||||
|
||||
expect(mergeList(listA, listB)).toEqual([
|
||||
{
|
||||
"id": "1682521800010412850",
|
||||
},
|
||||
{
|
||||
"id": "1682521800010412950",
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('mergeList empty successed', () => {
|
||||
const listA = []
|
||||
const listB = [{
|
||||
"id": "1682521800010412900",
|
||||
}]
|
||||
expect(mergeList(listA, listB)).toEqual([
|
||||
{
|
||||
"id": "1682521800010412900",
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('mergeList empty 2 successed', () => {
|
||||
const listA = [{
|
||||
"id": "1682521800010412900",
|
||||
}]
|
||||
const listB = []
|
||||
expect(mergeList(listA, listB)).toEqual([
|
||||
{
|
||||
"id": "1682521800010412900",
|
||||
}
|
||||
])
|
||||
})
|
||||
Reference in new issue
Block a user