feat: add aihubmix oauth
This commit is contained in:
parent
6a30eec5b4
commit
280ec3377b
@ -18,6 +18,8 @@ import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutSe
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { getResourcePath } from './utils'
|
||||
import { decrypt } from './utils/aes'
|
||||
import { encrypt } from './utils/aes'
|
||||
import { compress, decompress } from './utils/zip'
|
||||
|
||||
const fileManager = new FileStorage()
|
||||
@ -199,4 +201,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow())
|
||||
ipcMain.handle('miniwindow:close', () => windowService.closeMiniWindow())
|
||||
ipcMain.handle('miniwindow:toggle', () => windowService.toggleMiniWindow())
|
||||
|
||||
// aes
|
||||
ipcMain.handle('aes:encrypt', (_, text: string, secretKey: string, iv: string) => encrypt(text, secretKey, iv))
|
||||
ipcMain.handle('aes:decrypt', (_, encryptedData: string, iv: string, secretKey: string) =>
|
||||
decrypt(encryptedData, iv, secretKey)
|
||||
)
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ export class WindowService {
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
const { url } = details
|
||||
|
||||
const oauthProviderUrls = ['https://account.siliconflow.cn/oauth']
|
||||
const oauthProviderUrls = ['https://account.siliconflow.cn/oauth', 'https://aihubmix.com/oauth']
|
||||
|
||||
if (oauthProviderUrls.some((link) => url.startsWith(link))) {
|
||||
return {
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
import * as crypto from 'crypto'
|
||||
|
||||
// 定义密钥和初始化向量(IV)
|
||||
const secretKey = 'kDQvWz5slot3syfucoo53X6KKsEUJoeFikpiUWRJTLIo3zcUPpFvEa009kK13KCr'
|
||||
const iv = Buffer.from('Cherry Studio', 'hex')
|
||||
|
||||
// 加密函数
|
||||
export function encrypt(text: string): { iv: string; encryptedData: string } {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(secretKey), iv)
|
||||
export function encrypt(text: string, secretKey: string, iv: string): { iv: string; encryptedData: string } {
|
||||
const _iv = Buffer.from(iv, 'hex')
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(secretKey), _iv)
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex')
|
||||
encrypted += cipher.final('hex')
|
||||
return {
|
||||
iv: iv.toString('hex'),
|
||||
iv: _iv.toString('hex'),
|
||||
encryptedData: encrypted
|
||||
}
|
||||
}
|
||||
|
||||
// 解密函数
|
||||
export function decrypt(encryptedData: string, iv: string): string {
|
||||
export function decrypt(encryptedData: string, iv: string, secretKey: string): string {
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(secretKey), Buffer.from(iv, 'hex'))
|
||||
let decrypted = decipher.update(encryptedData, 'hex', 'utf8')
|
||||
decrypted += decipher.final('utf8')
|
||||
|
||||
4
src/preload/index.d.ts
vendored
4
src/preload/index.d.ts
vendored
@ -106,6 +106,10 @@ declare global {
|
||||
close: () => Promise<void>
|
||||
toggle: () => Promise<void>
|
||||
}
|
||||
aes: {
|
||||
encrypt: (text: string, secretKey: string, iv: string) => Promise<{ iv: string; encryptedData: string }>
|
||||
decrypt: (encryptedData: string, iv: string, secretKey: string) => Promise<string>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +99,11 @@ const api = {
|
||||
hide: () => ipcRenderer.invoke('miniwindow:hide'),
|
||||
close: () => ipcRenderer.invoke('miniwindow:close'),
|
||||
toggle: () => ipcRenderer.invoke('miniwindow:toggle')
|
||||
},
|
||||
aes: {
|
||||
encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke('aes:encrypt', text, secretKey, iv),
|
||||
decrypt: (encryptedData: string, iv: string, secretKey: string) =>
|
||||
ipcRenderer.invoke('aes:decrypt', encryptedData, iv, secretKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -501,6 +501,7 @@
|
||||
"docs_check": "Check",
|
||||
"docs_more_details": "for more details",
|
||||
"get_api_key": "Get API Key",
|
||||
"charge": "Charge",
|
||||
"no_models": "Please add models first before checking the API connection",
|
||||
"not_checked": "Not Checked",
|
||||
"remove_duplicate_keys": "Remove Duplicate Keys",
|
||||
@ -703,7 +704,8 @@
|
||||
"oauth_button": "Auth with {{provider}}",
|
||||
"get_key": "Get",
|
||||
"get_key_success": "API key automatically obtained successfully",
|
||||
"login": "Login"
|
||||
"login": "Login",
|
||||
"error": "API key automatically obtained failed, please get it manually"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,6 +492,7 @@
|
||||
"docs_check": "チェック",
|
||||
"docs_more_details": "詳細を確認",
|
||||
"get_api_key": "APIキーを取得",
|
||||
"charge": "充電",
|
||||
"no_models": "API接続をチェックする前に、モデルを追加してください",
|
||||
"not_checked": "未チェック",
|
||||
"remove_duplicate_keys": "重複キーを削除",
|
||||
@ -683,7 +684,8 @@
|
||||
"oauth_button": "{{provider}}で認証",
|
||||
"get_key": "取得",
|
||||
"get_key_success": "APIキーの自動取得に成功しました",
|
||||
"login": "認証"
|
||||
"login": "認証",
|
||||
"error": "APIキーの自動取得に失敗しました。手動で取得してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +493,7 @@
|
||||
"docs_check": "Проверить",
|
||||
"docs_more_details": "для получения дополнительной информации",
|
||||
"get_api_key": "Получить ключ API",
|
||||
"charge": "Пополнить",
|
||||
"no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API",
|
||||
"not_checked": "Не проверено",
|
||||
"remove_duplicate_keys": "Удалить дубликаты ключей",
|
||||
@ -695,7 +696,8 @@
|
||||
"oauth_button": "Авторизоваться с {{provider}}",
|
||||
"get_key": "Получить",
|
||||
"get_key_success": "Автоматический получение ключа API успешно",
|
||||
"login": "Войти"
|
||||
"login": "Войти",
|
||||
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,6 +499,7 @@
|
||||
"docs_check": "查看",
|
||||
"docs_more_details": "获取更多详情",
|
||||
"get_api_key": "点击这里获取密钥",
|
||||
"charge": "充值",
|
||||
"no_models": "请先添加模型再检查 API 连接",
|
||||
"not_checked": "未检查",
|
||||
"remove_duplicate_keys": "移除重复密钥",
|
||||
@ -690,7 +691,8 @@
|
||||
"oauth_button": "使用{{provider}}登录",
|
||||
"get_key": "获取",
|
||||
"get_key_success": "自动获取密钥成功",
|
||||
"login": "登录"
|
||||
"login": "登录",
|
||||
"error": "自动获取密钥失败,请手动获取"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,6 +498,7 @@
|
||||
"docs_check": "檢查",
|
||||
"docs_more_details": "查看更多細節",
|
||||
"get_api_key": "點擊這裡獲取密鑰",
|
||||
"charge": "充值",
|
||||
"no_models": "請先添加模型再檢查 API 連接",
|
||||
"not_checked": "未檢查",
|
||||
"remove_duplicate_keys": "移除重複密鑰",
|
||||
@ -689,7 +690,8 @@
|
||||
"oauth_button": "使用{{provider}}登入",
|
||||
"get_key": "獲取",
|
||||
"get_key_success": "自動獲取密鑰成功",
|
||||
"login": "登入"
|
||||
"login": "登入",
|
||||
"error": "自動獲取密鑰失敗,請手動獲取"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,6 @@ export function getProviderName(id: string) {
|
||||
}
|
||||
|
||||
export function isProviderSupportAuth(provider: Provider) {
|
||||
const supportProviders = ['silicon']
|
||||
const supportProviders = ['silicon', 'aihubmix']
|
||||
return supportProviders.includes(provider.id)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { SILICON_CLIENT_ID } from '@renderer/config/constant'
|
||||
import { getLanguageCode } from '@renderer/i18n'
|
||||
import i18n from '@renderer/i18n'
|
||||
export const oauthWithSiliconFlow = async (setKey) => {
|
||||
const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}`
|
||||
|
||||
@ -22,7 +23,7 @@ export const oauthWithSiliconFlow = async (setKey) => {
|
||||
}
|
||||
|
||||
export const oauthWithAihubmix = async (setKey) => {
|
||||
const authUrl = `https://aihubmix.com/login?cherry_studio_oauth=true&lang=${getLanguageCode()}&aff=SJyh`
|
||||
const authUrl = ` https://aihubmix.com/oauth?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh`
|
||||
|
||||
const popup = window.open(
|
||||
authUrl,
|
||||
@ -30,15 +31,25 @@ export const oauthWithAihubmix = async (setKey) => {
|
||||
'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes'
|
||||
)
|
||||
|
||||
const messageHandler = (event) => {
|
||||
const messageHandler = async (event) => {
|
||||
const data = event.data
|
||||
|
||||
if (data && data.key === 'cherry_studio_oauth_callback') {
|
||||
const apiKeys = data?.data?.apiKeys
|
||||
if (apiKeys && apiKeys.length > 0) {
|
||||
setKey(apiKeys[0].value)
|
||||
const { iv, encryptedData } = data.data
|
||||
|
||||
try {
|
||||
const secret = import.meta.env.RENDERER_VITE_AIHUBMIX_SECRET || ''
|
||||
const decryptedData: any = await window.api.aes.decrypt(encryptedData, iv, secret)
|
||||
const { api_keys } = JSON.parse(decryptedData)
|
||||
if (api_keys && api_keys.length > 0) {
|
||||
setKey(api_keys[0].value)
|
||||
popup?.close()
|
||||
window.removeEventListener('message', messageHandler)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[oauthWithAihubmix] error', error)
|
||||
popup?.close()
|
||||
window.removeEventListener('message', messageHandler)
|
||||
window.message.error(i18n.t('oauth.error'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user