feat: add aihubmix oauth

This commit is contained in:
kangfenmao 2025-02-07 22:27:28 +08:00
parent 6a30eec5b4
commit 280ec3377b
12 changed files with 56 additions and 21 deletions

View File

@ -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)
)
}

View File

@ -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 {

View File

@ -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')

View File

@ -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>
}
}
}
}

View File

@ -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)
}
}

View File

@ -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"
}
}
}

View File

@ -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キーの自動取得に失敗しました。手動で取得してください"
}
}
}

View File

@ -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 не удалось, пожалуйста, получите ключ вручную"
}
}
}

View File

@ -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": "自动获取密钥失败,请手动获取"
}
}
}

View File

@ -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": "自動獲取密鑰失敗,請手動獲取"
}
}
}

View File

@ -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)
}

View File

@ -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'))
}
}
}