mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 13:10:30 +08:00
196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
import { getControledMihomoConfig } from './controledMihomo'
|
|
import { profileConfigPath, profilePath } from '../utils/dirs'
|
|
import { startCore } from '../core/manager'
|
|
import { getAppConfig } from './app'
|
|
import { window } from '..'
|
|
import axios from 'axios'
|
|
import yaml from 'yaml'
|
|
import fs from 'fs'
|
|
import { dialog } from 'electron'
|
|
|
|
let profileConfig: IProfileConfig // profile.yaml
|
|
let currentProfile: Partial<IMihomoConfig> // profiles/xxx.yaml
|
|
|
|
export function getProfileConfig(force = false): IProfileConfig {
|
|
if (force || !profileConfig) {
|
|
profileConfig = yaml.parse(fs.readFileSync(profileConfigPath(), 'utf-8'))
|
|
}
|
|
return profileConfig
|
|
}
|
|
|
|
export function getProfileItem(id: string | undefined): IProfileItem {
|
|
const items = getProfileConfig().items
|
|
return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' }
|
|
}
|
|
|
|
export async function changeCurrentProfile(id: string): Promise<void> {
|
|
const oldId = getProfileConfig().current
|
|
profileConfig.current = id
|
|
getCurrentProfile(true)
|
|
try {
|
|
await startCore()
|
|
} catch (e) {
|
|
profileConfig.current = oldId
|
|
getCurrentProfile(true)
|
|
} finally {
|
|
window?.webContents.send('profileConfigUpdated')
|
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
|
}
|
|
}
|
|
|
|
export function updateProfileItem(item: IProfileItem): void {
|
|
const index = profileConfig.items.findIndex((i) => i.id === item.id)
|
|
profileConfig.items[index] = item
|
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
|
window?.webContents.send('profileConfigUpdated')
|
|
}
|
|
|
|
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
|
const newItem = await createProfile(item)
|
|
if (profileConfig.items.find((i) => i.id === newItem.id)) {
|
|
updateProfileItem(newItem)
|
|
} else {
|
|
profileConfig.items.push(newItem)
|
|
}
|
|
|
|
if (!getProfileConfig().current) {
|
|
changeCurrentProfile(newItem.id)
|
|
}
|
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
|
window?.webContents.send('profileConfigUpdated')
|
|
}
|
|
|
|
export function removeProfileItem(id: string): void {
|
|
profileConfig.items = profileConfig.items?.filter((item) => item.id !== id)
|
|
if (profileConfig.current === id) {
|
|
if (profileConfig.items.length > 0) {
|
|
profileConfig.current = profileConfig.items[0]?.id
|
|
} else {
|
|
profileConfig.current = undefined
|
|
}
|
|
}
|
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
|
fs.rmSync(profilePath(id))
|
|
window?.webContents.send('profileConfigUpdated')
|
|
}
|
|
|
|
export function getCurrentProfileItem(): IProfileItem {
|
|
return getProfileItem(getProfileConfig().current)
|
|
}
|
|
|
|
// attachment;filename=xxx.yaml; filename*=UTF-8''%xx%xx%xx
|
|
function parseFilename(str: string): string {
|
|
if (str.includes("filename*=UTF-8''")) {
|
|
const filename = decodeURIComponent(str.split("filename*=UTF-8''")[1])
|
|
return filename
|
|
} else {
|
|
const filename = str.split('filename=')[1]
|
|
return filename
|
|
}
|
|
}
|
|
|
|
// subscription-userinfo: upload=1234; download=2234; total=1024000; expire=2218532293
|
|
function parseSubinfo(str: string): ISubscriptionUserInfo {
|
|
const parts = str.split('; ')
|
|
const obj = {} as ISubscriptionUserInfo
|
|
parts.forEach((part) => {
|
|
const [key, value] = part.split('=')
|
|
obj[key] = parseInt(value)
|
|
})
|
|
return obj
|
|
}
|
|
|
|
export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
|
|
const id = item.id || new Date().getTime().toString(16)
|
|
const newItem = {
|
|
id,
|
|
name: item.name || item.type === 'remote' ? 'Remote File' : 'Local File',
|
|
type: item.type || 'local',
|
|
url: item.url,
|
|
interval: item.interval || 0,
|
|
updated: new Date().getTime()
|
|
} as IProfileItem
|
|
switch (newItem.type) {
|
|
case 'remote': {
|
|
if (!item.url) {
|
|
dialog.showErrorBox(
|
|
'URL is required for remote profile',
|
|
'URL is required for remote profile'
|
|
)
|
|
throw new Error('URL is required for remote profile')
|
|
}
|
|
try {
|
|
const ua = getAppConfig().userAgent || 'clash-meta'
|
|
const res = await axios.get(item.url, {
|
|
proxy: {
|
|
protocol: 'http',
|
|
host: '127.0.0.1',
|
|
port: getControledMihomoConfig()['mixed-port'] || 7890
|
|
},
|
|
headers: {
|
|
'User-Agent': ua
|
|
},
|
|
responseType: 'text'
|
|
})
|
|
const data = res.data
|
|
const headers = res.headers
|
|
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
|
newItem.name = parseFilename(headers['content-disposition'])
|
|
}
|
|
if (headers['profile-web-page-url']) {
|
|
newItem.home = headers['profile-web-page-url']
|
|
}
|
|
if (headers['profile-update-interval']) {
|
|
newItem.interval = parseInt(headers['profile-update-interval']) * 60
|
|
}
|
|
if (headers['subscription-userinfo']) {
|
|
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
|
}
|
|
setProfileStr(id, data)
|
|
} catch (e) {
|
|
dialog.showErrorBox('Failed to fetch remote profile', `${e}\nurl: ${item.url}`)
|
|
throw new Error(`Failed to fetch remote profile ${e}`)
|
|
}
|
|
break
|
|
}
|
|
case 'local': {
|
|
if (!item.file) {
|
|
dialog.showErrorBox(
|
|
'File is required for local profile',
|
|
'File is required for local profile'
|
|
)
|
|
throw new Error('File is required for local profile')
|
|
}
|
|
const data = item.file
|
|
setProfileStr(id, data)
|
|
break
|
|
}
|
|
}
|
|
|
|
return newItem
|
|
}
|
|
|
|
export function getProfileStr(id: string): string {
|
|
return fs.readFileSync(profilePath(id), 'utf-8')
|
|
}
|
|
|
|
export async function setProfileStr(id: string, content: string): Promise<void> {
|
|
fs.writeFileSync(profilePath(id), content, 'utf-8')
|
|
if (id === getProfileConfig().current) {
|
|
getCurrentProfile(true)
|
|
await startCore()
|
|
}
|
|
}
|
|
|
|
export function getCurrentProfile(force = false): Partial<IMihomoConfig> {
|
|
if (force || !currentProfile) {
|
|
const current = getProfileConfig().current
|
|
if (current) {
|
|
currentProfile = yaml.parse(getProfileStr(current))
|
|
} else {
|
|
currentProfile = yaml.parse(getProfileStr('default'))
|
|
}
|
|
}
|
|
return currentProfile
|
|
}
|