diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx
similarity index 98%
rename from clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx
rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx
index 3c0fada3a3..2de898b72e 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx
@@ -24,7 +24,7 @@ import {
import { alpha, cn, SidePage } from '@nyanpasu/ui'
import { createFileRoute } from '@tanstack/react-router'
-export const Route = createFileRoute('/proxies')({
+export const Route = createFileRoute('/(legacy)/proxies')({
component: ProxyPage,
})
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx
new file mode 100644
index 0000000000..9c1e3bfb99
--- /dev/null
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx
@@ -0,0 +1,78 @@
+import AppContainer from '@/components/app/app-container'
+import LocalesProvider from '@/components/app/locales-provider'
+import MutationProvider from '@/components/layout/mutation-provider'
+import NoticeProvider from '@/components/layout/notice-provider'
+import PageTransition from '@/components/layout/page-transition'
+import SchemeProvider from '@/components/layout/scheme-provider'
+import UpdaterDialog from '@/components/updater/updater-dialog-wrapper'
+import { UpdaterProvider } from '@/hooks/use-updater'
+import { FileRouteTypes } from '@/route-tree.gen'
+import { atomIsDrawer, memorizedRoutePathAtom } from '@/store'
+import { useSettings } from '@nyanpasu/interface'
+import { cn, useBreakpoint } from '@nyanpasu/ui'
+import { createFileRoute, useLocation } from '@tanstack/react-router'
+import 'dayjs/locale/ru'
+import 'dayjs/locale/zh-cn'
+import 'dayjs/locale/zh-tw'
+import { useAtom, useSetAtom } from 'jotai'
+import { PropsWithChildren, useEffect } from 'react'
+import { SWRConfig } from 'swr'
+
+export const Route = createFileRoute('/(legacy)')({
+ component: Layout,
+})
+
+const QueryLoaderProvider = ({ children }: PropsWithChildren) => {
+ const {
+ query: { isLoading },
+ } = useSettings()
+
+ return isLoading ? null : children
+}
+
+function Layout() {
+ const breakpoint = useBreakpoint()
+
+ const setMemorizedPath = useSetAtom(memorizedRoutePathAtom)
+
+ const pathname = useLocation({
+ select: (location) => location.pathname,
+ })
+
+ useEffect(() => {
+ if (pathname !== '/') {
+ setMemorizedPath(pathname as FileRouteTypes['fullPaths'])
+ }
+ }, [pathname, setMemorizedPath])
+
+ const [isDrawer, setIsDrawer] = useAtom(atomIsDrawer)
+
+ useEffect(() => {
+ setIsDrawer(breakpoint === 'sm' || breakpoint === 'xs')
+ }, [breakpoint, setIsDrawer])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx
similarity index 96%
rename from clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx
rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx
index ca0c69bca2..31d0c64b79 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx
@@ -8,7 +8,7 @@ import { useClashRules } from '@nyanpasu/interface'
import { alpha, BasePage } from '@nyanpasu/ui'
import { createFileRoute } from '@tanstack/react-router'
-export const Route = createFileRoute('/rules')({
+export const Route = createFileRoute('/(legacy)/rules')({
component: RulesPage,
})
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx
similarity index 98%
rename from clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx
rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx
index 3b4e1b07d4..8de7f2859e 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx
@@ -11,7 +11,7 @@ import { collectEnvs, openThat } from '@nyanpasu/interface'
import { BasePage } from '@nyanpasu/ui'
import { createFileRoute } from '@tanstack/react-router'
-export const Route = createFileRoute('/settings')({
+export const Route = createFileRoute('/(legacy)/settings')({
component: SettingPage,
})
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx
index 940ace011c..f4e5b4f7c2 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx
@@ -1,24 +1,14 @@
import { useMount } from 'ahooks'
import dayjs from 'dayjs'
-import AppContainer from '@/components/app/app-container'
-import LocalesProvider from '@/components/app/locales-provider'
-import MutationProvider from '@/components/layout/mutation-provider'
-import NoticeProvider from '@/components/layout/notice-provider'
-import PageTransition from '@/components/layout/page-transition'
-import SchemeProvider from '@/components/layout/scheme-provider'
import { ThemeModeProvider } from '@/components/layout/use-custom-theme'
-import UpdaterDialog from '@/components/updater/updater-dialog-wrapper'
import { useNyanpasuStorageSubscribers } from '@/hooks/use-store'
-import { UpdaterProvider } from '@/hooks/use-updater'
-import { FileRouteTypes } from '@/route-tree.gen'
-import { atomIsDrawer, memorizedRoutePathAtom } from '@/store'
import { CssBaseline } from '@mui/material'
import { StyledEngineProvider, useColorScheme } from '@mui/material/styles'
-import { cn, useBreakpoint } from '@nyanpasu/ui'
+import { cn } from '@nyanpasu/ui'
import {
createRootRoute,
ErrorComponentProps,
- useLocation,
+ Outlet,
} from '@tanstack/react-router'
import { emit } from '@tauri-apps/api/event'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
@@ -27,10 +17,8 @@ import 'dayjs/locale/zh-cn'
import 'dayjs/locale/zh-tw'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
-import { useAtom, useSetAtom } from 'jotai'
-import { lazy, PropsWithChildren, useEffect } from 'react'
-import { SWRConfig } from 'swr'
-import { NyanpasuProvider, useSettings } from '@nyanpasu/interface'
+import { lazy } from 'react'
+import { NyanpasuProvider } from '@nyanpasu/interface'
import styles from './-__root.module.scss'
dayjs.extend(relativeTime)
@@ -69,36 +57,9 @@ export const Route = createRootRoute({
pendingComponent: Pending,
})
-const QueryLoaderProvider = ({ children }: PropsWithChildren) => {
- const {
- query: { isLoading },
- } = useSettings()
-
- return isLoading ? null : children
-}
-
export default function App() {
- const breakpoint = useBreakpoint()
-
- const setMemorizedPath = useSetAtom(memorizedRoutePathAtom)
- const pathname = useLocation({
- select: (location) => location.pathname,
- })
-
- useEffect(() => {
- if (pathname !== '/') {
- setMemorizedPath(pathname as FileRouteTypes['fullPaths'])
- }
- }, [pathname, setMemorizedPath])
-
- const [isDrawer, setIsDrawer] = useAtom(atomIsDrawer)
-
useNyanpasuStorageSubscribers()
- useEffect(() => {
- setIsDrawer(breakpoint === 'sm' || breakpoint === 'xs')
- }, [breakpoint, setIsDrawer])
-
useMount(() => {
const appWindow = getCurrentWebviewWindow()
Promise.all([
@@ -110,38 +71,15 @@ export default function App() {
return (
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
}
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx
deleted file mode 100644
index e83f7084c4..0000000000
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import {
- createFileRoute,
- ErrorComponentProps,
- Outlet,
-} from '@tanstack/react-router'
-
-const Catch = ({ error }: ErrorComponentProps) => {
- return (
-
-
Oops!
-
Something went wrong... Caught at _layout error boundary.
-
{error.message}
-
- )
-}
-
-const Pending = () =>
Loading from _layout...
-
-export const Route = createFileRoute('/_layout')({
- component: Layout,
- errorComponent: Catch,
- pendingComponent: Pending,
-})
-
-function Layout() {
- return
-}
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx
index 605885cdad..dcd96353d7 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx
+++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx
@@ -1,32 +1,13 @@
import { useAtomValue } from 'jotai'
-import { useEffect, useRef } from 'react'
import { memorizedRoutePathAtom } from '@/store'
-import { createFileRoute, useNavigate } from '@tanstack/react-router'
+import { createFileRoute, Navigate } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
- component: IndexPage,
+ component: RouteComponent,
})
-function IndexPage() {
- const navigate = useNavigate()
+function RouteComponent() {
const memorizedNavigate = useAtomValue(memorizedRoutePathAtom)
- const lockRef = useRef(false)
- useEffect(() => {
- if (lockRef.current) {
- return
- }
- const to =
- memorizedNavigate && memorizedNavigate !== '/'
- ? memorizedNavigate
- : '/dashboard'
-
- lockRef.current = true
- console.log('navigate to', to)
- navigate({
- to: to,
- })
- }, [memorizedNavigate, navigate])
-
- return null
+ return
}
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts b/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts
index ba36b35547..72b0c2d84f 100644
--- a/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts
+++ b/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts
@@ -9,59 +9,19 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './pages/__root'
-import { Route as SettingsRouteImport } from './pages/settings'
-import { Route as RulesRouteImport } from './pages/rules'
-import { Route as ProxiesRouteImport } from './pages/proxies'
-import { Route as ProvidersRouteImport } from './pages/providers'
-import { Route as ProfilesRouteImport } from './pages/profiles'
-import { Route as LogsRouteImport } from './pages/logs'
-import { Route as DashboardRouteImport } from './pages/dashboard'
-import { Route as ConnectionsRouteImport } from './pages/connections'
-import { Route as LayoutRouteImport } from './pages/_layout'
+import { Route as legacyRouteRouteImport } from './pages/(legacy)/route'
import { Route as IndexRouteImport } from './pages/index'
+import { Route as legacySettingsRouteImport } from './pages/(legacy)/settings'
+import { Route as legacyRulesRouteImport } from './pages/(legacy)/rules'
+import { Route as legacyProxiesRouteImport } from './pages/(legacy)/proxies'
+import { Route as legacyProvidersRouteImport } from './pages/(legacy)/providers'
+import { Route as legacyProfilesRouteImport } from './pages/(legacy)/profiles'
+import { Route as legacyLogsRouteImport } from './pages/(legacy)/logs'
+import { Route as legacyDashboardRouteImport } from './pages/(legacy)/dashboard'
+import { Route as legacyConnectionsRouteImport } from './pages/(legacy)/connections'
-const SettingsRoute = SettingsRouteImport.update({
- id: '/settings',
- path: '/settings',
- getParentRoute: () => rootRouteImport,
-} as any)
-const RulesRoute = RulesRouteImport.update({
- id: '/rules',
- path: '/rules',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ProxiesRoute = ProxiesRouteImport.update({
- id: '/proxies',
- path: '/proxies',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ProvidersRoute = ProvidersRouteImport.update({
- id: '/providers',
- path: '/providers',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ProfilesRoute = ProfilesRouteImport.update({
- id: '/profiles',
- path: '/profiles',
- getParentRoute: () => rootRouteImport,
-} as any)
-const LogsRoute = LogsRouteImport.update({
- id: '/logs',
- path: '/logs',
- getParentRoute: () => rootRouteImport,
-} as any)
-const DashboardRoute = DashboardRouteImport.update({
- id: '/dashboard',
- path: '/dashboard',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ConnectionsRoute = ConnectionsRouteImport.update({
- id: '/connections',
- path: '/connections',
- getParentRoute: () => rootRouteImport,
-} as any)
-const LayoutRoute = LayoutRouteImport.update({
- id: '/_layout',
+const legacyRouteRoute = legacyRouteRouteImport.update({
+ id: '/(legacy)',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
@@ -69,41 +29,81 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
+const legacySettingsRoute = legacySettingsRouteImport.update({
+ id: '/settings',
+ path: '/settings',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyRulesRoute = legacyRulesRouteImport.update({
+ id: '/rules',
+ path: '/rules',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyProxiesRoute = legacyProxiesRouteImport.update({
+ id: '/proxies',
+ path: '/proxies',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyProvidersRoute = legacyProvidersRouteImport.update({
+ id: '/providers',
+ path: '/providers',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyProfilesRoute = legacyProfilesRouteImport.update({
+ id: '/profiles',
+ path: '/profiles',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyLogsRoute = legacyLogsRouteImport.update({
+ id: '/logs',
+ path: '/logs',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyDashboardRoute = legacyDashboardRouteImport.update({
+ id: '/dashboard',
+ path: '/dashboard',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
+const legacyConnectionsRoute = legacyConnectionsRouteImport.update({
+ id: '/connections',
+ path: '/connections',
+ getParentRoute: () => legacyRouteRoute,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
- '/connections': typeof ConnectionsRoute
- '/dashboard': typeof DashboardRoute
- '/logs': typeof LogsRoute
- '/profiles': typeof ProfilesRoute
- '/providers': typeof ProvidersRoute
- '/proxies': typeof ProxiesRoute
- '/rules': typeof RulesRoute
- '/settings': typeof SettingsRoute
+ '/connections': typeof legacyConnectionsRoute
+ '/dashboard': typeof legacyDashboardRoute
+ '/logs': typeof legacyLogsRoute
+ '/profiles': typeof legacyProfilesRoute
+ '/providers': typeof legacyProvidersRoute
+ '/proxies': typeof legacyProxiesRoute
+ '/rules': typeof legacyRulesRoute
+ '/settings': typeof legacySettingsRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
- '/connections': typeof ConnectionsRoute
- '/dashboard': typeof DashboardRoute
- '/logs': typeof LogsRoute
- '/profiles': typeof ProfilesRoute
- '/providers': typeof ProvidersRoute
- '/proxies': typeof ProxiesRoute
- '/rules': typeof RulesRoute
- '/settings': typeof SettingsRoute
+ '/connections': typeof legacyConnectionsRoute
+ '/dashboard': typeof legacyDashboardRoute
+ '/logs': typeof legacyLogsRoute
+ '/profiles': typeof legacyProfilesRoute
+ '/providers': typeof legacyProvidersRoute
+ '/proxies': typeof legacyProxiesRoute
+ '/rules': typeof legacyRulesRoute
+ '/settings': typeof legacySettingsRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
- '/_layout': typeof LayoutRoute
- '/connections': typeof ConnectionsRoute
- '/dashboard': typeof DashboardRoute
- '/logs': typeof LogsRoute
- '/profiles': typeof ProfilesRoute
- '/providers': typeof ProvidersRoute
- '/proxies': typeof ProxiesRoute
- '/rules': typeof RulesRoute
- '/settings': typeof SettingsRoute
+ '/(legacy)': typeof legacyRouteRouteWithChildren
+ '/(legacy)/connections': typeof legacyConnectionsRoute
+ '/(legacy)/dashboard': typeof legacyDashboardRoute
+ '/(legacy)/logs': typeof legacyLogsRoute
+ '/(legacy)/profiles': typeof legacyProfilesRoute
+ '/(legacy)/providers': typeof legacyProvidersRoute
+ '/(legacy)/proxies': typeof legacyProxiesRoute
+ '/(legacy)/rules': typeof legacyRulesRoute
+ '/(legacy)/settings': typeof legacySettingsRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -131,93 +131,29 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
- | '/_layout'
- | '/connections'
- | '/dashboard'
- | '/logs'
- | '/profiles'
- | '/providers'
- | '/proxies'
- | '/rules'
- | '/settings'
+ | '/(legacy)'
+ | '/(legacy)/connections'
+ | '/(legacy)/dashboard'
+ | '/(legacy)/logs'
+ | '/(legacy)/profiles'
+ | '/(legacy)/providers'
+ | '/(legacy)/proxies'
+ | '/(legacy)/rules'
+ | '/(legacy)/settings'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
- LayoutRoute: typeof LayoutRoute
- ConnectionsRoute: typeof ConnectionsRoute
- DashboardRoute: typeof DashboardRoute
- LogsRoute: typeof LogsRoute
- ProfilesRoute: typeof ProfilesRoute
- ProvidersRoute: typeof ProvidersRoute
- ProxiesRoute: typeof ProxiesRoute
- RulesRoute: typeof RulesRoute
- SettingsRoute: typeof SettingsRoute
+ legacyRouteRoute: typeof legacyRouteRouteWithChildren
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
- '/settings': {
- id: '/settings'
- path: '/settings'
- fullPath: '/settings'
- preLoaderRoute: typeof SettingsRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/rules': {
- id: '/rules'
- path: '/rules'
- fullPath: '/rules'
- preLoaderRoute: typeof RulesRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/proxies': {
- id: '/proxies'
- path: '/proxies'
- fullPath: '/proxies'
- preLoaderRoute: typeof ProxiesRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/providers': {
- id: '/providers'
- path: '/providers'
- fullPath: '/providers'
- preLoaderRoute: typeof ProvidersRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/profiles': {
- id: '/profiles'
- path: '/profiles'
- fullPath: '/profiles'
- preLoaderRoute: typeof ProfilesRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/logs': {
- id: '/logs'
- path: '/logs'
- fullPath: '/logs'
- preLoaderRoute: typeof LogsRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/dashboard': {
- id: '/dashboard'
- path: '/dashboard'
- fullPath: '/dashboard'
- preLoaderRoute: typeof DashboardRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/connections': {
- id: '/connections'
- path: '/connections'
- fullPath: '/connections'
- preLoaderRoute: typeof ConnectionsRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/_layout': {
- id: '/_layout'
+ '/(legacy)': {
+ id: '/(legacy)'
path: ''
fullPath: ''
- preLoaderRoute: typeof LayoutRouteImport
+ preLoaderRoute: typeof legacyRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
@@ -227,20 +163,94 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/(legacy)/settings': {
+ id: '/(legacy)/settings'
+ path: '/settings'
+ fullPath: '/settings'
+ preLoaderRoute: typeof legacySettingsRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/rules': {
+ id: '/(legacy)/rules'
+ path: '/rules'
+ fullPath: '/rules'
+ preLoaderRoute: typeof legacyRulesRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/proxies': {
+ id: '/(legacy)/proxies'
+ path: '/proxies'
+ fullPath: '/proxies'
+ preLoaderRoute: typeof legacyProxiesRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/providers': {
+ id: '/(legacy)/providers'
+ path: '/providers'
+ fullPath: '/providers'
+ preLoaderRoute: typeof legacyProvidersRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/profiles': {
+ id: '/(legacy)/profiles'
+ path: '/profiles'
+ fullPath: '/profiles'
+ preLoaderRoute: typeof legacyProfilesRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/logs': {
+ id: '/(legacy)/logs'
+ path: '/logs'
+ fullPath: '/logs'
+ preLoaderRoute: typeof legacyLogsRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/dashboard': {
+ id: '/(legacy)/dashboard'
+ path: '/dashboard'
+ fullPath: '/dashboard'
+ preLoaderRoute: typeof legacyDashboardRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
+ '/(legacy)/connections': {
+ id: '/(legacy)/connections'
+ path: '/connections'
+ fullPath: '/connections'
+ preLoaderRoute: typeof legacyConnectionsRouteImport
+ parentRoute: typeof legacyRouteRoute
+ }
}
}
+interface legacyRouteRouteChildren {
+ legacyConnectionsRoute: typeof legacyConnectionsRoute
+ legacyDashboardRoute: typeof legacyDashboardRoute
+ legacyLogsRoute: typeof legacyLogsRoute
+ legacyProfilesRoute: typeof legacyProfilesRoute
+ legacyProvidersRoute: typeof legacyProvidersRoute
+ legacyProxiesRoute: typeof legacyProxiesRoute
+ legacyRulesRoute: typeof legacyRulesRoute
+ legacySettingsRoute: typeof legacySettingsRoute
+}
+
+const legacyRouteRouteChildren: legacyRouteRouteChildren = {
+ legacyConnectionsRoute: legacyConnectionsRoute,
+ legacyDashboardRoute: legacyDashboardRoute,
+ legacyLogsRoute: legacyLogsRoute,
+ legacyProfilesRoute: legacyProfilesRoute,
+ legacyProvidersRoute: legacyProvidersRoute,
+ legacyProxiesRoute: legacyProxiesRoute,
+ legacyRulesRoute: legacyRulesRoute,
+ legacySettingsRoute: legacySettingsRoute,
+}
+
+const legacyRouteRouteWithChildren = legacyRouteRoute._addFileChildren(
+ legacyRouteRouteChildren,
+)
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
- LayoutRoute: LayoutRoute,
- ConnectionsRoute: ConnectionsRoute,
- DashboardRoute: DashboardRoute,
- LogsRoute: LogsRoute,
- ProfilesRoute: ProfilesRoute,
- ProvidersRoute: ProvidersRoute,
- ProxiesRoute: ProxiesRoute,
- RulesRoute: RulesRoute,
- SettingsRoute: SettingsRoute,
+ legacyRouteRoute: legacyRouteRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts
index 86a35cd102..b11c6f91e9 100644
--- a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts
+++ b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts
@@ -16,18 +16,6 @@ import react from '@vitejs/plugin-react-swc'
const IS_NIGHTLY = process.env.NIGHTLY?.toLowerCase() === 'true'
-const devtools = () => {
- return {
- name: 'react-devtools',
- transformIndexHtml(html: string) {
- return html.replace(
- /<\/head>/,
- ``,
- )
- },
- }
-}
-
const builtinVars = () => {
return {
name: 'built-in-vars',
@@ -123,7 +111,6 @@ export default defineConfig(({ command, mode }) => {
compiler: 'jsx', // or 'solid'
}),
sassDts({ esmExport: true }),
- isDev && devtools(),
],
resolve: {
alias: {
diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json
index 8c7f32d2a7..8d8cf06ee8 100644
--- a/clash-nyanpasu/package.json
+++ b/clash-nyanpasu/package.json
@@ -5,8 +5,8 @@
"license": "GPL-3.0",
"type": "module",
"scripts": {
- "dev": "run-p -r web:devtools tauri:dev",
- "dev:diff": "run-p -r web:devtools tauri:diff",
+ "dev": "run-p tauri:dev",
+ "dev:diff": "run-p tauri:diff",
"build": "tauri build",
"build:debug": "tauri build -f verge-dev deadlock-detection -d -c \"{ \\\"tauri\\\" : { \\\"updater\\\": { \\\"active\\\": false } }} \"",
"build:nightly": "tauri build -f nightly -c ./backend/tauri/tauri.nightly.conf.json",
@@ -18,7 +18,6 @@
"web:build": "pnpm --filter=@nyanpasu/nyanpasu build",
"web:serve": "pnpm --filter=@nyanpasu/nyanpasu preview",
"web:visualize": "pnpm --filter=@nyanpasu/nyanpasu bundle:visualize",
- "web:devtools": "pnpm react-devtools",
"lint": "run-s lint:*",
"lint:prettier": "prettier --check .",
"lint:eslint": "eslint --cache .",
@@ -36,7 +35,6 @@
"fmt": "run-p fmt:*",
"fmt:backend": "cargo fmt --manifest-path ./backend/Cargo.toml --all",
"fmt:prettier": "prettier --write .",
- "check": "tsx scripts/check.ts",
"updater": "tsx scripts/updater.ts",
"updater:nightly": "tsx scripts/updater-nightly.ts",
"send-notify": "tsx scripts/telegram-notify.ts",
@@ -50,7 +48,8 @@
"prepare": "husky",
"prepare:nightly": "tsx scripts/prepare-nightly.ts",
"prepare:release": "tsx scripts/prepare-release.ts",
- "prepare:preview": "tsx scripts/prepare-preview.ts"
+ "prepare:preview": "tsx scripts/prepare-preview.ts",
+ "prepare:check": "tsx scripts/check.ts"
},
"dependencies": {
"@prettier/plugin-oxc": "0.0.4",
@@ -97,7 +96,6 @@
"prettier-plugin-ember-template-tag": "2.1.0",
"prettier-plugin-tailwindcss": "0.7.1",
"prettier-plugin-toml": "2.0.6",
- "react-devtools": "7.0.1",
"stylelint": "16.25.0",
"stylelint-config-html": "1.1.0",
"stylelint-config-recess-order": "7.4.0",
diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml
index 7dbbfc5751..ed966803c8 100644
--- a/clash-nyanpasu/pnpm-lock.yaml
+++ b/clash-nyanpasu/pnpm-lock.yaml
@@ -139,9 +139,6 @@ importers:
prettier-plugin-toml:
specifier: 2.0.6
version: 2.0.6(prettier@3.6.2)
- react-devtools:
- specifier: 7.0.1
- version: 7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
stylelint:
specifier: 16.25.0
version: 16.25.0(typescript@5.9.3)
@@ -1507,10 +1504,6 @@ packages:
'@dual-bundle/import-meta-resolve@4.2.1':
resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==}
- '@electron/get@2.0.3':
- resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==}
- engines: {node: '>=12'}
-
'@emnapi/core@1.4.3':
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
@@ -2921,10 +2914,6 @@ packages:
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
- '@sindresorhus/is@4.6.0':
- resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
- engines: {node: '>=10'}
-
'@stylistic/eslint-plugin@2.11.0':
resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3074,10 +3063,6 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
- '@szmarczak/http-timer@4.0.6':
- resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
- engines: {node: '>=10'}
-
'@tailwindcss/node@4.1.17':
resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
@@ -3422,9 +3407,6 @@ packages:
'@types/babel__traverse@7.20.6':
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
- '@types/cacheable-request@6.0.3':
- resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
-
'@types/conventional-commits-parser@5.0.0':
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
@@ -3545,9 +3527,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
- '@types/http-cache-semantics@4.0.4':
- resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
-
'@types/js-cookie@2.2.7':
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
@@ -3563,9 +3542,6 @@ packages:
'@types/jsonfile@6.1.4':
resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
- '@types/keyv@3.1.4':
- resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
-
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
@@ -3578,9 +3554,6 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
- '@types/node@16.18.108':
- resolution: {integrity: sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==}
-
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
@@ -3609,9 +3582,6 @@ packages:
'@types/react@19.2.7':
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
- '@types/responselike@1.0.3':
- resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
-
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@@ -3630,9 +3600,6 @@ packages:
'@types/yargs@17.0.35':
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
- '@types/yauzl@2.10.3':
- resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
-
'@typescript-eslint/eslint-plugin@8.46.3':
resolution: {integrity: sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3948,17 +3915,10 @@ packages:
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
- ansi-align@2.0.0:
- resolution: {integrity: sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==}
-
ansi-escapes@7.0.0:
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
engines: {node: '>=18'}
- ansi-regex@3.0.1:
- resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==}
- engines: {node: '>=4'}
-
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -3967,10 +3927,6 @@ packages:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
- ansi-styles@3.2.1:
- resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
- engines: {node: '>=4'}
-
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -4128,17 +4084,9 @@ packages:
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
- boolean@3.2.0:
- resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
-
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
- boxen@1.3.0:
- resolution: {integrity: sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==}
- engines: {node: '>=4'}
-
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -4169,9 +4117,6 @@ packages:
buffer-builder@0.2.0:
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
- buffer-crc32@0.2.13:
- resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
-
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -4186,14 +4131,6 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
- cacheable-lookup@5.0.4:
- resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
- engines: {node: '>=10.6.0'}
-
- cacheable-request@7.0.4:
- resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
- engines: {node: '>=8'}
-
cacheable@1.10.4:
resolution: {integrity: sha512-Gd7ccIUkZ9TE2odLQVS+PDjIvQCdJKUlLdJRVvZu0aipj07Qfx+XIej7hhDrKGGoIxV5m5fT/kOJNJPQhQneRg==}
@@ -4232,10 +4169,6 @@ packages:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
- camelcase@4.1.0:
- resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==}
- engines: {node: '>=4'}
-
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
@@ -4246,17 +4179,9 @@ packages:
caniuse-lite@1.0.30001756:
resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==}
- capture-stack-trace@1.0.2:
- resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==}
- engines: {node: '>=0.10.0'}
-
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
- chalk@2.4.2:
- resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
- engines: {node: '>=4'}
-
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -4292,9 +4217,6 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
- ci-info@1.6.0:
- resolution: {integrity: sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==}
-
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
@@ -4302,10 +4224,6 @@ packages:
resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
engines: {node: '>= 10.0'}
- cli-boxes@1.0.0:
- resolution: {integrity: sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==}
- engines: {node: '>=0.10.0'}
-
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
@@ -4322,23 +4240,14 @@ packages:
resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
engines: {node: '>=20'}
- clone-response@1.0.3:
- resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
- color-convert@1.9.3:
- resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
-
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
- color-name@1.1.3:
- resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
-
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
@@ -4394,10 +4303,6 @@ packages:
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
- configstore@3.1.5:
- resolution: {integrity: sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==}
- engines: {node: '>=4'}
-
connect-history-api-fallback@1.6.0:
resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==}
engines: {node: '>=0.8'}
@@ -4486,26 +4391,15 @@ packages:
country-emoji@1.5.6:
resolution: {integrity: sha512-pSB8OOROfimFc2bcN+H41DuzXYIod/JQ6SgF4pYXkRCm9f8uF1JAJ0vXPhenug6xkpt3Gv33mdypMXB49CJWRA==}
- create-error-class@3.0.2:
- resolution: {integrity: sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==}
- engines: {node: '>=0.10.0'}
-
cross-env@10.1.0:
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
engines: {node: '>=20'}
hasBin: true
- cross-spawn@5.1.0:
- resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
-
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- crypto-random-string@1.0.0:
- resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==}
- engines: {node: '>=4'}
-
css-functions-list@3.2.3:
resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
engines: {node: '>=12 || >=16'}
@@ -4762,10 +4656,6 @@ packages:
decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
- decompress-response@6.0.0:
- resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
- engines: {node: '>=10'}
-
dedent@1.7.0:
resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==}
peerDependencies:
@@ -4774,21 +4664,9 @@ packages:
babel-plugin-macros:
optional: true
- deep-extend@0.6.0:
- resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
- engines: {node: '>=4.0.0'}
-
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
- default-gateway@6.0.3:
- resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==}
- engines: {node: '>= 10'}
-
- defer-to-connect@2.0.1:
- resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
- engines: {node: '>=10'}
-
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
@@ -4823,9 +4701,6 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
- detect-node@2.1.0:
- resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
-
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -4876,10 +4751,6 @@ packages:
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
- dot-prop@4.2.1:
- resolution: {integrity: sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==}
- engines: {node: '>=4'}
-
dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
@@ -4896,9 +4767,6 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
- duplexer3@0.1.5:
- resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
-
ejs@3.1.10:
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
engines: {node: '>=0.10.0'}
@@ -4910,11 +4778,6 @@ packages:
electron-to-chromium@1.5.258:
resolution: {integrity: sha512-rHUggNV5jKQ0sSdWwlaRDkFc3/rRJIVnOSe9yR4zrR07m3ZxhP4N27Hlg8VeJGGYgFTxK5NqDmWI4DSH72vIJg==}
- electron@23.3.13:
- resolution: {integrity: sha512-BaXtHEb+KYKLouUXlUVDa/lj9pj4F5kiE0kwFdJV84Y2EU7euIDgPthfKtchhr5MVHmjtavRMIV/zAwEiSQ9rQ==}
- engines: {node: '>= 12.20.55'}
- hasBin: true
-
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@@ -4924,9 +4787,6 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
- end-of-stream@1.4.4:
- resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
-
enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
@@ -5023,9 +4883,6 @@ packages:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'}
- es6-error@4.1.1:
- resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
-
es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
@@ -5042,10 +4899,6 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
- escape-string-regexp@1.0.5:
- resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
- engines: {node: '>=0.8.0'}
-
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -5269,14 +5122,6 @@ packages:
eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
- execa@0.7.0:
- resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==}
- engines: {node: '>=4'}
-
- execa@5.1.1:
- resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
- engines: {node: '>=10'}
-
exsolve@1.0.4:
resolution: {integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==}
@@ -5289,11 +5134,6 @@ packages:
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
- extract-zip@2.0.1:
- resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
- engines: {node: '>= 10.17.0'}
- hasBin: true
-
fast-content-type-parse@3.0.0:
resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==}
@@ -5336,9 +5176,6 @@ packages:
fd-package-json@2.0.0:
resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==}
- fd-slicer@1.1.0:
- resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
-
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -5429,10 +5266,6 @@ packages:
resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==}
engines: {node: '>=14.14'}
- fs-extra@8.1.0:
- resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
- engines: {node: '>=6 <7 || >=8'}
-
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -5487,18 +5320,6 @@ packages:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
- get-stream@3.0.0:
- resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==}
- engines: {node: '>=4'}
-
- get-stream@5.2.0:
- resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
- engines: {node: '>=8'}
-
- get-stream@6.0.1:
- resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
- engines: {node: '>=10'}
-
get-symbol-description@1.0.2:
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
engines: {node: '>= 0.4'}
@@ -5527,18 +5348,10 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
- global-agent@3.0.0:
- resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
- engines: {node: '>=10.0'}
-
global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
engines: {node: '>=18'}
- global-dirs@0.1.1:
- resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
- engines: {node: '>=4'}
-
global-modules@2.0.0:
resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
engines: {node: '>=6'}
@@ -5589,14 +5402,6 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
- got@11.8.6:
- resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
- engines: {node: '>=10.19.0'}
-
- got@6.7.1:
- resolution: {integrity: sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==}
- engines: {node: '>=4'}
-
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -5606,10 +5411,6 @@ packages:
has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
- has-flag@3.0.0:
- resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
- engines: {node: '>=4'}
-
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -5697,17 +5498,6 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
- http-cache-semantics@4.1.1:
- resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
-
- http2-wrapper@1.0.3:
- resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
- engines: {node: '>=10.19.0'}
-
- human-signals@2.1.0:
- resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
- engines: {node: '>=10.17.0'}
-
husky@9.1.7:
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
engines: {node: '>=18'}
@@ -5761,10 +5551,6 @@ packages:
resolution: {integrity: sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==}
engines: {node: '>=16.20'}
- import-lazy@2.1.0:
- resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==}
- engines: {node: '>=4'}
-
import-lazy@4.0.0:
resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
engines: {node: '>=8'}
@@ -5796,10 +5582,6 @@ packages:
inline-style-prefixer@7.0.1:
resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==}
- internal-ip@6.2.0:
- resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==}
- engines: {node: '>=10'}
-
internal-slot@1.0.7:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
@@ -5820,14 +5602,6 @@ packages:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
- ip-regex@4.3.0:
- resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==}
- engines: {node: '>=8'}
-
- ipaddr.js@1.9.1:
- resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
- engines: {node: '>= 0.10'}
-
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
@@ -5875,10 +5649,6 @@ packages:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
- is-ci@1.2.1:
- resolution: {integrity: sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==}
- hasBin: true
-
is-core-module@2.15.1:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
@@ -5919,10 +5689,6 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
- is-fullwidth-code-point@2.0.0:
- resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
- engines: {node: '>=4'}
-
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
@@ -5942,14 +5708,6 @@ packages:
is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
- is-installed-globally@0.1.0:
- resolution: {integrity: sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==}
- engines: {node: '>=4'}
-
- is-ip@3.1.0:
- resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==}
- engines: {node: '>=8'}
-
is-map@2.0.3:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
@@ -5962,10 +5720,6 @@ packages:
resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==}
engines: {node: '>=16'}
- is-npm@1.0.0:
- resolution: {integrity: sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==}
- engines: {node: '>=0.10.0'}
-
is-number-object@1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'}
@@ -5978,18 +5732,10 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
- is-obj@1.0.1:
- resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
- engines: {node: '>=0.10.0'}
-
is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
- is-path-inside@1.0.1:
- resolution: {integrity: sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==}
- engines: {node: '>=0.10.0'}
-
is-plain-obj@4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
@@ -5998,10 +5744,6 @@ packages:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
- is-redirect@1.0.0:
- resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==}
- engines: {node: '>=0.10.0'}
-
is-regex@1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'}
@@ -6010,10 +5752,6 @@ packages:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
- is-retry-allowed@1.2.0:
- resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==}
- engines: {node: '>=0.10.0'}
-
is-set@2.0.3:
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
engines: {node: '>= 0.4'}
@@ -6026,14 +5764,6 @@ packages:
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'}
- is-stream@1.1.0:
- resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
- engines: {node: '>=0.10.0'}
-
- is-stream@2.0.1:
- resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
- engines: {node: '>=8'}
-
is-string@1.0.7:
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
engines: {node: '>= 0.4'}
@@ -6199,9 +5929,6 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
- json-stringify-safe@5.0.1:
- resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
-
json5@1.0.2:
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
hasBin: true
@@ -6214,9 +5941,6 @@ packages:
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
- jsonfile@4.0.0:
- resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
-
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -6255,10 +5979,6 @@ packages:
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
- latest-version@3.1.0:
- resolution: {integrity: sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==}
- engines: {node: '>=4'}
-
less@4.2.0:
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
engines: {node: '>=6'}
@@ -6430,17 +6150,6 @@ packages:
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
- lowercase-keys@1.0.1:
- resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==}
- engines: {node: '>=0.10.0'}
-
- lowercase-keys@2.0.0:
- resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
- engines: {node: '>=8'}
-
- lru-cache@4.1.5:
- resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
-
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -6454,10 +6163,6 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- make-dir@1.3.0:
- resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==}
- engines: {node: '>=4'}
-
make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
@@ -6467,10 +6172,6 @@ packages:
engines: {node: '>= 18'}
hasBin: true
- matcher@3.0.0:
- resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
- engines: {node: '>=10'}
-
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -6523,9 +6224,6 @@ packages:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
- merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -6611,22 +6309,10 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
- mimic-fn@2.1.0:
- resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
- engines: {node: '>=6'}
-
mimic-function@5.0.1:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
- mimic-response@1.0.1:
- resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
- engines: {node: '>=4'}
-
- mimic-response@3.1.0:
- resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
- engines: {node: '>=10'}
-
minimatch@3.0.8:
resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
@@ -6785,10 +6471,6 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
- normalize-url@6.1.0:
- resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
- engines: {node: '>=10'}
-
npm-normalize-package-bin@4.0.0:
resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==}
engines: {node: ^18.17.0 || >=20.5.0}
@@ -6798,14 +6480,6 @@ packages:
engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'}
hasBin: true
- npm-run-path@2.0.2:
- resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
- engines: {node: '>=4'}
-
- npm-run-path@4.0.1:
- resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
- engines: {node: '>=8'}
-
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -6863,10 +6537,6 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- onetime@5.1.2:
- resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
- engines: {node: '>=6'}
-
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
@@ -6893,18 +6563,6 @@ packages:
oxc-resolver@11.12.0:
resolution: {integrity: sha512-zmS2q2txiB+hS2u0aiIwmvITIJN8c8ThlWoWB762Wx5nUw8WBlttp0rzt8nnuP1cGIq9YJ7sGxfsgokm+SQk5Q==}
- p-cancelable@2.1.1:
- resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
- engines: {node: '>=8'}
-
- p-event@4.2.0:
- resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==}
- engines: {node: '>=8'}
-
- p-finally@1.0.0:
- resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
- engines: {node: '>=4'}
-
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -6925,14 +6583,6 @@ packages:
resolution: {integrity: sha512-xL4PiFRQa/f9L9ZvR4/gUCRNus4N8YX80ku8kv9Jqz+ZokkiZLM0bcvX0gm1F3PDi9SPRsww1BDsTWgE6Y1GLQ==}
engines: {node: '>=20'}
- p-timeout@3.2.0:
- resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
- engines: {node: '>=8'}
-
- package-json@4.0.1:
- resolution: {integrity: sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==}
- engines: {node: '>=4'}
-
package-manager-detector@1.3.0:
resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
@@ -6975,13 +6625,6 @@ packages:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
- path-is-inside@1.0.2:
- resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
-
- path-key@2.0.1:
- resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
- engines: {node: '>=4'}
-
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -6999,9 +6642,6 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- pend@1.2.0:
- resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
-
peowly@1.3.2:
resolution: {integrity: sha512-BYIrwr8JCXY49jUZscgw311w9oGEKo7ux/s+BxrhKTQbiQ0iYNdZNJ5LgagaeercQdFHwnR7Z5IxxFWVQ+BasQ==}
engines: {node: '>=18.6.0'}
@@ -7030,10 +6670,6 @@ packages:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
- pify@3.0.0:
- resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
- engines: {node: '>=4'}
-
pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
@@ -7145,10 +6781,6 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
- prepend-http@1.0.4:
- resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==}
- engines: {node: '>=0.10.0'}
-
prettier-linter-helpers@1.0.0:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
@@ -7225,10 +6857,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- progress@2.0.3:
- resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
- engines: {node: '>=0.4.0'}
-
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -7238,12 +6866,6 @@ packages:
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
- pseudomap@1.0.2:
- resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
-
- pump@3.0.0:
- resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -7257,21 +6879,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- quick-lru@5.1.1:
- resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
- engines: {node: '>=10'}
-
- rc@1.2.8:
- resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
- hasBin: true
-
- react-devtools-core@7.0.1:
- resolution: {integrity: sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw==}
-
- react-devtools@7.0.1:
- resolution: {integrity: sha512-I2UXoJlsqNeN3uCrlrJw0V+K6HTHU5Q1x+BWJlF99hBC6A/lKhe+IuITZXyKb8BluMReSBYUhUYGkJGgr2KPQQ==}
- hasBin: true
-
react-dom@19.2.0:
resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
peerDependencies:
@@ -7435,13 +7042,6 @@ packages:
resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==}
engines: {node: '>=4'}
- registry-auth-token@3.4.0:
- resolution: {integrity: sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==}
-
- registry-url@3.1.0:
- resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
- engines: {node: '>=0.10.0'}
-
regjsgen@0.8.0:
resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
@@ -7479,9 +7079,6 @@ packages:
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
- resolve-alpn@1.2.1:
- resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -7506,9 +7103,6 @@ packages:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
- responselike@2.0.1:
- resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
-
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -7520,10 +7114,6 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
- roarr@2.15.4:
- resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
- engines: {node: '>=8.0'}
-
robust-predicates@3.0.2:
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
@@ -7562,9 +7152,6 @@ packages:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'}
- safe-buffer@5.2.1:
- resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
-
safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'}
@@ -7712,13 +7299,6 @@ packages:
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
- semver-compare@1.0.0:
- resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
-
- semver-diff@2.1.0:
- resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==}
- engines: {node: '>=0.10.0'}
-
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
@@ -7742,10 +7322,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
- serialize-error@7.0.1:
- resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
- engines: {node: '>=10'}
-
seroval-plugins@1.3.2:
resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==}
engines: {node: '>=10'}
@@ -7772,18 +7348,10 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
- shebang-command@1.2.0:
- resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
- engines: {node: '>=0.10.0'}
-
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
- shebang-regex@1.0.0:
- resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
- engines: {node: '>=0.10.0'}
-
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
@@ -7810,9 +7378,6 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
- signal-exit@3.0.7:
- resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
-
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@@ -7922,10 +7487,6 @@ packages:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
- string-width@2.1.1:
- resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
- engines: {node: '>=4'}
-
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -7964,10 +7525,6 @@ packages:
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
- strip-ansi@4.0.0:
- resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==}
- engines: {node: '>=4'}
-
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -7980,18 +7537,6 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
- strip-eof@1.0.0:
- resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
- engines: {node: '>=0.10.0'}
-
- strip-final-newline@2.0.0:
- resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
- engines: {node: '>=6'}
-
- strip-json-comments@2.0.1:
- resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
- engines: {node: '>=0.10.0'}
-
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -8064,14 +7609,6 @@ packages:
resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==}
hasBin: true
- sumchecker@3.0.1:
- resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
- engines: {node: '>= 8.0'}
-
- supports-color@5.5.0:
- resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
- engines: {node: '>=4'}
-
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -8135,10 +7672,6 @@ packages:
telegram@2.26.22:
resolution: {integrity: sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==}
- term-size@1.2.0:
- resolution: {integrity: sha512-7dPUZQGy/+m3/wjVz3ZW5dobSoD/02NxJpoXUX0WIyjfVS3l0c+b/+9phIDFA7FHzkYtwtMFgeGZ/Y8jVTeqQQ==}
- engines: {node: '>=4'}
-
terser@5.36.0:
resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==}
engines: {node: '>=10'}
@@ -8155,10 +7688,6 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
- timed-out@4.0.1:
- resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==}
- engines: {node: '>=0.10.0'}
-
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -8255,10 +7784,6 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- type-fest@0.13.1:
- resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
- engines: {node: '>=10'}
-
type@2.7.2:
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
@@ -8367,10 +7892,6 @@ packages:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
engines: {node: '>=18.12.0'}
- unique-string@1.0.0:
- resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==}
- engines: {node: '>=4'}
-
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
@@ -8398,10 +7919,6 @@ packages:
universal-user-agent@7.0.2:
resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==}
- universalify@0.1.2:
- resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
- engines: {node: '>= 4.0.0'}
-
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -8456,10 +7973,6 @@ packages:
unrs-resolver@1.10.1:
resolution: {integrity: sha512-EFrL7Hw4kmhZdwWO3dwwFJo6hO3FXuQ6Bg8BK/faHZ9m1YxqBS31BNSTxklIQkxK/4LlV8zTYnPsIRLBzTzjCA==}
- unzip-response@2.0.1:
- resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==}
- engines: {node: '>=4'}
-
update-browserslist-db@1.1.3:
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
@@ -8472,17 +7985,9 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
- update-notifier@2.5.0:
- resolution: {integrity: sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==}
- engines: {node: '>=4'}
-
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- url-parse-lax@1.0.0:
- resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==}
- engines: {node: '>=0.10.0'}
-
use-resize-observer@9.1.0:
resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
peerDependencies:
@@ -8692,10 +8197,6 @@ packages:
engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true
- widest-line@2.0.1:
- resolution: {integrity: sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==}
- engines: {node: '>=4'}
-
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -8714,29 +8215,10 @@ packages:
write-file-atomic@1.3.4:
resolution: {integrity: sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==}
- write-file-atomic@2.4.3:
- resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==}
-
write-file-atomic@5.0.1:
resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- ws@7.5.10:
- resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
- engines: {node: '>=8.3.0'}
- peerDependencies:
- bufferutil: ^4.0.1
- utf-8-validate: ^5.0.2
- peerDependenciesMeta:
- bufferutil:
- optional: true
- utf-8-validate:
- optional: true
-
- xdg-basedir@3.0.0:
- resolution: {integrity: sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==}
- engines: {node: '>=4'}
-
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -8746,9 +8228,6 @@ packages:
engines: {node: '>=0.10.32'}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- yallist@2.1.2:
- resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
-
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -8789,9 +8268,6 @@ packages:
resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
- yauzl@2.10.0:
- resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
-
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -10117,20 +9593,6 @@ snapshots:
'@dual-bundle/import-meta-resolve@4.2.1': {}
- '@electron/get@2.0.3':
- dependencies:
- debug: 4.4.3
- env-paths: 2.2.1
- fs-extra: 8.1.0
- got: 11.8.6
- progress: 2.0.3
- semver: 6.3.1
- sumchecker: 3.0.1
- optionalDependencies:
- global-agent: 3.0.0
- transitivePeerDependencies:
- - supports-color
-
'@emnapi/core@1.4.3':
dependencies:
'@emnapi/wasi-threads': 1.0.2
@@ -10599,7 +10061,7 @@ snapshots:
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
- csstype: 3.1.3
+ csstype: 3.2.3
prop-types: 15.8.1
react: 19.2.0
optionalDependencies:
@@ -10612,7 +10074,7 @@ snapshots:
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
- csstype: 3.1.3
+ csstype: 3.2.3
prop-types: 15.8.1
react: 19.2.0
optionalDependencies:
@@ -11440,8 +10902,6 @@ snapshots:
'@shikijs/vscode-textmate@10.0.2': {}
- '@sindresorhus/is@4.6.0': {}
-
'@stylistic/eslint-plugin@2.11.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/utils': 8.46.2(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
@@ -11576,10 +11036,6 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
- '@szmarczak/http-timer@4.0.6':
- dependencies:
- defer-to-connect: 2.0.1
-
'@tailwindcss/node@4.1.17':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -11949,13 +11405,6 @@ snapshots:
dependencies:
'@babel/types': 7.28.4
- '@types/cacheable-request@6.0.3':
- dependencies:
- '@types/http-cache-semantics': 4.0.4
- '@types/keyv': 3.1.4
- '@types/node': 24.10.1
- '@types/responselike': 1.0.3
-
'@types/conventional-commits-parser@5.0.0':
dependencies:
'@types/node': 24.10.1
@@ -12102,8 +11551,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.2
- '@types/http-cache-semantics@4.0.4': {}
-
'@types/js-cookie@2.2.7': {}
'@types/js-cookie@3.0.6': {}
@@ -12116,10 +11563,6 @@ snapshots:
dependencies:
'@types/node': 24.10.1
- '@types/keyv@3.1.4':
- dependencies:
- '@types/node': 24.10.1
-
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.7
@@ -12132,8 +11575,6 @@ snapshots:
'@types/ms@0.7.34': {}
- '@types/node@16.18.108': {}
-
'@types/node@24.10.1':
dependencies:
undici-types: 7.16.0
@@ -12162,10 +11603,6 @@ snapshots:
dependencies:
csstype: 3.2.3
- '@types/responselike@1.0.3':
- dependencies:
- '@types/node': 24.10.1
-
'@types/semver@7.7.1': {}
'@types/unist@2.0.10': {}
@@ -12180,11 +11617,6 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
- '@types/yauzl@2.10.3':
- dependencies:
- '@types/node': 24.10.1
- optional: true
-
'@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -12562,24 +11994,14 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
use-resize-observer: 9.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
- ansi-align@2.0.0:
- dependencies:
- string-width: 2.1.1
-
ansi-escapes@7.0.0:
dependencies:
environment: 1.1.0
- ansi-regex@3.0.1: {}
-
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
- ansi-styles@3.2.1:
- dependencies:
- color-convert: 1.9.3
-
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -12741,7 +12163,7 @@ snapshots:
dependencies:
'@babel/runtime': 7.28.4
cosmiconfig: 7.1.0
- resolve: 1.22.8
+ resolve: 1.22.10
babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.0):
dependencies:
@@ -12787,21 +12209,8 @@ snapshots:
boolbase@1.0.0: {}
- boolean@3.2.0:
- optional: true
-
bottleneck@2.19.5: {}
- boxen@1.3.0:
- dependencies:
- ansi-align: 2.0.0
- camelcase: 4.1.0
- chalk: 2.4.2
- cli-boxes: 1.0.0
- string-width: 2.1.1
- term-size: 1.2.0
- widest-line: 2.0.1
-
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@@ -12837,8 +12246,6 @@ snapshots:
buffer-builder@0.2.0: {}
- buffer-crc32@0.2.13: {}
-
buffer-from@1.1.2: {}
buffer@6.0.3:
@@ -12852,18 +12259,6 @@ snapshots:
cac@6.7.14: {}
- cacheable-lookup@5.0.4: {}
-
- cacheable-request@7.0.4:
- dependencies:
- clone-response: 1.0.3
- get-stream: 5.2.0
- http-cache-semantics: 4.1.1
- keyv: 4.5.4
- lowercase-keys: 2.0.0
- normalize-url: 6.1.0
- responselike: 2.0.1
-
cacheable@1.10.4:
dependencies:
hookified: 1.11.0
@@ -12913,24 +12308,14 @@ snapshots:
camelcase-css@2.0.1: {}
- camelcase@4.1.0: {}
-
camelcase@6.3.0: {}
caniuse-lite@1.0.30001726: {}
caniuse-lite@1.0.30001756: {}
- capture-stack-trace@1.0.2: {}
-
ccount@2.0.1: {}
- chalk@2.4.2:
- dependencies:
- ansi-styles: 3.2.1
- escape-string-regexp: 1.0.5
- supports-color: 5.5.0
-
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -12966,16 +12351,12 @@ snapshots:
chownr@3.0.0: {}
- ci-info@1.6.0: {}
-
classnames@2.5.1: {}
clean-css@5.3.3:
dependencies:
source-map: 0.6.1
- cli-boxes@1.0.0: {}
-
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
@@ -12997,22 +12378,12 @@ snapshots:
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
- clone-response@1.0.3:
- dependencies:
- mimic-response: 1.0.1
-
clsx@2.1.1: {}
- color-convert@1.9.3:
- dependencies:
- color-name: 1.1.3
-
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
- color-name@1.1.3: {}
-
color-name@1.1.4: {}
colord@2.9.3: {}
@@ -13050,15 +12421,6 @@ snapshots:
confbox@0.2.2: {}
- configstore@3.1.5:
- dependencies:
- dot-prop: 4.2.1
- graceful-fs: 4.2.11
- make-dir: 1.3.0
- unique-string: 1.0.0
- write-file-atomic: 2.4.3
- xdg-basedir: 3.0.0
-
connect-history-api-fallback@1.6.0: {}
consola@2.15.3: {}
@@ -13143,29 +12505,17 @@ snapshots:
country-emoji@1.5.6: {}
- create-error-class@3.0.2:
- dependencies:
- capture-stack-trace: 1.0.2
-
cross-env@10.1.0:
dependencies:
'@epic-web/invariant': 1.0.0
cross-spawn: 7.0.6
- cross-spawn@5.1.0:
- dependencies:
- lru-cache: 4.1.5
- shebang-command: 1.2.0
- which: 1.3.1
-
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- crypto-random-string@1.0.0: {}
-
css-functions-list@3.2.3: {}
css-in-js-utils@3.1.0:
@@ -13427,24 +12777,12 @@ snapshots:
dependencies:
character-entities: 2.0.2
- decompress-response@6.0.0:
- dependencies:
- mimic-response: 3.1.0
-
dedent@1.7.0(babel-plugin-macros@3.1.0):
optionalDependencies:
babel-plugin-macros: 3.1.0
- deep-extend@0.6.0: {}
-
deep-is@0.1.4: {}
- default-gateway@6.0.3:
- dependencies:
- execa: 5.1.1
-
- defer-to-connect@2.0.1: {}
-
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
@@ -13474,9 +12812,6 @@ snapshots:
detect-libc@2.0.4: {}
- detect-node@2.1.0:
- optional: true
-
devlop@1.1.0:
dependencies:
dequal: 2.0.3
@@ -13494,7 +12829,7 @@ snapshots:
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.28.4
- csstype: 3.1.3
+ csstype: 3.2.3
dom-serializer@1.4.1:
dependencies:
@@ -13543,10 +12878,6 @@ snapshots:
no-case: 3.0.4
tslib: 2.8.1
- dot-prop@4.2.1:
- dependencies:
- is-obj: 1.0.1
-
dot-prop@5.3.0:
dependencies:
is-obj: 2.0.0
@@ -13561,8 +12892,6 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
- duplexer3@0.1.5: {}
-
ejs@3.1.10:
dependencies:
jake: 10.9.2
@@ -13571,24 +12900,12 @@ snapshots:
electron-to-chromium@1.5.258: {}
- electron@23.3.13:
- dependencies:
- '@electron/get': 2.0.3
- '@types/node': 16.18.108
- extract-zip: 2.0.1
- transitivePeerDependencies:
- - supports-color
-
emoji-regex-xs@1.0.0: {}
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {}
- end-of-stream@1.4.4:
- dependencies:
- once: 1.4.0
-
enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
@@ -13848,9 +13165,6 @@ snapshots:
esniff: 2.0.1
next-tick: 1.1.0
- es6-error@4.1.1:
- optional: true
-
es6-iterator@2.0.3:
dependencies:
d: 1.0.2
@@ -13892,8 +13206,6 @@ snapshots:
escalade@3.2.0: {}
- escape-string-regexp@1.0.5: {}
-
escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {}
@@ -14184,28 +13496,6 @@ snapshots:
eventemitter3@5.0.1: {}
- execa@0.7.0:
- dependencies:
- cross-spawn: 5.1.0
- get-stream: 3.0.0
- is-stream: 1.1.0
- npm-run-path: 2.0.2
- p-finally: 1.0.0
- signal-exit: 3.0.7
- strip-eof: 1.0.0
-
- execa@5.1.1:
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 6.0.1
- human-signals: 2.1.0
- is-stream: 2.0.1
- merge-stream: 2.0.0
- npm-run-path: 4.0.1
- onetime: 5.1.2
- signal-exit: 3.0.7
- strip-final-newline: 2.0.0
-
exsolve@1.0.4: {}
exsolve@1.0.7: {}
@@ -14216,16 +13506,6 @@ snapshots:
extend@3.0.2: {}
- extract-zip@2.0.1:
- dependencies:
- debug: 4.4.3
- get-stream: 5.2.0
- yauzl: 2.10.0
- optionalDependencies:
- '@types/yauzl': 2.10.3
- transitivePeerDependencies:
- - supports-color
-
fast-content-type-parse@3.0.0: {}
fast-deep-equal@3.1.3: {}
@@ -14268,10 +13548,6 @@ snapshots:
dependencies:
walk-up-path: 4.0.0
- fd-slicer@1.1.0:
- dependencies:
- pend: 1.2.0
-
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -14360,12 +13636,6 @@ snapshots:
jsonfile: 6.1.0
universalify: 2.0.1
- fs-extra@8.1.0:
- dependencies:
- graceful-fs: 4.2.11
- jsonfile: 4.0.0
- universalify: 0.1.2
-
fs.realpath@1.0.0:
optional: true
@@ -14439,14 +13709,6 @@ snapshots:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
- get-stream@3.0.0: {}
-
- get-stream@5.2.0:
- dependencies:
- pump: 3.0.0
-
- get-stream@6.0.1: {}
-
get-symbol-description@1.0.2:
dependencies:
call-bind: 1.0.8
@@ -14487,24 +13749,10 @@ snapshots:
path-is-absolute: 1.0.1
optional: true
- global-agent@3.0.0:
- dependencies:
- boolean: 3.2.0
- es6-error: 4.1.1
- matcher: 3.0.0
- roarr: 2.15.4
- semver: 7.7.3
- serialize-error: 7.0.1
- optional: true
-
global-directory@4.0.1:
dependencies:
ini: 4.1.1
- global-dirs@0.1.1:
- dependencies:
- ini: 1.3.8
-
global-modules@2.0.0:
dependencies:
global-prefix: 3.0.0
@@ -14552,44 +13800,12 @@ snapshots:
gopd@1.2.0: {}
- got@11.8.6:
- dependencies:
- '@sindresorhus/is': 4.6.0
- '@szmarczak/http-timer': 4.0.6
- '@types/cacheable-request': 6.0.3
- '@types/responselike': 1.0.3
- cacheable-lookup: 5.0.4
- cacheable-request: 7.0.4
- decompress-response: 6.0.0
- http2-wrapper: 1.0.3
- lowercase-keys: 2.0.0
- p-cancelable: 2.1.1
- responselike: 2.0.1
-
- got@6.7.1:
- dependencies:
- '@types/keyv': 3.1.4
- '@types/responselike': 1.0.3
- create-error-class: 3.0.2
- duplexer3: 0.1.5
- get-stream: 3.0.0
- is-redirect: 1.0.0
- is-retry-allowed: 1.2.0
- is-stream: 1.1.0
- lowercase-keys: 1.0.1
- safe-buffer: 5.2.1
- timed-out: 4.0.1
- unzip-response: 2.0.1
- url-parse-lax: 1.0.0
-
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
has-bigints@1.0.2: {}
- has-flag@3.0.0: {}
-
has-flag@4.0.0: {}
has-property-descriptors@1.0.2:
@@ -14709,15 +13925,6 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
- http-cache-semantics@4.1.1: {}
-
- http2-wrapper@1.0.3:
- dependencies:
- quick-lru: 5.1.1
- resolve-alpn: 1.2.1
-
- human-signals@2.1.0: {}
-
husky@9.1.7: {}
hyphenate-style-name@1.1.0: {}
@@ -14759,8 +13966,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- import-lazy@2.1.0: {}
-
import-lazy@4.0.0: {}
import-meta-resolve@4.1.0: {}
@@ -14786,13 +13991,6 @@ snapshots:
dependencies:
css-in-js-utils: 3.1.0
- internal-ip@6.2.0:
- dependencies:
- default-gateway: 6.0.3
- ipaddr.js: 1.9.1
- is-ip: 3.1.0
- p-event: 4.2.0
-
internal-slot@1.0.7:
dependencies:
es-errors: 1.3.0
@@ -14814,10 +14012,6 @@ snapshots:
jsbn: 1.1.0
sprintf-js: 1.1.3
- ip-regex@4.3.0: {}
-
- ipaddr.js@1.9.1: {}
-
is-alphabetical@2.0.1: {}
is-alphanumerical@2.0.1:
@@ -14870,10 +14064,6 @@ snapshots:
is-callable@1.2.7: {}
- is-ci@1.2.1:
- dependencies:
- ci-info: 1.6.0
-
is-core-module@2.15.1:
dependencies:
hasown: 2.0.2
@@ -14911,8 +14101,6 @@ snapshots:
dependencies:
call-bound: 1.0.4
- is-fullwidth-code-point@2.0.0: {}
-
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@5.0.0:
@@ -14929,23 +14117,12 @@ snapshots:
is-hexadecimal@2.0.1: {}
- is-installed-globally@0.1.0:
- dependencies:
- global-dirs: 0.1.1
- is-path-inside: 1.0.1
-
- is-ip@3.1.0:
- dependencies:
- ip-regex: 4.3.0
-
is-map@2.0.3: {}
is-negative-zero@2.0.3: {}
is-network-error@1.1.0: {}
- is-npm@1.0.0: {}
-
is-number-object@1.0.7:
dependencies:
has-tostringtag: 1.0.2
@@ -14957,20 +14134,12 @@ snapshots:
is-number@7.0.0: {}
- is-obj@1.0.1: {}
-
is-obj@2.0.0: {}
- is-path-inside@1.0.1:
- dependencies:
- path-is-inside: 1.0.2
-
is-plain-obj@4.1.0: {}
is-plain-object@5.0.0: {}
- is-redirect@1.0.0: {}
-
is-regex@1.1.4:
dependencies:
call-bind: 1.0.8
@@ -14983,8 +14152,6 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
- is-retry-allowed@1.2.0: {}
-
is-set@2.0.3: {}
is-shared-array-buffer@1.0.3:
@@ -14995,10 +14162,6 @@ snapshots:
dependencies:
call-bound: 1.0.4
- is-stream@1.1.0: {}
-
- is-stream@2.0.1: {}
-
is-string@1.0.7:
dependencies:
has-tostringtag: 1.0.2
@@ -15130,9 +14293,6 @@ snapshots:
json-stable-stringify-without-jsonify@1.0.1: {}
- json-stringify-safe@5.0.1:
- optional: true
-
json5@1.0.2:
dependencies:
minimist: 1.2.8
@@ -15141,10 +14301,6 @@ snapshots:
jsonc-parser@3.3.1: {}
- jsonfile@4.0.0:
- optionalDependencies:
- graceful-fs: 4.2.11
-
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
@@ -15193,10 +14349,6 @@ snapshots:
kolorist@1.8.0: {}
- latest-version@3.1.0:
- dependencies:
- package-json: 4.0.1
-
less@4.2.0:
dependencies:
copy-anything: 2.0.6
@@ -15356,15 +14508,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- lowercase-keys@1.0.1: {}
-
- lowercase-keys@2.0.0: {}
-
- lru-cache@4.1.5:
- dependencies:
- pseudomap: 1.0.2
- yallist: 2.1.2
-
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -15381,10 +14524,6 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
- make-dir@1.3.0:
- dependencies:
- pify: 3.0.0
-
make-dir@2.1.0:
dependencies:
pify: 4.0.1
@@ -15393,11 +14532,6 @@ snapshots:
marked@14.0.0: {}
- matcher@3.0.0:
- dependencies:
- escape-string-regexp: 4.0.0
- optional: true
-
math-intrinsics@1.1.0: {}
mathml-tag-names@2.1.3: {}
@@ -15503,8 +14637,6 @@ snapshots:
meow@13.2.0: {}
- merge-stream@2.0.0: {}
-
merge2@1.4.1: {}
meta-json-schema@1.19.16: {}
@@ -15652,14 +14784,8 @@ snapshots:
mime@3.0.0: {}
- mimic-fn@2.1.0: {}
-
mimic-function@5.0.1: {}
- mimic-response@1.0.1: {}
-
- mimic-response@3.1.0: {}
-
minimatch@3.0.8:
dependencies:
brace-expansion: 1.1.11
@@ -15838,8 +14964,6 @@ snapshots:
normalize-range@0.1.2: {}
- normalize-url@6.1.0: {}
-
npm-normalize-package-bin@4.0.0: {}
npm-run-all2@8.0.4:
@@ -15853,14 +14977,6 @@ snapshots:
shell-quote: 1.8.1
which: 5.0.0
- npm-run-path@2.0.2:
- dependencies:
- path-key: 2.0.1
-
- npm-run-path@4.0.1:
- dependencies:
- path-key: 3.1.1
-
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -15942,10 +15058,6 @@ snapshots:
dependencies:
wrappy: 1.0.2
- onetime@5.1.2:
- dependencies:
- mimic-fn: 2.1.0
-
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
@@ -16019,14 +15131,6 @@ snapshots:
'@oxc-resolver/binding-win32-ia32-msvc': 11.12.0
'@oxc-resolver/binding-win32-x64-msvc': 11.12.0
- p-cancelable@2.1.1: {}
-
- p-event@4.2.0:
- dependencies:
- p-timeout: 3.2.0
-
- p-finally@1.0.0: {}
-
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -16047,17 +15151,6 @@ snapshots:
dependencies:
is-network-error: 1.1.0
- p-timeout@3.2.0:
- dependencies:
- p-finally: 1.0.0
-
- package-json@4.0.1:
- dependencies:
- got: 6.7.1
- registry-auth-token: 3.4.0
- registry-url: 3.1.0
- semver: 5.7.2
-
package-manager-detector@1.3.0: {}
pako@2.1.0: {}
@@ -16105,10 +15198,6 @@ snapshots:
path-is-absolute@1.0.1:
optional: true
- path-is-inside@1.0.2: {}
-
- path-key@2.0.1: {}
-
path-key@3.1.1: {}
path-parse@1.0.7: {}
@@ -16119,8 +15208,6 @@ snapshots:
pathe@2.0.3: {}
- pend@1.2.0: {}
-
peowly@1.3.2: {}
picocolors@1.1.1: {}
@@ -16135,8 +15222,6 @@ snapshots:
pify@2.3.0: {}
- pify@3.0.0: {}
-
pify@4.0.1:
optional: true
@@ -16242,8 +15327,6 @@ snapshots:
prelude-ls@1.2.1: {}
- prepend-http@1.0.4: {}
-
prettier-linter-helpers@1.0.0:
dependencies:
fast-diff: 1.3.0
@@ -16271,8 +15354,6 @@ snapshots:
prettier@3.6.2: {}
- progress@2.0.3: {}
-
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -16284,13 +15365,6 @@ snapshots:
prr@1.0.1:
optional: true
- pseudomap@1.0.2: {}
-
- pump@3.0.0:
- dependencies:
- end-of-stream: 1.4.4
- once: 1.4.0
-
punycode@2.3.1: {}
quansync@0.2.10: {}
@@ -16299,36 +15373,6 @@ snapshots:
queue-microtask@1.2.3: {}
- quick-lru@5.1.1: {}
-
- rc@1.2.8:
- dependencies:
- deep-extend: 0.6.0
- ini: 1.3.8
- minimist: 1.2.8
- strip-json-comments: 2.0.1
-
- react-devtools-core@7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
- dependencies:
- shell-quote: 1.8.1
- ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)
- transitivePeerDependencies:
- - bufferutil
- - utf-8-validate
-
- react-devtools@7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
- dependencies:
- cross-spawn: 5.1.0
- electron: 23.3.13
- internal-ip: 6.2.0
- minimist: 1.2.8
- react-devtools-core: 7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
- update-notifier: 2.5.0
- transitivePeerDependencies:
- - bufferutil
- - supports-color
- - utf-8-validate
-
react-dom@19.2.0(react@19.2.0):
dependencies:
react: 19.2.0
@@ -16522,15 +15566,6 @@ snapshots:
unicode-match-property-ecmascript: 2.0.0
unicode-match-property-value-ecmascript: 2.2.0
- registry-auth-token@3.4.0:
- dependencies:
- rc: 1.2.8
- safe-buffer: 5.2.1
-
- registry-url@3.1.0:
- dependencies:
- rc: 1.2.8
-
regjsgen@0.8.0: {}
regjsparser@0.12.0:
@@ -16568,8 +15603,6 @@ snapshots:
resize-observer-polyfill@1.5.1: {}
- resolve-alpn@1.2.1: {}
-
resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
@@ -16594,10 +15627,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- responselike@2.0.1:
- dependencies:
- lowercase-keys: 2.0.0
-
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -16607,16 +15636,6 @@ snapshots:
rfdc@1.4.1: {}
- roarr@2.15.4:
- dependencies:
- boolean: 3.2.0
- detect-node: 2.1.0
- globalthis: 1.0.4
- json-stringify-safe: 5.0.1
- semver-compare: 1.0.0
- sprintf-js: 1.1.3
- optional: true
-
robust-predicates@3.0.2: {}
rollup-plugin-visualizer@5.12.0(rollup@4.46.2):
@@ -16683,8 +15702,6 @@ snapshots:
has-symbols: 1.1.0
isarray: 2.0.5
- safe-buffer@5.2.1: {}
-
safe-push-apply@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -16818,15 +15835,9 @@ snapshots:
scule@1.3.0: {}
- semver-compare@1.0.0:
+ semver@5.7.2:
optional: true
- semver-diff@2.1.0:
- dependencies:
- semver: 5.7.2
-
- semver@5.7.2: {}
-
semver@6.3.1: {}
semver@7.5.4:
@@ -16837,11 +15848,6 @@ snapshots:
semver@7.7.3: {}
- serialize-error@7.0.1:
- dependencies:
- type-fest: 0.13.1
- optional: true
-
seroval-plugins@1.3.2(seroval@1.3.2):
dependencies:
seroval: 1.3.2
@@ -16872,16 +15878,10 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.1
- shebang-command@1.2.0:
- dependencies:
- shebang-regex: 1.0.0
-
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
- shebang-regex@1.0.0: {}
-
shebang-regex@3.0.0: {}
shell-quote@1.8.1: {}
@@ -16925,8 +15925,6 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
- signal-exit@3.0.7: {}
-
signal-exit@4.1.0: {}
slash@3.0.0: {}
@@ -17021,11 +16019,6 @@ snapshots:
string-argv@0.3.2: {}
- string-width@2.1.1:
- dependencies:
- is-fullwidth-code-point: 2.0.0
- strip-ansi: 4.0.0
-
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -17099,10 +16092,6 @@ snapshots:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
- strip-ansi@4.0.0:
- dependencies:
- ansi-regex: 3.0.1
-
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -17113,12 +16102,6 @@ snapshots:
strip-bom@3.0.0: {}
- strip-eof@1.0.0: {}
-
- strip-final-newline@2.0.0: {}
-
- strip-json-comments@2.0.1: {}
-
strip-json-comments@3.1.1: {}
strip-json-comments@5.0.2: {}
@@ -17231,16 +16214,6 @@ snapshots:
- supports-color
optional: true
- sumchecker@3.0.1:
- dependencies:
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
-
- supports-color@5.5.0:
- dependencies:
- has-flag: 3.0.0
-
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -17322,10 +16295,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- term-size@1.2.0:
- dependencies:
- execa: 0.7.0
-
terser@5.36.0:
dependencies:
'@jridgewell/source-map': 0.3.6
@@ -17339,8 +16308,6 @@ snapshots:
through@2.3.8: {}
- timed-out@4.0.1: {}
-
tiny-invariant@1.3.3: {}
tiny-warning@1.0.3: {}
@@ -17421,9 +16388,6 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
- type-fest@0.13.1:
- optional: true
-
type@2.7.2: {}
typed-array-buffer@1.0.2:
@@ -17598,10 +16562,6 @@ snapshots:
unplugin: 2.3.11
unplugin-utils: 0.3.1
- unique-string@1.0.0:
- dependencies:
- crypto-random-string: 1.0.0
-
unist-util-is@6.0.0:
dependencies:
'@types/unist': 3.0.2
@@ -17636,8 +16596,6 @@ snapshots:
universal-user-agent@7.0.2: {}
- universalify@0.1.2: {}
-
universalify@2.0.1: {}
unplugin-auto-import@20.3.0:
@@ -17704,8 +16662,6 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.10.1
'@unrs/resolver-binding-win32-x64-msvc': 1.10.1
- unzip-response@2.0.1: {}
-
update-browserslist-db@1.1.3(browserslist@4.25.1):
dependencies:
browserslist: 4.25.1
@@ -17718,27 +16674,10 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
- update-notifier@2.5.0:
- dependencies:
- boxen: 1.3.0
- chalk: 2.4.2
- configstore: 3.1.5
- import-lazy: 2.1.0
- is-ci: 1.2.1
- is-installed-globally: 0.1.0
- is-npm: 1.0.0
- latest-version: 3.1.0
- semver-diff: 2.1.0
- xdg-basedir: 3.0.0
-
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
- url-parse-lax@1.0.0:
- dependencies:
- prepend-http: 1.0.4
-
use-resize-observer@9.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@juggle/resize-observer': 3.4.0
@@ -17984,10 +16923,6 @@ snapshots:
dependencies:
isexe: 3.1.1
- widest-line@2.0.1:
- dependencies:
- string-width: 2.1.1
-
word-wrap@1.2.5: {}
wrap-ansi@7.0.0:
@@ -18010,30 +16945,15 @@ snapshots:
imurmurhash: 0.1.4
slide: 1.1.6
- write-file-atomic@2.4.3:
- dependencies:
- graceful-fs: 4.2.11
- imurmurhash: 0.1.4
- signal-exit: 3.0.7
-
write-file-atomic@5.0.1:
dependencies:
imurmurhash: 0.1.4
signal-exit: 4.1.0
- ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10):
- optionalDependencies:
- bufferutil: 4.0.8
- utf-8-validate: 5.0.10
-
- xdg-basedir@3.0.0: {}
-
y18n@5.0.8: {}
yaeti@0.0.6: {}
- yallist@2.1.2: {}
-
yallist@3.1.1: {}
yallist@4.0.0: {}
@@ -18069,11 +16989,6 @@ snapshots:
y18n: 5.0.8
yargs-parser: 22.0.0
- yauzl@2.10.0:
- dependencies:
- buffer-crc32: 0.2.13
- fd-slicer: 1.1.0
-
yocto-queue@0.1.0: {}
yocto-queue@1.1.1: {}
diff --git a/clash-nyanpasu/scripts/check.ts b/clash-nyanpasu/scripts/check.ts
index 3678746e72..32307c0816 100644
--- a/clash-nyanpasu/scripts/check.ts
+++ b/clash-nyanpasu/scripts/check.ts
@@ -100,8 +100,7 @@ Promise.all(jobs).then(() => {
const commands = [
'pnpm dev - development with react dev tools',
- 'pnpm dev:diff - deadlock development with react dev tools (recommend)',
- 'pnpm tauri:diff - deadlock development',
+ 'pnpm dev:diff - deadlock development (recommend)',
]
consola.log(' next command:\n')
diff --git a/lede/feeds.conf.default b/lede/feeds.conf.default
index 3cb8df49e5..90755bdc19 100644
--- a/lede/feeds.conf.default
+++ b/lede/feeds.conf.default
@@ -1,6 +1,6 @@
src-git packages https://github.com/coolsnowwolf/packages
#src-git luci https://github.com/coolsnowwolf/luci.git
-src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-25.12
+src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-23.05
#src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-24.10
src-git routing https://github.com/coolsnowwolf/routing
src-git telephony https://github.com/coolsnowwolf/telephony.git
diff --git a/lede/include/version.mk b/lede/include/version.mk
index 8f3b93aa50..abd7bce7f3 100644
--- a/lede/include/version.mk
+++ b/lede/include/version.mk
@@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \
sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1))))
VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER))
-VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),25.12-SNAPSHOT)
+VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.3)
VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE))
VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION))
VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO))
-VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/25.12-SNAPSHOT)
+VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.3)
VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST))
VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt)
diff --git a/lede/package/base-files/image-config.in b/lede/package/base-files/image-config.in
index 2cc8a8b2ac..73c62b349a 100644
--- a/lede/package/base-files/image-config.in
+++ b/lede/package/base-files/image-config.in
@@ -190,7 +190,7 @@ if VERSIONOPT
config VERSION_REPO
string
prompt "Release repository"
- default "https://downloads.openwrt.org/releases/25.12-SNAPSHOT"
+ default "https://downloads.openwrt.org/releases/24.10.3"
help
This is the repository address embedded in the image, it defaults
to the trunk snapshot repo; the url may contain the following placeholders:
diff --git a/mihomo/adapter/outbound/sudoku.go b/mihomo/adapter/outbound/sudoku.go
index f9313ca39f..bd393ec616 100644
--- a/mihomo/adapter/outbound/sudoku.go
+++ b/mihomo/adapter/outbound/sudoku.go
@@ -20,17 +20,19 @@ type Sudoku struct {
type SudokuOption struct {
BasicOption
- Name string `proxy:"name"`
- Server string `proxy:"server"`
- Port int `proxy:"port"`
- Key string `proxy:"key"`
- AEADMethod string `proxy:"aead-method,omitempty"`
- PaddingMin *int `proxy:"padding-min,omitempty"`
- PaddingMax *int `proxy:"padding-max,omitempty"`
- TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
- EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"`
- HTTPMask bool `proxy:"http-mask,omitempty"`
- CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
+ Name string `proxy:"name"`
+ Server string `proxy:"server"`
+ Port int `proxy:"port"`
+ Key string `proxy:"key"`
+ AEADMethod string `proxy:"aead-method,omitempty"`
+ PaddingMin *int `proxy:"padding-min,omitempty"`
+ PaddingMax *int `proxy:"padding-max,omitempty"`
+ TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
+ EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"`
+ HTTPMask bool `proxy:"http-mask,omitempty"`
+ HTTPMaskStrategy string `proxy:"http-mask-strategy,omitempty"` // "random" (default), "post", "websocket"
+ CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
+ CustomTables []string `proxy:"custom-tables,omitempty"` // optional table rotation patterns, overrides custom-table when non-empty
}
// DialContext implements C.ProxyAdapter
@@ -54,7 +56,9 @@ func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
defer done(&err)
}
- c, err = sudoku.ClientHandshake(c, cfg)
+ c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{
+ HTTPMaskStrategy: s.option.HTTPMaskStrategy,
+ })
if err != nil {
return nil, err
}
@@ -97,7 +101,9 @@ func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
defer done(&err)
}
- c, err = sudoku.ClientHandshake(c, cfg)
+ c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{
+ HTTPMaskStrategy: s.option.HTTPMaskStrategy,
+ })
if err != nil {
return nil, err
}
@@ -185,11 +191,15 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) {
HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds,
DisableHTTPMask: !option.HTTPMask,
}
- table, err := sudoku.NewTableWithCustom(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable)
+ tables, err := sudoku.NewTablesWithCustomPatterns(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable, option.CustomTables)
if err != nil {
- return nil, fmt.Errorf("build table failed: %w", err)
+ return nil, fmt.Errorf("build table(s) failed: %w", err)
+ }
+ if len(tables) == 1 {
+ baseConf.Table = tables[0]
+ } else {
+ baseConf.Tables = tables
}
- baseConf.Table = table
if option.AEADMethod != "" {
baseConf.AEADMethod = option.AEADMethod
}
diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml
index 26b1af2c90..04d15bd202 100644
--- a/mihomo/docs/config.yaml
+++ b/mihomo/docs/config.yaml
@@ -1049,7 +1049,9 @@ proxies: # socks5
padding-max: 7 # 最大填充字节数
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
+ # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
http-mask: true # 是否启用http掩码
+ # http-mask-strategy: random # 可选:random(默认)、post、websocket;仅在 http-mask=true 时生效
enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与服务端端保持相同(如果此处为false,则要求aead不可为none)
# anytls
@@ -1591,6 +1593,7 @@ listeners:
padding-max: 15 # 填充最大长度,均不建议过大
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
+ # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
handshake-timeout: 5 # optional
enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与客户端保持相同(如果此处为false,则要求aead不可为none)
diff --git a/mihomo/go.mod b/mihomo/go.mod
index 8fb7e51f1f..c0160c2630 100644
--- a/mihomo/go.mod
+++ b/mihomo/go.mod
@@ -43,7 +43,7 @@ require (
github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
- github.com/saba-futai/sudoku v0.0.2-c
+ github.com/saba-futai/sudoku v0.0.2-d
github.com/sagernet/cors v1.2.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/samber/lo v1.52.0
diff --git a/mihomo/go.sum b/mihomo/go.sum
index 08d1d002a8..c6fba8e505 100644
--- a/mihomo/go.sum
+++ b/mihomo/go.sum
@@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/saba-futai/sudoku v0.0.2-c h1:0CaoCKx4Br8UL97fnIxn8Y7rnQpflBza7kfaIrdg2rI=
-github.com/saba-futai/sudoku v0.0.2-c/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo=
+github.com/saba-futai/sudoku v0.0.2-d h1:HW/gIyNUFcDchpMN+ZhluM86U/HGkWkkRV+9Km6WZM8=
+github.com/saba-futai/sudoku v0.0.2-d/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
diff --git a/mihomo/listener/config/sudoku.go b/mihomo/listener/config/sudoku.go
index b581f3f5ce..848db875d7 100644
--- a/mihomo/listener/config/sudoku.go
+++ b/mihomo/listener/config/sudoku.go
@@ -9,16 +9,17 @@ import (
// SudokuServer describes a Sudoku inbound server configuration.
// It is internal to the listener layer and mainly used for logging and wiring.
type SudokuServer struct {
- Enable bool `json:"enable"`
- Listen string `json:"listen"`
- Key string `json:"key"`
- AEADMethod string `json:"aead-method,omitempty"`
- PaddingMin *int `json:"padding-min,omitempty"`
- PaddingMax *int `json:"padding-max,omitempty"`
- TableType string `json:"table-type,omitempty"`
- HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"`
- EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"`
- CustomTable string `json:"custom-table,omitempty"`
+ Enable bool `json:"enable"`
+ Listen string `json:"listen"`
+ Key string `json:"key"`
+ AEADMethod string `json:"aead-method,omitempty"`
+ PaddingMin *int `json:"padding-min,omitempty"`
+ PaddingMax *int `json:"padding-max,omitempty"`
+ TableType string `json:"table-type,omitempty"`
+ HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"`
+ EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"`
+ CustomTable string `json:"custom-table,omitempty"`
+ CustomTables []string `json:"custom-tables,omitempty"`
// mihomo private extension (not the part of standard Sudoku protocol)
MuxOption sing.MuxOption `json:"mux-option,omitempty"`
diff --git a/mihomo/listener/inbound/sudoku.go b/mihomo/listener/inbound/sudoku.go
index 6c4092473c..433976026d 100644
--- a/mihomo/listener/inbound/sudoku.go
+++ b/mihomo/listener/inbound/sudoku.go
@@ -13,14 +13,15 @@ import (
type SudokuOption struct {
BaseOption
- Key string `inbound:"key"`
- AEADMethod string `inbound:"aead-method,omitempty"`
- PaddingMin *int `inbound:"padding-min,omitempty"`
- PaddingMax *int `inbound:"padding-max,omitempty"`
- TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
- HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"`
- EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"`
- CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
+ Key string `inbound:"key"`
+ AEADMethod string `inbound:"aead-method,omitempty"`
+ PaddingMin *int `inbound:"padding-min,omitempty"`
+ PaddingMax *int `inbound:"padding-max,omitempty"`
+ TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy"
+ HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"`
+ EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"`
+ CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv
+ CustomTables []string `inbound:"custom-tables,omitempty"`
// mihomo private extension (not the part of standard Sudoku protocol)
MuxOption MuxOption `inbound:"mux-option,omitempty"`
@@ -57,6 +58,7 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) {
HandshakeTimeoutSecond: options.HandshakeTimeoutSecond,
EnablePureDownlink: options.EnablePureDownlink,
CustomTable: options.CustomTable,
+ CustomTables: options.CustomTables,
}
serverConf.MuxOption = options.MuxOption.Build()
diff --git a/mihomo/listener/sudoku/server.go b/mihomo/listener/sudoku/server.go
index 8351e36597..e90e231c1d 100644
--- a/mihomo/listener/sudoku/server.go
+++ b/mihomo/listener/sudoku/server.go
@@ -166,7 +166,7 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition)
enablePureDownlink = *config.EnablePureDownlink
}
- table, err := sudoku.NewTableWithCustom(config.Key, tableType, config.CustomTable)
+ tables, err := sudoku.NewTablesWithCustomPatterns(config.Key, tableType, config.CustomTable, config.CustomTables)
if err != nil {
_ = l.Close()
return nil, err
@@ -180,12 +180,16 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition)
protoConf := sudoku.ProtocolConfig{
Key: config.Key,
AEADMethod: defaultConf.AEADMethod,
- Table: table,
PaddingMin: paddingMin,
PaddingMax: paddingMax,
EnablePureDownlink: enablePureDownlink,
HandshakeTimeoutSeconds: handshakeTimeout,
}
+ if len(tables) == 1 {
+ protoConf.Table = tables[0]
+ } else {
+ protoConf.Tables = tables
+ }
if config.AEADMethod != "" {
protoConf.AEADMethod = config.AEADMethod
}
diff --git a/mihomo/transport/sudoku/features_test.go b/mihomo/transport/sudoku/features_test.go
new file mode 100644
index 0000000000..8eb3aedd25
--- /dev/null
+++ b/mihomo/transport/sudoku/features_test.go
@@ -0,0 +1,192 @@
+package sudoku
+
+import (
+ "bytes"
+ "io"
+ "net"
+ "testing"
+ "time"
+
+ sudokuobfs "github.com/saba-futai/sudoku/pkg/obfs/sudoku"
+)
+
+type discardConn struct{}
+
+func (discardConn) Read([]byte) (int, error) { return 0, io.EOF }
+func (discardConn) Write(p []byte) (int, error) { return len(p), nil }
+func (discardConn) Close() error { return nil }
+func (discardConn) LocalAddr() net.Addr { return nil }
+func (discardConn) RemoteAddr() net.Addr { return nil }
+func (discardConn) SetDeadline(time.Time) error { return nil }
+func (discardConn) SetReadDeadline(time.Time) error { return nil }
+func (discardConn) SetWriteDeadline(time.Time) error { return nil }
+
+func TestSudokuObfsWriter_ReducesWriteAllocs(t *testing.T) {
+ table := sudokuobfs.NewTable("alloc-seed", "prefer_ascii")
+ w := newSudokuObfsWriter(discardConn{}, table, 0, 0)
+
+ payload := bytes.Repeat([]byte{0x42}, 2048)
+ if _, err := w.Write(payload); err != nil {
+ t.Fatalf("warmup write: %v", err)
+ }
+
+ allocs := testing.AllocsPerRun(100, func() {
+ if _, err := w.Write(payload); err != nil {
+ t.Fatalf("write: %v", err)
+ }
+ })
+ if allocs != 0 {
+ t.Fatalf("expected 0 allocs/run, got %.2f", allocs)
+ }
+}
+
+func TestHTTPMaskStrategy_WebSocketAndPost(t *testing.T) {
+ key := "mask-test-key"
+ target := "1.1.1.1:80"
+ table := sudokuobfs.NewTable("mask-seed", "prefer_ascii")
+
+ base := DefaultConfig()
+ base.Key = key
+ base.AEADMethod = "chacha20-poly1305"
+ base.Table = table
+ base.PaddingMin = 0
+ base.PaddingMax = 0
+ base.EnablePureDownlink = true
+ base.HandshakeTimeoutSeconds = 5
+ base.DisableHTTPMask = false
+ base.ServerAddress = "example.com:443"
+
+ cases := []string{"post", "websocket"}
+ for _, strategy := range cases {
+ t.Run(strategy, func(t *testing.T) {
+ serverConn, clientConn := net.Pipe()
+ defer serverConn.Close()
+ defer clientConn.Close()
+
+ errCh := make(chan error, 1)
+ go func() {
+ defer close(errCh)
+ session, err := ServerHandshake(serverConn, base)
+ if err != nil {
+ errCh <- err
+ return
+ }
+ defer session.Conn.Close()
+ if session.Type != SessionTypeTCP {
+ errCh <- io.ErrUnexpectedEOF
+ return
+ }
+ if session.Target != target {
+ errCh <- io.ErrClosedPipe
+ return
+ }
+ _, _ = session.Conn.Write([]byte("ok"))
+ }()
+
+ cConn, err := ClientHandshakeWithOptions(clientConn, base, ClientHandshakeOptions{HTTPMaskStrategy: strategy})
+ if err != nil {
+ t.Fatalf("client handshake: %v", err)
+ }
+ defer cConn.Close()
+
+ addrBuf, err := EncodeAddress(target)
+ if err != nil {
+ t.Fatalf("encode addr: %v", err)
+ }
+ if _, err := cConn.Write(addrBuf); err != nil {
+ t.Fatalf("write addr: %v", err)
+ }
+
+ buf := make([]byte, 2)
+ if _, err := io.ReadFull(cConn, buf); err != nil {
+ t.Fatalf("read: %v", err)
+ }
+ if string(buf) != "ok" {
+ t.Fatalf("unexpected payload: %q", buf)
+ }
+
+ if err := <-errCh; err != nil {
+ t.Fatalf("server: %v", err)
+ }
+ })
+ }
+}
+
+func TestCustomTablesRotation_ProbedByServer(t *testing.T) {
+ key := "rotate-test-key"
+ target := "8.8.8.8:53"
+
+ t1, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "xpxvvpvv")
+ if err != nil {
+ t.Fatalf("t1: %v", err)
+ }
+ t2, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "vxpvxvvp")
+ if err != nil {
+ t.Fatalf("t2: %v", err)
+ }
+
+ serverCfg := DefaultConfig()
+ serverCfg.Key = key
+ serverCfg.AEADMethod = "chacha20-poly1305"
+ serverCfg.Tables = []*sudokuobfs.Table{t1, t2}
+ serverCfg.PaddingMin = 0
+ serverCfg.PaddingMax = 0
+ serverCfg.EnablePureDownlink = true
+ serverCfg.HandshakeTimeoutSeconds = 5
+ serverCfg.DisableHTTPMask = true
+
+ clientCfg := DefaultConfig()
+ *clientCfg = *serverCfg
+ clientCfg.ServerAddress = "example.com:443"
+
+ for i := 0; i < 10; i++ {
+ serverConn, clientConn := net.Pipe()
+
+ errCh := make(chan error, 1)
+ go func() {
+ defer close(errCh)
+ defer serverConn.Close()
+ session, err := ServerHandshake(serverConn, serverCfg)
+ if err != nil {
+ errCh <- err
+ return
+ }
+ defer session.Conn.Close()
+ if session.Type != SessionTypeTCP {
+ errCh <- io.ErrUnexpectedEOF
+ return
+ }
+ if session.Target != target {
+ errCh <- io.ErrClosedPipe
+ return
+ }
+ _, _ = session.Conn.Write([]byte{0xaa, 0xbb, 0xcc})
+ }()
+
+ cConn, err := ClientHandshake(clientConn, clientCfg)
+ if err != nil {
+ t.Fatalf("client handshake: %v", err)
+ }
+
+ addrBuf, err := EncodeAddress(target)
+ if err != nil {
+ t.Fatalf("encode addr: %v", err)
+ }
+ if _, err := cConn.Write(addrBuf); err != nil {
+ t.Fatalf("write addr: %v", err)
+ }
+
+ buf := make([]byte, 3)
+ if _, err := io.ReadFull(cConn, buf); err != nil {
+ t.Fatalf("read: %v", err)
+ }
+ if !bytes.Equal(buf, []byte{0xaa, 0xbb, 0xcc}) {
+ t.Fatalf("payload mismatch: %x", buf)
+ }
+ _ = cConn.Close()
+
+ if err := <-errCh; err != nil {
+ t.Fatalf("server: %v", err)
+ }
+ }
+}
diff --git a/mihomo/transport/sudoku/handshake.go b/mihomo/transport/sudoku/handshake.go
index d34fceb40e..989d281323 100644
--- a/mihomo/transport/sudoku/handshake.go
+++ b/mihomo/transport/sudoku/handshake.go
@@ -2,11 +2,13 @@ package sudoku
import (
"bufio"
+ "crypto/rand"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"net"
+ "strings"
"time"
"github.com/saba-futai/sudoku/apis"
@@ -110,25 +112,35 @@ func downlinkMode(cfg *apis.ProtocolConfig) byte {
return downlinkModePacked
}
-func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig) net.Conn {
- base := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false)
+func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table) net.Conn {
+ baseReader := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, false)
+ baseWriter := newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax)
if cfg.EnablePureDownlink {
- return base
+ return &directionalConn{
+ Conn: raw,
+ reader: baseReader,
+ writer: baseWriter,
+ }
}
- packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax)
+ packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax)
return &directionalConn{
Conn: raw,
reader: packed,
- writer: base,
+ writer: baseWriter,
}
}
-func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, record bool) (*sudoku.Conn, net.Conn) {
- uplink := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, record)
+func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table, record bool) (*sudoku.Conn, net.Conn) {
+ uplink := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, record)
if cfg.EnablePureDownlink {
- return uplink, uplink
+ downlink := &directionalConn{
+ Conn: raw,
+ reader: uplink,
+ writer: newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax),
+ }
+ return uplink, downlink
}
- packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax)
+ packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax)
return uplink, &directionalConn{
Conn: raw,
reader: uplink,
@@ -170,8 +182,19 @@ func ClientAEADSeed(key string) string {
return key
}
+type ClientHandshakeOptions struct {
+ // HTTPMaskStrategy controls how the client generates the HTTP mask header when DisableHTTPMask=false.
+ // Supported: ""/"random" (default), "post", "websocket".
+ HTTPMaskStrategy string
+}
+
// ClientHandshake performs the client-side Sudoku handshake (without sending target address).
func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, error) {
+ return ClientHandshakeWithOptions(rawConn, cfg, ClientHandshakeOptions{})
+}
+
+// ClientHandshakeWithOptions performs the client-side Sudoku handshake (without sending target address).
+func ClientHandshakeWithOptions(rawConn net.Conn, cfg *apis.ProtocolConfig, opt ClientHandshakeOptions) (net.Conn, error) {
if cfg == nil {
return nil, fmt.Errorf("config is required")
}
@@ -180,18 +203,26 @@ func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, erro
}
if !cfg.DisableHTTPMask {
- if err := httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil {
+ if err := WriteHTTPMaskHeader(rawConn, cfg.ServerAddress, opt.HTTPMaskStrategy); err != nil {
return nil, fmt.Errorf("write http mask failed: %w", err)
}
}
- obfsConn := buildClientObfsConn(rawConn, cfg)
+ table, tableID, err := pickClientTable(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ obfsConn := buildClientObfsConn(rawConn, cfg, table)
cConn, err := crypto.NewAEADConn(obfsConn, ClientAEADSeed(cfg.Key), cfg.AEADMethod)
if err != nil {
return nil, fmt.Errorf("setup crypto failed: %w", err)
}
handshake := buildHandshakePayload(cfg.Key)
+ if len(tableCandidates(cfg)) > 1 {
+ handshake[15] = tableID
+ }
if _, err := cConn.Write(handshake[:]); err != nil {
cConn.Close()
return nil, fmt.Errorf("send handshake failed: %w", err)
@@ -218,21 +249,25 @@ func ServerHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (*ServerSession
handshakeTimeout = 5 * time.Second
}
+ rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout))
+
bufReader := bufio.NewReader(rawConn)
if !cfg.DisableHTTPMask {
- if peek, _ := bufReader.Peek(4); len(peek) == 4 && string(peek) == "POST" {
+ if peek, err := bufReader.Peek(4); err == nil && httpmask.LooksLikeHTTPRequestStart(peek) {
if _, err := httpmask.ConsumeHeader(bufReader); err != nil {
return nil, fmt.Errorf("invalid http header: %w", err)
}
}
}
- rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout))
- bConn := &bufferedConn{
- Conn: rawConn,
- r: bufReader,
+ selectedTable, preRead, err := selectTableByProbe(bufReader, cfg, tableCandidates(cfg))
+ if err != nil {
+ return nil, err
}
- sConn, obfsConn := buildServerObfsConn(bConn, cfg, true)
+
+ baseConn := &preBufferedConn{Conn: rawConn, buf: preRead}
+ bConn := &bufferedConn{Conn: baseConn, r: bufio.NewReader(baseConn)}
+ sConn, obfsConn := buildServerObfsConn(bConn, cfg, selectedTable, true)
cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod)
if err != nil {
return nil, fmt.Errorf("crypto setup failed: %w", err)
@@ -313,3 +348,24 @@ func GenKeyPair() (privateKey, publicKey string, err error) {
publicKey = crypto.EncodePoint(pair.Public) // Master Public Key for server
return
}
+
+func normalizeHTTPMaskStrategy(strategy string) string {
+ s := strings.TrimSpace(strings.ToLower(strategy))
+ switch s {
+ case "", "random":
+ return "random"
+ case "ws":
+ return "websocket"
+ default:
+ return s
+ }
+}
+
+// randomByte returns a cryptographically random byte (with a math/rand fallback).
+func randomByte() byte {
+ var b [1]byte
+ if _, err := rand.Read(b[:]); err == nil {
+ return b[0]
+ }
+ return byte(time.Now().UnixNano())
+}
diff --git a/mihomo/transport/sudoku/httpmask_strategy.go b/mihomo/transport/sudoku/httpmask_strategy.go
new file mode 100644
index 0000000000..dc90991d13
--- /dev/null
+++ b/mihomo/transport/sudoku/httpmask_strategy.go
@@ -0,0 +1,179 @@
+package sudoku
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "math/rand"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/saba-futai/sudoku/pkg/obfs/httpmask"
+)
+
+var (
+ httpMaskUserAgents = []string{
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+ }
+ httpMaskAccepts = []string{
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
+ "application/json, text/plain, */*",
+ "application/octet-stream",
+ "*/*",
+ }
+ httpMaskAcceptLanguages = []string{
+ "en-US,en;q=0.9",
+ "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
+ }
+ httpMaskAcceptEncodings = []string{
+ "gzip, deflate, br",
+ "gzip, deflate",
+ }
+ httpMaskPaths = []string{
+ "/api/v1/upload",
+ "/data/sync",
+ "/v1/telemetry",
+ "/session",
+ "/ws",
+ }
+ httpMaskContentTypes = []string{
+ "application/octet-stream",
+ "application/json",
+ }
+)
+
+var (
+ httpMaskRngPool = sync.Pool{
+ New: func() any { return rand.New(rand.NewSource(time.Now().UnixNano())) },
+ }
+ httpMaskBufPool = sync.Pool{
+ New: func() any {
+ b := make([]byte, 0, 1024)
+ return &b
+ },
+ }
+)
+
+func trimPortForHost(host string) string {
+ if host == "" {
+ return host
+ }
+ h, _, err := net.SplitHostPort(host)
+ if err == nil && h != "" {
+ return h
+ }
+ return host
+}
+
+func appendCommonHeaders(buf []byte, host string, r *rand.Rand) []byte {
+ ua := httpMaskUserAgents[r.Intn(len(httpMaskUserAgents))]
+ accept := httpMaskAccepts[r.Intn(len(httpMaskAccepts))]
+ lang := httpMaskAcceptLanguages[r.Intn(len(httpMaskAcceptLanguages))]
+ enc := httpMaskAcceptEncodings[r.Intn(len(httpMaskAcceptEncodings))]
+
+ buf = append(buf, "Host: "...)
+ buf = append(buf, host...)
+ buf = append(buf, "\r\nUser-Agent: "...)
+ buf = append(buf, ua...)
+ buf = append(buf, "\r\nAccept: "...)
+ buf = append(buf, accept...)
+ buf = append(buf, "\r\nAccept-Language: "...)
+ buf = append(buf, lang...)
+ buf = append(buf, "\r\nAccept-Encoding: "...)
+ buf = append(buf, enc...)
+ buf = append(buf, "\r\nConnection: keep-alive\r\n"...)
+ buf = append(buf, "Cache-Control: no-cache\r\nPragma: no-cache\r\n"...)
+ return buf
+}
+
+// WriteHTTPMaskHeader writes an HTTP/1.x request header as a mask, according to strategy.
+// Supported strategies: ""/"random", "post", "websocket".
+func WriteHTTPMaskHeader(w io.Writer, host string, strategy string) error {
+ switch normalizeHTTPMaskStrategy(strategy) {
+ case "random":
+ return httpmask.WriteRandomRequestHeader(w, host)
+ case "post":
+ return writeHTTPMaskPOST(w, host)
+ case "websocket":
+ return writeHTTPMaskWebSocket(w, host)
+ default:
+ return fmt.Errorf("unsupported http-mask-strategy: %s", strategy)
+ }
+}
+
+func writeHTTPMaskPOST(w io.Writer, host string) error {
+ r := httpMaskRngPool.Get().(*rand.Rand)
+ defer httpMaskRngPool.Put(r)
+
+ path := httpMaskPaths[r.Intn(len(httpMaskPaths))]
+ ctype := httpMaskContentTypes[r.Intn(len(httpMaskContentTypes))]
+
+ bufPtr := httpMaskBufPool.Get().(*[]byte)
+ buf := *bufPtr
+ buf = buf[:0]
+ defer func() {
+ if cap(buf) <= 4096 {
+ *bufPtr = buf
+ httpMaskBufPool.Put(bufPtr)
+ }
+ }()
+
+ const minCL = int64(4 * 1024)
+ const maxCL = int64(10 * 1024 * 1024)
+ contentLength := minCL + r.Int63n(maxCL-minCL+1)
+
+ buf = append(buf, "POST "...)
+ buf = append(buf, path...)
+ buf = append(buf, " HTTP/1.1\r\n"...)
+ buf = appendCommonHeaders(buf, host, r)
+ buf = append(buf, "Content-Type: "...)
+ buf = append(buf, ctype...)
+ buf = append(buf, "\r\nContent-Length: "...)
+ buf = strconv.AppendInt(buf, contentLength, 10)
+ buf = append(buf, "\r\n\r\n"...)
+
+ _, err := w.Write(buf)
+ return err
+}
+
+func writeHTTPMaskWebSocket(w io.Writer, host string) error {
+ r := httpMaskRngPool.Get().(*rand.Rand)
+ defer httpMaskRngPool.Put(r)
+
+ path := httpMaskPaths[r.Intn(len(httpMaskPaths))]
+
+ bufPtr := httpMaskBufPool.Get().(*[]byte)
+ buf := *bufPtr
+ buf = buf[:0]
+ defer func() {
+ if cap(buf) <= 4096 {
+ *bufPtr = buf
+ httpMaskBufPool.Put(bufPtr)
+ }
+ }()
+
+ hostNoPort := trimPortForHost(host)
+ var keyBytes [16]byte
+ for i := 0; i < len(keyBytes); i++ {
+ keyBytes[i] = byte(r.Intn(256))
+ }
+ var wsKey [24]byte
+ base64.StdEncoding.Encode(wsKey[:], keyBytes[:])
+
+ buf = append(buf, "GET "...)
+ buf = append(buf, path...)
+ buf = append(buf, " HTTP/1.1\r\n"...)
+ buf = appendCommonHeaders(buf, host, r)
+ buf = append(buf, "Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...)
+ buf = append(buf, wsKey[:]...)
+ buf = append(buf, "\r\nOrigin: https://"...)
+ buf = append(buf, hostNoPort...)
+ buf = append(buf, "\r\n\r\n"...)
+
+ _, err := w.Write(buf)
+ return err
+}
diff --git a/mihomo/transport/sudoku/obfs_writer.go b/mihomo/transport/sudoku/obfs_writer.go
new file mode 100644
index 0000000000..f980359119
--- /dev/null
+++ b/mihomo/transport/sudoku/obfs_writer.go
@@ -0,0 +1,113 @@
+package sudoku
+
+import (
+ crypto_rand "crypto/rand"
+ "encoding/binary"
+ "math/rand"
+ "net"
+
+ "github.com/saba-futai/sudoku/pkg/obfs/sudoku"
+)
+
+// perm4 matches github.com/saba-futai/sudoku/pkg/obfs/sudoku perm4.
+var perm4 = [24][4]byte{
+ {0, 1, 2, 3},
+ {0, 1, 3, 2},
+ {0, 2, 1, 3},
+ {0, 2, 3, 1},
+ {0, 3, 1, 2},
+ {0, 3, 2, 1},
+ {1, 0, 2, 3},
+ {1, 0, 3, 2},
+ {1, 2, 0, 3},
+ {1, 2, 3, 0},
+ {1, 3, 0, 2},
+ {1, 3, 2, 0},
+ {2, 0, 1, 3},
+ {2, 0, 3, 1},
+ {2, 1, 0, 3},
+ {2, 1, 3, 0},
+ {2, 3, 0, 1},
+ {2, 3, 1, 0},
+ {3, 0, 1, 2},
+ {3, 0, 2, 1},
+ {3, 1, 0, 2},
+ {3, 1, 2, 0},
+ {3, 2, 0, 1},
+ {3, 2, 1, 0},
+}
+
+type sudokuObfsWriter struct {
+ conn net.Conn
+ table *sudoku.Table
+ rng *rand.Rand
+ paddingRate float32
+
+ outBuf []byte
+ pads []byte
+ padLen int
+}
+
+func newSudokuObfsWriter(conn net.Conn, table *sudoku.Table, pMin, pMax int) *sudokuObfsWriter {
+ var seedBytes [8]byte
+ if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
+ binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
+ }
+ seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
+ localRng := rand.New(rand.NewSource(seed))
+
+ min := float32(pMin) / 100.0
+ span := float32(pMax-pMin) / 100.0
+ rate := min + localRng.Float32()*span
+
+ w := &sudokuObfsWriter{
+ conn: conn,
+ table: table,
+ rng: localRng,
+ paddingRate: rate,
+ }
+ w.pads = table.PaddingPool
+ w.padLen = len(w.pads)
+ return w
+}
+
+func (w *sudokuObfsWriter) Write(p []byte) (int, error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+
+ // Worst-case: 4 hints + up to 6 paddings per input byte.
+ needed := len(p)*10 + 1
+ if cap(w.outBuf) < needed {
+ w.outBuf = make([]byte, 0, needed)
+ }
+ out := w.outBuf[:0]
+
+ pads := w.pads
+ padLen := w.padLen
+
+ for _, b := range p {
+ if padLen > 0 && w.rng.Float32() < w.paddingRate {
+ out = append(out, pads[w.rng.Intn(padLen)])
+ }
+
+ puzzles := w.table.EncodeTable[b]
+ puzzle := puzzles[w.rng.Intn(len(puzzles))]
+
+ perm := perm4[w.rng.Intn(len(perm4))]
+ for _, idx := range perm {
+ if padLen > 0 && w.rng.Float32() < w.paddingRate {
+ out = append(out, pads[w.rng.Intn(padLen)])
+ }
+ out = append(out, puzzle[idx])
+ }
+ }
+
+ if padLen > 0 && w.rng.Float32() < w.paddingRate {
+ out = append(out, pads[w.rng.Intn(padLen)])
+ }
+
+ w.outBuf = out
+ _, err := w.conn.Write(out)
+ return len(p), err
+}
diff --git a/mihomo/transport/sudoku/table_probe.go b/mihomo/transport/sudoku/table_probe.go
new file mode 100644
index 0000000000..f12c172226
--- /dev/null
+++ b/mihomo/transport/sudoku/table_probe.go
@@ -0,0 +1,152 @@
+package sudoku
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "time"
+
+ "github.com/saba-futai/sudoku/apis"
+ "github.com/saba-futai/sudoku/pkg/crypto"
+ "github.com/saba-futai/sudoku/pkg/obfs/sudoku"
+)
+
+func tableCandidates(cfg *apis.ProtocolConfig) []*sudoku.Table {
+ if cfg == nil {
+ return nil
+ }
+ if len(cfg.Tables) > 0 {
+ return cfg.Tables
+ }
+ if cfg.Table != nil {
+ return []*sudoku.Table{cfg.Table}
+ }
+ return nil
+}
+
+func pickClientTable(cfg *apis.ProtocolConfig) (*sudoku.Table, byte, error) {
+ candidates := tableCandidates(cfg)
+ if len(candidates) == 0 {
+ return nil, 0, fmt.Errorf("no table configured")
+ }
+ if len(candidates) == 1 {
+ return candidates[0], 0, nil
+ }
+ idx := int(randomByte()) % len(candidates)
+ return candidates[idx], byte(idx), nil
+}
+
+type readOnlyConn struct {
+ *bytes.Reader
+}
+
+func (c *readOnlyConn) Write([]byte) (int, error) { return 0, io.ErrClosedPipe }
+func (c *readOnlyConn) Close() error { return nil }
+func (c *readOnlyConn) LocalAddr() net.Addr { return nil }
+func (c *readOnlyConn) RemoteAddr() net.Addr { return nil }
+func (c *readOnlyConn) SetDeadline(time.Time) error { return nil }
+func (c *readOnlyConn) SetReadDeadline(time.Time) error { return nil }
+func (c *readOnlyConn) SetWriteDeadline(time.Time) error { return nil }
+
+func drainBuffered(r *bufio.Reader) ([]byte, error) {
+ n := r.Buffered()
+ if n <= 0 {
+ return nil, nil
+ }
+ out := make([]byte, n)
+ _, err := io.ReadFull(r, out)
+ return out, err
+}
+
+func probeHandshakeBytes(probe []byte, cfg *apis.ProtocolConfig, table *sudoku.Table) error {
+ rc := &readOnlyConn{Reader: bytes.NewReader(probe)}
+ _, obfsConn := buildServerObfsConn(rc, cfg, table, false)
+ cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod)
+ if err != nil {
+ return err
+ }
+
+ var handshakeBuf [16]byte
+ if _, err := io.ReadFull(cConn, handshakeBuf[:]); err != nil {
+ return err
+ }
+ ts := int64(binary.BigEndian.Uint64(handshakeBuf[:8]))
+ if absInt64(time.Now().Unix()-ts) > 60 {
+ return fmt.Errorf("timestamp skew/replay detected")
+ }
+
+ modeBuf := []byte{0}
+ if _, err := io.ReadFull(cConn, modeBuf); err != nil {
+ return err
+ }
+ if modeBuf[0] != downlinkMode(cfg) {
+ return fmt.Errorf("downlink mode mismatch")
+ }
+
+ return nil
+}
+
+func selectTableByProbe(r *bufio.Reader, cfg *apis.ProtocolConfig, tables []*sudoku.Table) (*sudoku.Table, []byte, error) {
+ const (
+ maxProbeBytes = 64 * 1024
+ readChunk = 4 * 1024
+ )
+ if len(tables) == 0 {
+ return nil, nil, fmt.Errorf("no table candidates")
+ }
+ if len(tables) > 255 {
+ return nil, nil, fmt.Errorf("too many table candidates: %d", len(tables))
+ }
+
+ probe, err := drainBuffered(r)
+ if err != nil {
+ return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
+ }
+
+ tmp := make([]byte, readChunk)
+ for {
+ if len(tables) == 1 {
+ tail, err := drainBuffered(r)
+ if err != nil {
+ return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
+ }
+ probe = append(probe, tail...)
+ return tables[0], probe, nil
+ }
+
+ needMore := false
+ for _, table := range tables {
+ err := probeHandshakeBytes(probe, cfg, table)
+ if err == nil {
+ tail, err := drainBuffered(r)
+ if err != nil {
+ return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
+ }
+ probe = append(probe, tail...)
+ return table, probe, nil
+ }
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ needMore = true
+ }
+ }
+
+ if !needMore {
+ return nil, probe, fmt.Errorf("handshake table selection failed")
+ }
+ if len(probe) >= maxProbeBytes {
+ return nil, probe, fmt.Errorf("handshake probe exceeded %d bytes", maxProbeBytes)
+ }
+
+ n, err := r.Read(tmp)
+ if n > 0 {
+ probe = append(probe, tmp[:n]...)
+ }
+ if err != nil {
+ return nil, probe, fmt.Errorf("handshake probe read failed: %w", err)
+ }
+ }
+}
diff --git a/mihomo/transport/sudoku/tables.go b/mihomo/transport/sudoku/tables.go
new file mode 100644
index 0000000000..429a4ab327
--- /dev/null
+++ b/mihomo/transport/sudoku/tables.go
@@ -0,0 +1,30 @@
+package sudoku
+
+import (
+ "strings"
+
+ "github.com/saba-futai/sudoku/pkg/obfs/sudoku"
+)
+
+// NewTablesWithCustomPatterns builds one or more obfuscation tables from x/v/p custom patterns.
+// When customTables is non-empty it overrides customTable (matching upstream Sudoku behavior).
+func NewTablesWithCustomPatterns(key string, tableType string, customTable string, customTables []string) ([]*sudoku.Table, error) {
+ patterns := customTables
+ if len(patterns) == 0 && strings.TrimSpace(customTable) != "" {
+ patterns = []string{customTable}
+ }
+ if len(patterns) == 0 {
+ patterns = []string{""}
+ }
+
+ tables := make([]*sudoku.Table, 0, len(patterns))
+ for _, pattern := range patterns {
+ pattern = strings.TrimSpace(pattern)
+ t, err := NewTableWithCustom(key, tableType, pattern)
+ if err != nil {
+ return nil, err
+ }
+ tables = append(tables, t)
+ }
+ return tables, nil
+}
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
index ac109cd95f..e822fb6d65 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
+++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
@@ -308,6 +308,17 @@ o = s:option(DummyValue, "switch_mode", " ")
o.template = appname .. "/global/proxy"
o:depends({ _tcp_node_bool = "1" })
+-- Node → DNS Depends Settings
+o = s:option(DummyValue, "_node_sel_shunt", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ tcp_node = "__always__" })
+
+o = s:option(DummyValue, "_node_sel_other", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
+
---- DNS
o = s:option(ListValue, "dns_shunt", "DNS " .. translate("Shunt"))
o.default = "chinadns-ng"
@@ -333,6 +344,7 @@ end
if has_xray then
o:value("xray", "Xray")
end
+o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["tcp_node"]
local id_val = f and f:formvalue(section) or ""
@@ -362,7 +374,9 @@ o:value("tcp", "TCP")
o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
o:depends("dns_mode", "xray")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, ["tcp+doh"] = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "xray" then
@@ -377,7 +391,9 @@ o:value("tcp", "TCP")
o:value("doh", "DoH")
o:depends("dns_mode", "sing-box")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, doh = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "sing-box" then
@@ -444,6 +460,7 @@ o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet"))
o.datatype = "ipaddr"
o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"})
+o:depends({_node_sel_shunt = "1"})
o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS"))
o.default = "none"
@@ -485,6 +502,7 @@ for k, v in pairs(nodes_table) do
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["xray_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
end
if v.type == "sing-box" and has_singbox then
tcp:value(v.id, v["remark"])
@@ -493,17 +511,13 @@ for k, v in pairs(nodes_table) do
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["singbox_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
- end
- if has_xray or has_singbox then
- s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
end
else
tcp:value(v.id, v["remark"])
tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-
- s.fields["dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
end
end
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 30665ed6dd..de37538e08 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -309,6 +309,18 @@ o = s:taboption("Main", Flag, "tcp_node_socks_bind_local", translate("TCP Node")
o.default = "1"
o:depends({ tcp_node = "", ["!reverse"] = true })
+-- Node → DNS Depends Settings
+o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ tcp_node = "__always__" })
+
+o = s:taboption("Main", DummyValue, "_node_sel_other", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
+
+-- [[ DNS Settings ]]--
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shunt"))
@@ -388,8 +400,8 @@ end
if has_xray then
o:value("xray", "Xray")
end
-o:depends({ dns_shunt = "chinadns-ng", tcp_node = "" })
-o:depends({ dns_shunt = "dnsmasq", tcp_node = "" })
+o:depends({ dns_shunt = "chinadns-ng", _node_sel_other = "1" })
+o:depends({ dns_shunt = "dnsmasq", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["smartdns_dns_mode"]
if f and f:formvalue(section) then
@@ -408,7 +420,7 @@ if api.is_finded("smartdns") then
if has_xray then
o:value("xray", "Xray")
end
- o:depends({ dns_shunt = "smartdns", tcp_node = "" })
+ o:depends({ dns_shunt = "smartdns", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["dns_mode"]
if f and f:formvalue(section) then
@@ -468,7 +480,9 @@ o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
o:depends("dns_mode", "xray")
o:depends("smartdns_dns_mode", "xray")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, ["tcp+doh"] = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "xray" or s.fields["smartdns_dns_mode"]:formvalue(section) == "xray" then
@@ -484,7 +498,9 @@ o:value("doh", "DoH")
o:depends("dns_mode", "sing-box")
o:depends("smartdns_dns_mode", "sing-box")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, doh = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "sing-box" or s.fields["smartdns_dns_mode"]:formvalue(section) == "sing-box" then
@@ -548,6 +564,7 @@ o.datatype = "ipaddr"
o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"})
o:depends("dns_shunt", "smartdns")
+o:depends("_node_sel_shunt", "1")
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
o.default = "0"
@@ -557,6 +574,7 @@ o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"})
o:depends({dns_mode = "xray", dns_shunt = "dnsmasq"})
o:depends({dns_mode = "xray", dns_shunt = "chinadns-ng"})
o:depends({smartdns_dns_mode = "xray", dns_shunt = "smartdns"})
+o:depends("_node_sel_shunt", "1")
o.validate = function(self, value, t)
if value and value == "1" then
local _dns_mode = s.fields["dns_mode"]:formvalue(t)
@@ -810,22 +828,15 @@ for k, v in pairs(nodes_table) do
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", tcp_node = v.id })
- s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", tcp_node = v.id })
- s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id })
- s.fields["remote_fakedns"]:depends({ tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
+ s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", _node_sel_shunt = "1" })
+ s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", _node_sel_shunt = "1" })
end
else
tcp:value(v.id, v["remark"])
tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-
- s.fields["dns_mode"]:depends({ dns_shunt = "chinadns-ng", tcp_node = v.id })
- s.fields["dns_mode"]:depends({ dns_shunt = "dnsmasq", tcp_node = v.id })
- if api.is_finded("smartdns") then
- s.fields["smartdns_dns_mode"]:depends({ dns_shunt = "smartdns", tcp_node = v.id })
- end
end
if v.type == "Socks" then
if has_singbox or has_xray then
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua
index 7b7be7cee9..63dc72556e 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -201,8 +201,8 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "/",
- headers = (node.ws_host or node.ws_user_agent) and {
- Host = node.ws_host,
+ host = node.ws_host,
+ headers = node.ws_user_agent and {
["User-Agent"] = node.ws_user_agent
} or nil,
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil,
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
index 685ea00918..1bcf0ab744 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
+++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
@@ -139,7 +139,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
lv_openPanel(cbid,display,panel,listContainer,hiddenSelect,searchInput);
}
});
- lv_adaptiveStyle(cbid); // copy select styles
+ lv_registerAdaptive(cbid);
})();
//]]>
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
index 922db7279b..21538a2ee0 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
+++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
@@ -244,9 +244,11 @@ local appname = api.appname
return lv_rgbToHex(r, g, b);
}
+ // copy select styles
function lv_adaptiveStyle(cbid) {
const display = document.getElementById(cbid + ".display");
const hiddenSelect = document.getElementById(cbid);
+ const panel = document.getElementById(cbid + ".panel");
if (hiddenSelect && display) {
const elOption = hiddenSelect.getElementsByTagName("option")[0]
const styleSelect = window.getComputedStyle(hiddenSelect)
@@ -466,7 +468,13 @@ local appname = api.appname
}
panel.style.left = rect.left + "px";
panel.style.top = top + "px";
- panel.style.minWidth = rect.width + "px";
+ const panelRect = panel.getBoundingClientRect();
+ const displayWidth = rect.width;
+ const remainingWidth = window.innerWidth - panelRect.left - 12;
+ const maxWidth = Math.max(displayWidth, Math.floor(remainingWidth));
+ panel.style.maxWidth = maxWidth + "px";
+ panel.style.minWidth = displayWidth + "px";
+ panel.style.width = "auto";
panel.style.visibility = "";
}
@@ -601,22 +609,24 @@ local appname = api.appname
if(!li || li === listContainer) return;
const key = li.getAttribute('data-key') || "";
const text = li.querySelector(".lv-item-label")?.textContent || li.textContent || key;
- if (key !== hiddenSelect.value) {
- //动态改值
+
+ const changed = key !== hiddenSelect.value;
+ if (changed) {
+ //改值
hiddenSelect.options[0].value = key;
+ hiddenSelect.options[0].text = key;
hiddenSelect.value = key;
labelSpan.textContent = text;
labelSpan.title = text;
- setTimeout(() => {
- try {
- const evt = new Event('change', { bubbles: true });
- hiddenSelect.dispatchEvent(evt);
- } catch(e){}
- }, 0);
lv_highlightSelectedItem(listContainer, hiddenSelect);
lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput);
}
lv_closePanel(cbid,panel,listContainer,hiddenSelect,searchInput);
+ if (changed) {
+ setTimeout(() => {
+ hiddenSelect.dispatchEvent(new Event('change', { bubbles: true }));
+ }, 0);
+ }
});
// 搜索功能
@@ -635,27 +645,22 @@ local appname = api.appname
}
});
});
-
- // 设置宽度
- panel.style.maxWidth = lv_getPanelMaxWidth(display);
- panel.style.minWidth = display.getBoundingClientRect().width + "px";
- panel.style.width = "auto";
}
- function lv_getPanelMaxWidth(display) {
- if (!display) return 0;
- const rectW = el => el && el.getBoundingClientRect().width;
- const fallback = rectW(display) || 0;
- const cbiValue = display.closest(".cbi-value");
- if (cbiValue) {
- const valueW = rectW(cbiValue);
- const titleW = rectW(cbiValue.querySelector(".cbi-value-title"));
- if (valueW) {
- return Math.floor(titleW ? valueW - titleW : valueW);
- }
- }
- const fieldW = rectW(display.closest(".cbi-value-field"));
- return Math.floor(fieldW || fallback);
+ const lv_adaptiveControls = new Set();
+ function lv_registerAdaptive(cbid) {
+ lv_adaptiveControls.add(cbid);
+ lv_adaptiveStyle(cbid);
}
+ let lv_adaptiveTicking = false;
+ window.addEventListener("resize", () => {
+ if (!lv_adaptiveTicking) {
+ lv_adaptiveTicking = true;
+ requestAnimationFrame(() => {
+ lv_adaptiveControls.forEach(cbid => lv_adaptiveStyle(cbid));
+ lv_adaptiveTicking = false;
+ });
+ }
+ });
//]]>
diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock
index 28e85c9a3d..f97cf9cbdc 100644
--- a/shadowsocks-rust/Cargo.lock
+++ b/shadowsocks-rust/Cargo.lock
@@ -821,7 +821,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -928,7 +928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1470,7 +1470,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2 0.6.1",
+ "socket2 0.5.10",
"system-configuration",
"tokio",
"tower-service",
@@ -2127,7 +2127,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -2452,7 +2452,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls",
- "socket2 0.6.1",
+ "socket2 0.5.10",
"thiserror",
"tokio",
"tracing",
@@ -2489,9 +2489,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
- "socket2 0.6.1",
+ "socket2 0.5.10",
"tracing",
- "windows-sys 0.60.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -2604,9 +2604,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
-version = "0.12.25"
+version = "0.12.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a"
+checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f"
dependencies = [
"base64",
"bytes",
@@ -2758,7 +2758,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -3432,7 +3432,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -4060,7 +4060,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys 0.48.0",
]
[[package]]
diff --git a/sing-box/.github/CRONET_GO_VERSION b/sing-box/.github/CRONET_GO_VERSION
index 7616cee1c2..a7af089083 100644
--- a/sing-box/.github/CRONET_GO_VERSION
+++ b/sing-box/.github/CRONET_GO_VERSION
@@ -1 +1 @@
-fe7ab107d3a222ca878b9a727d76075938ee7cde
+3745171528eaf62dd8819df4c089b1259a32a1f2
diff --git a/sing-box/.github/workflows/build.yml b/sing-box/.github/workflows/build.yml
index 928b7ce9d4..d256558932 100644
--- a/sing-box/.github/workflows/build.yml
+++ b/sing-box/.github/workflows/build.yml
@@ -69,11 +69,11 @@ jobs:
strategy:
matrix:
include:
- - { os: linux, arch: amd64, variant: purego, openwrt: "x86_64" }
+ - { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" }
- { os: linux, arch: amd64, variant: glibc, naive: true }
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- - { os: linux, arch: arm64, variant: purego, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
+ - { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm64, variant: glibc, naive: true }
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
@@ -190,7 +190,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
- TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
+ TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound"
fi
@@ -427,7 +427,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
- TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
+ TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then
TAGS="${TAGS},with_naive_outbound"
fi
@@ -495,7 +495,7 @@ jobs:
- name: Build
run: |
mkdir -p dist
- go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" `
+ go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" `
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
./cmd/sing-box
env:
@@ -885,6 +885,16 @@ jobs:
with:
path: dist
merge-multiple: true
+ - name: Generate SFA version metadata
+ run: |-
+ VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)
+ cat > dist/SFA-version-metadata.json << EOF
+ {
+ "version_code": ${VERSION_CODE},
+ "version_name": "${VERSION}"
+ }
+ EOF
+ cat dist/SFA-version-metadata.json
- name: Upload builds
if: ${{ env.PUBLISHED == 'false' }}
run: |-
diff --git a/sing-box/.github/workflows/docker.yml b/sing-box/.github/workflows/docker.yml
index e4041b5b00..5447457e26 100644
--- a/sing-box/.github/workflows/docker.yml
+++ b/sing-box/.github/workflows/docker.yml
@@ -93,7 +93,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
- TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
+ TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
diff --git a/sing-box/.github/workflows/linux.yml b/sing-box/.github/workflows/linux.yml
index eb1f32805c..1f56467c76 100644
--- a/sing-box/.github/workflows/linux.yml
+++ b/sing-box/.github/workflows/linux.yml
@@ -116,7 +116,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
- TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
+ TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
diff --git a/sing-box/Dockerfile b/sing-box/Dockerfile
index 5162d46131..fb39e8b603 100644
--- a/sing-box/Dockerfile
+++ b/sing-box/Dockerfile
@@ -13,7 +13,7 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
- "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \
+ "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
diff --git a/sing-box/Makefile b/sing-box/Makefile
index fcf5b332f4..9a364af1a0 100644
--- a/sing-box/Makefile
+++ b/sing-box/Makefile
@@ -1,6 +1,6 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
-TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0
+TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
diff --git a/sing-box/constant/proxy.go b/sing-box/constant/proxy.go
index a54a3a75d8..a519362345 100644
--- a/sing-box/constant/proxy.go
+++ b/sing-box/constant/proxy.go
@@ -29,6 +29,7 @@ const (
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
TypeCCM = "ccm"
+ TypeOCM = "ocm"
)
const (
diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md
index d67cbed7e2..c587f2aadb 100644
--- a/sing-box/docs/changelog.md
+++ b/sing-box/docs/changelog.md
@@ -2,6 +2,15 @@
icon: material/alert-decagram
---
+#### 1.13.0-alpha.30
+
+* Add OpenAI Codex Multiplexer service **1**
+* Fixes and improvements
+
+**1**:
+
+See [OCM](/configuration/service/ocm).
+
#### 1.13.0-alpha.29
* Add UDP over TCP support for naiveproxy outbound **1**
diff --git a/sing-box/docs/configuration/service/index.md b/sing-box/docs/configuration/service/index.md
index 2bd1a4a3f8..de3583b2ba 100644
--- a/sing-box/docs/configuration/service/index.md
+++ b/sing-box/docs/configuration/service/index.md
@@ -25,6 +25,7 @@ icon: material/new-box
|------------|------------------------|
| `ccm` | [CCM](./ccm) |
| `derp` | [DERP](./derp) |
+| `ocm` | [OCM](./ocm) |
| `resolved` | [Resolved](./resolved) |
| `ssm-api` | [SSM API](./ssm-api) |
diff --git a/sing-box/docs/configuration/service/index.zh.md b/sing-box/docs/configuration/service/index.zh.md
index b4a73eda92..a0d18cbba7 100644
--- a/sing-box/docs/configuration/service/index.zh.md
+++ b/sing-box/docs/configuration/service/index.zh.md
@@ -25,6 +25,7 @@ icon: material/new-box
|-----------|------------------------|
| `ccm` | [CCM](./ccm) |
| `derp` | [DERP](./derp) |
+| `ocm` | [OCM](./ocm) |
| `resolved`| [Resolved](./resolved) |
| `ssm-api` | [SSM API](./ssm-api) |
diff --git a/sing-box/docs/configuration/service/ocm.md b/sing-box/docs/configuration/service/ocm.md
new file mode 100644
index 0000000000..59dba7daa8
--- /dev/null
+++ b/sing-box/docs/configuration/service/ocm.md
@@ -0,0 +1,171 @@
+---
+icon: material/new-box
+---
+
+!!! question "Since sing-box 1.13.0"
+
+# OCM
+
+OCM (OpenAI Codex Multiplexer) service is a multiplexing service that allows you to access your local OpenAI Codex subscription remotely through custom tokens.
+
+It handles OAuth authentication with OpenAI's API on your local machine while allowing remote clients to authenticate using custom tokens.
+
+### Structure
+
+```json
+{
+ "type": "ocm",
+
+ ... // Listen Fields
+
+ "credential_path": "",
+ "usages_path": "",
+ "users": [],
+ "headers": {},
+ "detour": "",
+ "tls": {}
+}
+```
+
+### Listen Fields
+
+See [Listen Fields](/configuration/shared/listen/) for details.
+
+### Fields
+
+#### credential_path
+
+Path to the OpenAI OAuth credentials file.
+
+If not specified, defaults to `~/.codex/auth.json`.
+
+Refreshed tokens are automatically written back to the same location.
+
+#### usages_path
+
+Path to the file for storing aggregated API usage statistics.
+
+Usage tracking is disabled if not specified.
+
+When enabled, the service tracks and saves comprehensive statistics including:
+- Request counts
+- Token usage (input, output, cached)
+- Calculated costs in USD based on OpenAI API pricing
+
+Statistics are organized by model and optionally by user when authentication is enabled.
+
+The statistics file is automatically saved every minute and upon service shutdown.
+
+#### users
+
+List of authorized users for token authentication.
+
+If empty, no authentication is required.
+
+Object format:
+
+```json
+{
+ "name": "",
+ "token": ""
+}
+```
+
+Object fields:
+
+- `name`: Username identifier for tracking purposes.
+- `token`: Bearer token for authentication. Clients authenticate by setting the `Authorization: Bearer
` header.
+
+#### headers
+
+Custom HTTP headers to send to the OpenAI API.
+
+These headers will override any existing headers with the same name.
+
+#### detour
+
+Outbound tag for connecting to the OpenAI API.
+
+#### tls
+
+TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
+
+### Example
+
+#### Server
+
+```json
+{
+ "services": [
+ {
+ "type": "ocm",
+ "listen": "127.0.0.1",
+ "listen_port": 8080
+ }
+ ]
+}
+```
+
+#### Client
+
+Add to `~/.codex/config.toml`:
+
+```toml
+[model_providers.ocm]
+name = "OCM Proxy"
+base_url = "http://127.0.0.1:8080/v1"
+wire_api = "responses"
+requires_openai_auth = false
+```
+
+Then run:
+
+```bash
+codex --model-provider ocm
+```
+
+### Example with Authentication
+
+#### Server
+
+```json
+{
+ "services": [
+ {
+ "type": "ocm",
+ "listen": "0.0.0.0",
+ "listen_port": 8080,
+ "usages_path": "./codex-usages.json",
+ "users": [
+ {
+ "name": "alice",
+ "token": "sk-alice-secret-token"
+ },
+ {
+ "name": "bob",
+ "token": "sk-bob-secret-token"
+ }
+ ]
+ }
+ ]
+}
+```
+
+#### Client
+
+Add to `~/.codex/config.toml`:
+
+```toml
+[model_providers.ocm]
+name = "OCM Proxy"
+base_url = "http://127.0.0.1:8080/v1"
+wire_api = "responses"
+requires_openai_auth = false
+experimental_bearer_token = "sk-alice-secret-token"
+```
+
+Then run:
+
+```bash
+codex --model-provider ocm
+```
diff --git a/sing-box/docs/configuration/service/ocm.zh.md b/sing-box/docs/configuration/service/ocm.zh.md
new file mode 100644
index 0000000000..ee1d851013
--- /dev/null
+++ b/sing-box/docs/configuration/service/ocm.zh.md
@@ -0,0 +1,171 @@
+---
+icon: material/new-box
+---
+
+!!! question "自 sing-box 1.13.0 起"
+
+# OCM
+
+OCM(OpenAI Codex 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 OpenAI Codex 订阅。
+
+它在本地机器上处理与 OpenAI API 的 OAuth 身份验证,同时允许远程客户端使用自定义令牌进行身份验证。
+
+### 结构
+
+```json
+{
+ "type": "ocm",
+
+ ... // 监听字段
+
+ "credential_path": "",
+ "usages_path": "",
+ "users": [],
+ "headers": {},
+ "detour": "",
+ "tls": {}
+}
+```
+
+### 监听字段
+
+参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。
+
+### 字段
+
+#### credential_path
+
+OpenAI OAuth 凭据文件的路径。
+
+如果未指定,默认值为 `~/.codex/auth.json`。
+
+刷新的令牌会自动写回相同位置。
+
+#### usages_path
+
+用于存储聚合 API 使用统计信息的文件路径。
+
+如果未指定,使用跟踪将被禁用。
+
+启用后,服务会跟踪并保存全面的统计信息,包括:
+- 请求计数
+- 令牌使用量(输入、输出、缓存)
+- 基于 OpenAI API 定价计算的美元成本
+
+统计信息按模型以及可选的用户(启用身份验证时)进行组织。
+
+统计文件每分钟自动保存一次,并在服务关闭时保存。
+
+#### users
+
+用于令牌身份验证的授权用户列表。
+
+如果为空,则不需要身份验证。
+
+对象格式:
+
+```json
+{
+ "name": "",
+ "token": ""
+}
+```
+
+对象字段:
+
+- `name`:用于跟踪的用户名标识符。
+- `token`:用于身份验证的 Bearer 令牌。客户端通过设置 `Authorization: Bearer ` 头进行身份验证。
+
+#### headers
+
+发送到 OpenAI API 的自定义 HTTP 头。
+
+这些头会覆盖同名的现有头。
+
+#### detour
+
+用于连接 OpenAI API 的出站标签。
+
+#### tls
+
+TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
+
+### 示例
+
+#### 服务端
+
+```json
+{
+ "services": [
+ {
+ "type": "ocm",
+ "listen": "127.0.0.1",
+ "listen_port": 8080
+ }
+ ]
+}
+```
+
+#### 客户端
+
+在 `~/.codex/config.toml` 中添加:
+
+```toml
+[model_providers.ocm]
+name = "OCM Proxy"
+base_url = "http://127.0.0.1:8080/v1"
+wire_api = "responses"
+requires_openai_auth = false
+```
+
+然后运行:
+
+```bash
+codex --model-provider ocm
+```
+
+### 带身份验证的示例
+
+#### 服务端
+
+```json
+{
+ "services": [
+ {
+ "type": "ocm",
+ "listen": "0.0.0.0",
+ "listen_port": 8080,
+ "usages_path": "./codex-usages.json",
+ "users": [
+ {
+ "name": "alice",
+ "token": "sk-alice-secret-token"
+ },
+ {
+ "name": "bob",
+ "token": "sk-bob-secret-token"
+ }
+ ]
+ }
+ ]
+}
+```
+
+#### 客户端
+
+在 `~/.codex/config.toml` 中添加:
+
+```toml
+[model_providers.ocm]
+name = "OCM Proxy"
+base_url = "http://127.0.0.1:8080/v1"
+wire_api = "responses"
+requires_openai_auth = false
+experimental_bearer_token = "sk-alice-secret-token"
+```
+
+然后运行:
+
+```bash
+codex --model-provider ocm
+```
diff --git a/sing-box/go.mod b/sing-box/go.mod
index 5bd2a6527f..88fa0348a3 100644
--- a/sing-box/go.mod
+++ b/sing-box/go.mod
@@ -21,12 +21,13 @@ require (
github.com/metacubex/utls v1.8.3
github.com/mholt/acmez/v3 v3.1.2
github.com/miekg/dns v1.1.67
+ github.com/openai/openai-go/v3 v3.13.0
github.com/oschwald/maxminddb-golang v1.13.1
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cors v1.2.1
- github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7
- github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7
+ github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0
+ github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.10
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
@@ -107,29 +108,29 @@ require (
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
- github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect
- github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect
+ github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
+ github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/spf13/pflag v1.0.6 // indirect
diff --git a/sing-box/go.sum b/sing-box/go.sum
index 97dc5459c2..99465d060c 100644
--- a/sing-box/go.sum
+++ b/sing-box/go.sum
@@ -132,6 +132,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q=
+github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
@@ -152,56 +154,56 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
-github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 h1:jb/nr5YECJ56gcAphQ7tBWierrBbaLT7v1MI9n3e/Gw=
-github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo=
-github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 h1:6PjoWjKnYrz/HmEezV1Z5K39EC8l+sek1V14aXlslyc=
-github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:SrXj1iQMVqZcy8XINBJOhlBncfCe7DimX6mTRY+rdDw=
-github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b h1:+Dk1yBvaKl49l8j3YFoEvraAdt7VMy7n2Qzrs40/ekI=
-github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
-github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b h1:tjkKLyRhD1ePdl48SjW38o7yjW1fCJ2x2nyvq5e/8oE=
-github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
-github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b h1:P++HSm1JhmkKbDskFNfQuR8aCTg5uEWe2/5qFfj+6YU=
-github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
-github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b h1:Rbo1r5Mk8yWlZTC8gcyuQFv2BXUI1/wWMC9Vc+cJNQ8=
-github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
-github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b h1:VA1M5Yw09HiBD+Zemq6mOBVwBd4pr47LMN9WKOVf62Q=
-github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
-github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AdWIsXfKxH3/hGjiYqcUSc0fb+R4vONjfRaO0emwdNA=
-github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
-github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:sg8SupaVsj0Krc4DKSC1n2quig08bRtmsF0/iwwXeAI=
-github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
-github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b h1:0dmsm/vEAYxQjtH4sS/A8X6bf6YqS0I0Vc6oDZdnlRc=
-github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
-github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:jnT/bYjzvdfGVgPEgZX0Mi0qkm8qcU/DluV+TqShVPg=
-github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
-github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b h1:/NqFcrdXS3e3Ad+ILfrwXFw3urwwFsQ1XxrDW9PkU4E=
-github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
-github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b h1:vqeLRyeHq++RCcuUriJflTQne7hldEVJ19Or0xwCIrs=
-github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
-github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b h1:Xr7dFoKy0o2YdPl2JcU7GtM4NxQyS8vGovd6Aw4pX8I=
-github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
-github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b h1:GEt+x1qXt8xicDSD4GXOHs0WrVec5HAo+HmBAXzkidg=
-github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
-github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b h1:MbjH6TmLoXlAkBWoUzuNF2w0FPfOMY6Rj9T226fe858=
-github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
-github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AP85VNYiACL8QQeXqCUB8hz5hFOUtgwReLELRhve/4c=
-github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
-github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b h1:4uNGGiOrJsa2S+PteucoO/Qyzz7FWHNJw2ezOkS7QiM=
-github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
-github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b h1:N5yoxOlynwvTgaJnEOsL3iuI6FFmDJy1toyNSU+vlLA=
-github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
-github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:JKyBNyt/DWAutvuDFjFTi0dMe0bh5zG7UUpZHH8Uqzo=
-github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
-github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b h1:m0sCMM6ry0+eXBuTPLGY9JYOVcIvtHcDEcstMo+oSTU=
-github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
-github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:UURnlFD48/9wn7cdi1NqYQuTvJZEFuQShxX8pvk2Fsg=
-github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
-github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b h1:jhwpI5IXK5RPvbk9+xUV9GAw2QeRZvcZprp4bJOP9e0=
-github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk=
-github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b h1:qoleSwhzgH6jDSwqktbJCPDex4yMWtijcouGR8+BL+s=
-github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
-github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b h1:v7eakED1u8ZTKjmqxa+Eu0S5ewK+r+mfEf9KI6ymu+I=
-github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
+github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0 h1:LESmw76c/bzprJSDdAPpXTDLbZnir2w3TEX08uPD+Ls=
+github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo=
+github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0 h1:q2Y9oZBQyCyFV7hMdZXtpPabJWq1WiWL/eyKRbj2z28=
+github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0/go.mod h1:ozG0O0AvB4rKe9A7twrqp4UvnExSzmbEhsdcmm14PwM=
+github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f h1:eXI9DXjk4WtcukZumm2EEihwvNswjcoe3oT9LrriOxw=
+github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
+github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:AmjLqs5BiC8YPi900EB5LdEZK5kWmHg0MMmQbpT9WsM=
+github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
+github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f h1:ts10PNXwK+2HP15ushAYJVMtoUV7V9UAOr+8AMr00uw=
+github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
+github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:dUd/75r4DC/P0374iXDyx7vPlCt7j13vuuCsoPhvJvg=
+github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
+github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:Wfjf525uzg8TRV752sn6N7Ycw+Ee0Eorytd2cT7aVh0=
+github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
+github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:y8KaKG9TgX5KPcUxzMu/hiPChoUNL8U17f92YWrV4zo=
+github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
+github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:mxy23kdWCNe0zLPC8+jw0kmvPNeC8WPRGA5O9BWvC0U=
+github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
+github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:TNPhjBjjMBJTRIoK+0gqIjDks/5bJm47sGZ3bw8gCFs=
+github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
+github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:WBSrJO0CiqWKS7sMstL0zJJVvt2L7TrmBmbj8vPz+gY=
+github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
+github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f h1:UOJuw1PUXmLGlVTdvr7U93/olD6CeDgFpkeKg7OdzbI=
+github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
+github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f h1:wfENhS38qymYlW0Ef9Y45tvPRAEyj5WFcQDVebg0Uqc=
+github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
+github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:JMdNN9P8Pt65PKwUOrK6/ejXrosOPyAP7QdlA2X71E0=
+github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
+github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f h1:6lyBrJKu+nQSiNdP1jWLKbhEnJ5pJ3Amcr96qAk/Wxk=
+github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
+github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f h1:9/7uFHDpI5ZGzLAVSro0dBGOVBxboIFsxtI2QIrf3CI=
+github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
+github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:3vgjnWxtlP2qqCP9A3kA4IRRr3AjDzF0C8NiB0qRpnY=
+github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
+github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f h1:HXNSrz0zn5LgbdfLoIJb45YQY789vs8EfSjZwX6P46Y=
+github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
+github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f h1:mEys4XlVs9Ewnj08PPkuMD+7Cd9+kWIe2UAvhBDc4t4=
+github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
+github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:Jsqlc8k52ZsOF6nd/Pq7jh9MhZUEh6wxbabL8Y5CilU=
+github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
+github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:wP4nDfGDca5JvqTj1mpqu3CMfMahsmIY1vPyALTtqXI=
+github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
+github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:psbgr8u0fuUwH95k8+Z4HfMcyOevAXARGRLdfexsldo=
+github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
+github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f h1:xzfMiwWj9cyJWDr0WWC6xkZ14RWo/XqEQrJlJm6MZag=
+github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk=
+github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:AxA2aJ5IcEUkAgsEDeu6ICHd01F+Kn61bXKfZB4uKTk=
+github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
+github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:M21bGiwCgCY2rl0huKXo36eV0D4ep3ZgRPh6BKgvGh0=
+github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c=
diff --git a/sing-box/include/ocm.go b/sing-box/include/ocm.go
new file mode 100644
index 0000000000..cdea9eeaea
--- /dev/null
+++ b/sing-box/include/ocm.go
@@ -0,0 +1,12 @@
+//go:build with_ocm
+
+package include
+
+import (
+ "github.com/sagernet/sing-box/adapter/service"
+ "github.com/sagernet/sing-box/service/ocm"
+)
+
+func registerOCMService(registry *service.Registry) {
+ ocm.RegisterService(registry)
+}
diff --git a/sing-box/include/ocm_stub.go b/sing-box/include/ocm_stub.go
new file mode 100644
index 0000000000..d5a94fcba9
--- /dev/null
+++ b/sing-box/include/ocm_stub.go
@@ -0,0 +1,20 @@
+//go:build !with_ocm
+
+package include
+
+import (
+ "context"
+
+ "github.com/sagernet/sing-box/adapter"
+ "github.com/sagernet/sing-box/adapter/service"
+ C "github.com/sagernet/sing-box/constant"
+ "github.com/sagernet/sing-box/log"
+ "github.com/sagernet/sing-box/option"
+ E "github.com/sagernet/sing/common/exceptions"
+)
+
+func registerOCMService(registry *service.Registry) {
+ service.Register[option.OCMServiceOptions](registry, C.TypeOCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) {
+ return nil, E.New(`OCM is not included in this build, rebuild with -tags with_ocm`)
+ })
+}
diff --git a/sing-box/include/registry.go b/sing-box/include/registry.go
index 8f08189d40..d909b8500b 100644
--- a/sing-box/include/registry.go
+++ b/sing-box/include/registry.go
@@ -136,6 +136,7 @@ func ServiceRegistry() *service.Registry {
registerDERPService(registry)
registerCCMService(registry)
+ registerOCMService(registry)
return registry
}
diff --git a/sing-box/mkdocs.yml b/sing-box/mkdocs.yml
index c49bfa2a67..a505f3e4d1 100644
--- a/sing-box/mkdocs.yml
+++ b/sing-box/mkdocs.yml
@@ -176,6 +176,8 @@ nav:
- DERP: configuration/service/derp.md
- Resolved: configuration/service/resolved.md
- SSM API: configuration/service/ssm-api.md
+ - CCM: configuration/service/ccm.md
+ - OCM: configuration/service/ocm.md
markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets
diff --git a/sing-box/option/ocm.go b/sing-box/option/ocm.go
new file mode 100644
index 0000000000..c13a1c1f53
--- /dev/null
+++ b/sing-box/option/ocm.go
@@ -0,0 +1,20 @@
+package option
+
+import (
+ "github.com/sagernet/sing/common/json/badoption"
+)
+
+type OCMServiceOptions struct {
+ ListenOptions
+ InboundTLSOptionsContainer
+ CredentialPath string `json:"credential_path,omitempty"`
+ Users []OCMUser `json:"users,omitempty"`
+ Headers badoption.HTTPHeader `json:"headers,omitempty"`
+ Detour string `json:"detour,omitempty"`
+ UsagesPath string `json:"usages_path,omitempty"`
+}
+
+type OCMUser struct {
+ Name string `json:"name,omitempty"`
+ Token string `json:"token,omitempty"`
+}
diff --git a/sing-box/release/local/common.sh b/sing-box/release/local/common.sh
index d24bba4759..68a494babd 100755
--- a/sing-box/release/local/common.sh
+++ b/sing-box/release/local/common.sh
@@ -11,7 +11,7 @@ INSTALL_CONFIG_PATH="/usr/local/etc/sing-box"
INSTALL_DATA_PATH="/var/lib/sing-box"
SYSTEMD_SERVICE_PATH="/etc/systemd/system"
-DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0"
+DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0"
setup_environment() {
if [ -d /usr/local/go ]; then
diff --git a/sing-box/service/ccm/service_usage.go b/sing-box/service/ccm/service_usage.go
index 53ae46587a..7d39e3ce56 100644
--- a/sing-box/service/ccm/service_usage.go
+++ b/sing-box/service/ccm/service_usage.go
@@ -124,12 +124,69 @@ var (
CacheWritePrice: 3.75,
}
+ opus45Pricing = ModelPricing{
+ InputPrice: 5.0,
+ OutputPrice: 25.0,
+ CacheReadPrice: 0.5,
+ CacheWritePrice: 6.25,
+ }
+
+ sonnet45StandardPricing = ModelPricing{
+ InputPrice: 3.0,
+ OutputPrice: 15.0,
+ CacheReadPrice: 0.3,
+ CacheWritePrice: 3.75,
+ }
+
+ sonnet45PremiumPricing = ModelPricing{
+ InputPrice: 6.0,
+ OutputPrice: 22.5,
+ CacheReadPrice: 0.6,
+ CacheWritePrice: 7.5,
+ }
+
+ haiku45Pricing = ModelPricing{
+ InputPrice: 1.0,
+ OutputPrice: 5.0,
+ CacheReadPrice: 0.1,
+ CacheWritePrice: 1.25,
+ }
+
+ haiku3Pricing = ModelPricing{
+ InputPrice: 0.25,
+ OutputPrice: 1.25,
+ CacheReadPrice: 0.03,
+ CacheWritePrice: 0.3,
+ }
+
+ opus3Pricing = ModelPricing{
+ InputPrice: 15.0,
+ OutputPrice: 75.0,
+ CacheReadPrice: 1.5,
+ CacheWritePrice: 18.75,
+ }
+
modelFamilies = []modelFamily{
+ {
+ pattern: regexp.MustCompile(`^claude-opus-4-5-`),
+ standardPricing: opus45Pricing,
+ premiumPricing: nil,
+ },
{
pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`),
standardPricing: opus4Pricing,
premiumPricing: nil,
},
+ {
+ pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`),
+ standardPricing: opus3Pricing,
+ premiumPricing: nil,
+ },
+ {
+ pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`),
+ standardPricing: sonnet45StandardPricing,
+ premiumPricing: &sonnet45PremiumPricing,
+ },
{
pattern: regexp.MustCompile(`^claude-3-7-sonnet-`),
standardPricing: sonnet4StandardPricing,
@@ -140,6 +197,16 @@ var (
standardPricing: sonnet4StandardPricing,
premiumPricing: &sonnet4PremiumPricing,
},
+ {
+ pattern: regexp.MustCompile(`^claude-3-5-sonnet-`),
+ standardPricing: sonnet35Pricing,
+ premiumPricing: nil,
+ },
+ {
+ pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`),
+ standardPricing: haiku45Pricing,
+ premiumPricing: nil,
+ },
{
pattern: regexp.MustCompile(`^claude-haiku-4-`),
standardPricing: haiku4Pricing,
@@ -151,8 +218,8 @@ var (
premiumPricing: nil,
},
{
- pattern: regexp.MustCompile(`^claude-3-5-sonnet-`),
- standardPricing: sonnet35Pricing,
+ pattern: regexp.MustCompile(`^claude-3-haiku-`),
+ standardPricing: haiku3Pricing,
premiumPricing: nil,
},
}
diff --git a/sing-box/service/ocm/credential.go b/sing-box/service/ocm/credential.go
new file mode 100644
index 0000000000..76651a8e14
--- /dev/null
+++ b/sing-box/service/ocm/credential.go
@@ -0,0 +1,173 @@
+package ocm
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "net/http"
+ "os"
+ "os/user"
+ "path/filepath"
+ "time"
+
+ E "github.com/sagernet/sing/common/exceptions"
+)
+
+const (
+ oauth2ClientID = "app_EMoamEEZ73f0CkXaXp7hrann"
+ oauth2TokenURL = "https://auth.openai.com/oauth/token"
+ openaiAPIBaseURL = "https://api.openai.com"
+ chatGPTBackendURL = "https://chatgpt.com/backend-api/codex"
+ tokenRefreshIntervalDays = 8
+)
+
+func getRealUser() (*user.User, error) {
+ if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" {
+ sudoUserInfo, err := user.Lookup(sudoUser)
+ if err == nil {
+ return sudoUserInfo, nil
+ }
+ }
+ return user.Current()
+}
+
+func getDefaultCredentialsPath() (string, error) {
+ if codexHome := os.Getenv("CODEX_HOME"); codexHome != "" {
+ return filepath.Join(codexHome, "auth.json"), nil
+ }
+ userInfo, err := getRealUser()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(userInfo.HomeDir, ".codex", "auth.json"), nil
+}
+
+func readCredentialsFromFile(path string) (*oauthCredentials, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ var credentials oauthCredentials
+ err = json.Unmarshal(data, &credentials)
+ if err != nil {
+ return nil, err
+ }
+ return &credentials, nil
+}
+
+func writeCredentialsToFile(credentials *oauthCredentials, path string) error {
+ data, err := json.MarshalIndent(credentials, "", " ")
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(path, data, 0o600)
+}
+
+type oauthCredentials struct {
+ APIKey string `json:"OPENAI_API_KEY,omitempty"`
+ Tokens *tokenData `json:"tokens,omitempty"`
+ LastRefresh *time.Time `json:"last_refresh,omitempty"`
+}
+
+type tokenData struct {
+ IDToken string `json:"id_token,omitempty"`
+ AccessToken string `json:"access_token"`
+ RefreshToken string `json:"refresh_token"`
+ AccountID string `json:"account_id,omitempty"`
+}
+
+func (c *oauthCredentials) isAPIKeyMode() bool {
+ return c.APIKey != ""
+}
+
+func (c *oauthCredentials) getAccessToken() string {
+ if c.APIKey != "" {
+ return c.APIKey
+ }
+ if c.Tokens != nil {
+ return c.Tokens.AccessToken
+ }
+ return ""
+}
+
+func (c *oauthCredentials) getAccountID() string {
+ if c.Tokens != nil {
+ return c.Tokens.AccountID
+ }
+ return ""
+}
+
+func (c *oauthCredentials) needsRefresh() bool {
+ if c.APIKey != "" {
+ return false
+ }
+ if c.Tokens == nil || c.Tokens.RefreshToken == "" {
+ return false
+ }
+ if c.LastRefresh == nil {
+ return true
+ }
+ return time.Since(*c.LastRefresh) >= time.Duration(tokenRefreshIntervalDays)*24*time.Hour
+}
+
+func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) {
+ if credentials.Tokens == nil || credentials.Tokens.RefreshToken == "" {
+ return nil, E.New("refresh token is empty")
+ }
+
+ requestBody, err := json.Marshal(map[string]string{
+ "grant_type": "refresh_token",
+ "refresh_token": credentials.Tokens.RefreshToken,
+ "client_id": oauth2ClientID,
+ "scope": "openid profile email",
+ })
+ if err != nil {
+ return nil, E.Cause(err, "marshal request")
+ }
+
+ request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody))
+ if err != nil {
+ return nil, err
+ }
+ request.Header.Set("Content-Type", "application/json")
+ request.Header.Set("Accept", "application/json")
+
+ response, err := httpClient.Do(request)
+ if err != nil {
+ return nil, err
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(response.Body)
+ return nil, E.New("refresh failed: ", response.Status, " ", string(body))
+ }
+
+ var tokenResponse struct {
+ IDToken string `json:"id_token"`
+ AccessToken string `json:"access_token"`
+ RefreshToken string `json:"refresh_token"`
+ }
+ err = json.NewDecoder(response.Body).Decode(&tokenResponse)
+ if err != nil {
+ return nil, E.Cause(err, "decode response")
+ }
+
+ newCredentials := *credentials
+ if newCredentials.Tokens == nil {
+ newCredentials.Tokens = &tokenData{}
+ }
+ if tokenResponse.IDToken != "" {
+ newCredentials.Tokens.IDToken = tokenResponse.IDToken
+ }
+ if tokenResponse.AccessToken != "" {
+ newCredentials.Tokens.AccessToken = tokenResponse.AccessToken
+ }
+ if tokenResponse.RefreshToken != "" {
+ newCredentials.Tokens.RefreshToken = tokenResponse.RefreshToken
+ }
+ now := time.Now()
+ newCredentials.LastRefresh = &now
+
+ return &newCredentials, nil
+}
diff --git a/sing-box/service/ocm/credential_darwin.go b/sing-box/service/ocm/credential_darwin.go
new file mode 100644
index 0000000000..f3da2a63ed
--- /dev/null
+++ b/sing-box/service/ocm/credential_darwin.go
@@ -0,0 +1,25 @@
+//go:build darwin
+
+package ocm
+
+func platformReadCredentials(customPath string) (*oauthCredentials, error) {
+ if customPath == "" {
+ var err error
+ customPath, err = getDefaultCredentialsPath()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return readCredentialsFromFile(customPath)
+}
+
+func platformWriteCredentials(credentials *oauthCredentials, customPath string) error {
+ if customPath == "" {
+ var err error
+ customPath, err = getDefaultCredentialsPath()
+ if err != nil {
+ return err
+ }
+ }
+ return writeCredentialsToFile(credentials, customPath)
+}
diff --git a/sing-box/service/ocm/credential_other.go b/sing-box/service/ocm/credential_other.go
new file mode 100644
index 0000000000..22dfd0337a
--- /dev/null
+++ b/sing-box/service/ocm/credential_other.go
@@ -0,0 +1,25 @@
+//go:build !darwin
+
+package ocm
+
+func platformReadCredentials(customPath string) (*oauthCredentials, error) {
+ if customPath == "" {
+ var err error
+ customPath, err = getDefaultCredentialsPath()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return readCredentialsFromFile(customPath)
+}
+
+func platformWriteCredentials(credentials *oauthCredentials, customPath string) error {
+ if customPath == "" {
+ var err error
+ customPath, err = getDefaultCredentialsPath()
+ if err != nil {
+ return err
+ }
+ }
+ return writeCredentialsToFile(credentials, customPath)
+}
diff --git a/sing-box/service/ocm/service.go b/sing-box/service/ocm/service.go
new file mode 100644
index 0000000000..e8f9541054
--- /dev/null
+++ b/sing-box/service/ocm/service.go
@@ -0,0 +1,555 @@
+package ocm
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "io"
+ "mime"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/sagernet/sing-box/adapter"
+ boxService "github.com/sagernet/sing-box/adapter/service"
+ "github.com/sagernet/sing-box/common/dialer"
+ "github.com/sagernet/sing-box/common/listener"
+ "github.com/sagernet/sing-box/common/tls"
+ C "github.com/sagernet/sing-box/constant"
+ "github.com/sagernet/sing-box/log"
+ "github.com/sagernet/sing-box/option"
+ "github.com/sagernet/sing/common"
+ "github.com/sagernet/sing/common/buf"
+ E "github.com/sagernet/sing/common/exceptions"
+ M "github.com/sagernet/sing/common/metadata"
+ N "github.com/sagernet/sing/common/network"
+ aTLS "github.com/sagernet/sing/common/tls"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/openai/openai-go/v3"
+ "github.com/openai/openai-go/v3/responses"
+ "golang.org/x/net/http2"
+)
+
+func RegisterService(registry *boxService.Registry) {
+ boxService.Register[option.OCMServiceOptions](registry, C.TypeOCM, NewService)
+}
+
+type errorResponse struct {
+ Error errorDetails `json:"error"`
+}
+
+type errorDetails struct {
+ Type string `json:"type"`
+ Code string `json:"code,omitempty"`
+ Message string `json:"message"`
+}
+
+func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(statusCode)
+
+ json.NewEncoder(w).Encode(errorResponse{
+ Error: errorDetails{
+ Type: errorType,
+ Message: message,
+ },
+ })
+}
+
+func isHopByHopHeader(header string) bool {
+ switch strings.ToLower(header) {
+ case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host":
+ return true
+ default:
+ return false
+ }
+}
+
+type Service struct {
+ boxService.Adapter
+ ctx context.Context
+ logger log.ContextLogger
+ credentialPath string
+ credentials *oauthCredentials
+ users []option.OCMUser
+ httpClient *http.Client
+ httpHeaders http.Header
+ listener *listener.Listener
+ tlsConfig tls.ServerConfig
+ httpServer *http.Server
+ userManager *UserManager
+ accessMutex sync.RWMutex
+ usageTracker *AggregatedUsage
+ trackingGroup sync.WaitGroup
+ shuttingDown bool
+}
+
+func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) {
+ serviceDialer, err := dialer.NewWithOptions(dialer.Options{
+ Context: ctx,
+ Options: option.DialerOptions{
+ Detour: options.Detour,
+ },
+ RemoteIsDomain: true,
+ })
+ if err != nil {
+ return nil, E.Cause(err, "create dialer")
+ }
+
+ httpClient := &http.Client{
+ Transport: &http.Transport{
+ ForceAttemptHTTP2: true,
+ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+ return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
+ },
+ },
+ }
+
+ userManager := &UserManager{
+ tokenMap: make(map[string]string),
+ }
+
+ var usageTracker *AggregatedUsage
+ if options.UsagesPath != "" {
+ usageTracker = &AggregatedUsage{
+ LastUpdated: time.Now(),
+ Combinations: make([]CostCombination, 0),
+ filePath: options.UsagesPath,
+ logger: logger,
+ }
+ }
+
+ service := &Service{
+ Adapter: boxService.NewAdapter(C.TypeOCM, tag),
+ ctx: ctx,
+ logger: logger,
+ credentialPath: options.CredentialPath,
+ users: options.Users,
+ httpClient: httpClient,
+ httpHeaders: options.Headers.Build(),
+ listener: listener.New(listener.Options{
+ Context: ctx,
+ Logger: logger,
+ Network: []string{N.NetworkTCP},
+ Listen: options.ListenOptions,
+ }),
+ userManager: userManager,
+ usageTracker: usageTracker,
+ }
+
+ if options.TLS != nil {
+ tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
+ if err != nil {
+ return nil, err
+ }
+ service.tlsConfig = tlsConfig
+ }
+
+ return service, nil
+}
+
+func (s *Service) Start(stage adapter.StartStage) error {
+ if stage != adapter.StartStateStart {
+ return nil
+ }
+
+ s.userManager.UpdateUsers(s.users)
+
+ credentials, err := platformReadCredentials(s.credentialPath)
+ if err != nil {
+ return E.Cause(err, "read credentials")
+ }
+ s.credentials = credentials
+
+ if s.usageTracker != nil {
+ err = s.usageTracker.Load()
+ if err != nil {
+ s.logger.Warn("load usage statistics: ", err)
+ }
+ }
+
+ router := chi.NewRouter()
+ router.Mount("/", s)
+
+ s.httpServer = &http.Server{Handler: router}
+
+ if s.tlsConfig != nil {
+ err = s.tlsConfig.Start()
+ if err != nil {
+ return E.Cause(err, "create TLS config")
+ }
+ }
+
+ tcpListener, err := s.listener.ListenTCP()
+ if err != nil {
+ return err
+ }
+
+ if s.tlsConfig != nil {
+ if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
+ s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
+ }
+ tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)
+ }
+
+ go func() {
+ serveErr := s.httpServer.Serve(tcpListener)
+ if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
+ s.logger.Error("serve error: ", serveErr)
+ }
+ }()
+
+ return nil
+}
+
+func (s *Service) getAccessToken() (string, error) {
+ s.accessMutex.RLock()
+ if !s.credentials.needsRefresh() {
+ token := s.credentials.getAccessToken()
+ s.accessMutex.RUnlock()
+ return token, nil
+ }
+ s.accessMutex.RUnlock()
+
+ s.accessMutex.Lock()
+ defer s.accessMutex.Unlock()
+
+ if !s.credentials.needsRefresh() {
+ return s.credentials.getAccessToken(), nil
+ }
+
+ newCredentials, err := refreshToken(s.httpClient, s.credentials)
+ if err != nil {
+ return "", err
+ }
+
+ s.credentials = newCredentials
+
+ err = platformWriteCredentials(newCredentials, s.credentialPath)
+ if err != nil {
+ s.logger.Warn("persist refreshed token: ", err)
+ }
+
+ return newCredentials.getAccessToken(), nil
+}
+
+func (s *Service) getAccountID() string {
+ s.accessMutex.RLock()
+ defer s.accessMutex.RUnlock()
+ return s.credentials.getAccountID()
+}
+
+func (s *Service) isAPIKeyMode() bool {
+ s.accessMutex.RLock()
+ defer s.accessMutex.RUnlock()
+ return s.credentials.isAPIKeyMode()
+}
+
+func (s *Service) getBaseURL() string {
+ if s.isAPIKeyMode() {
+ return openaiAPIBaseURL
+ }
+ return chatGPTBackendURL
+}
+
+func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ path := r.URL.Path
+ if !strings.HasPrefix(path, "/v1/") {
+ writeJSONError(w, r, http.StatusNotFound, "invalid_request_error", "path must start with /v1/")
+ return
+ }
+
+ var proxyPath string
+ if s.isAPIKeyMode() {
+ proxyPath = path
+ } else {
+ if path == "/v1/chat/completions" {
+ writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
+ "chat completions endpoint is only available in API key mode")
+ return
+ }
+ proxyPath = strings.TrimPrefix(path, "/v1")
+ }
+
+ var username string
+ if len(s.users) > 0 {
+ authHeader := r.Header.Get("Authorization")
+ if authHeader == "" {
+ s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header")
+ writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
+ return
+ }
+ clientToken := strings.TrimPrefix(authHeader, "Bearer ")
+ if clientToken == authHeader {
+ s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format")
+ writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
+ return
+ }
+ var ok bool
+ username, ok = s.userManager.Authenticate(clientToken)
+ if !ok {
+ s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken)
+ writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
+ return
+ }
+ }
+
+ var requestModel string
+
+ if s.usageTracker != nil && r.Body != nil {
+ bodyBytes, err := io.ReadAll(r.Body)
+ if err == nil {
+ var request struct {
+ Model string `json:"model"`
+ }
+ err := json.Unmarshal(bodyBytes, &request)
+ if err == nil {
+ requestModel = request.Model
+ }
+ r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
+ }
+ }
+
+ accessToken, err := s.getAccessToken()
+ if err != nil {
+ s.logger.Error("get access token: ", err)
+ writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed")
+ return
+ }
+
+ proxyURL := s.getBaseURL() + proxyPath
+ if r.URL.RawQuery != "" {
+ proxyURL += "?" + r.URL.RawQuery
+ }
+ proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body)
+ if err != nil {
+ s.logger.Error("create proxy request: ", err)
+ writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error")
+ return
+ }
+
+ for key, values := range r.Header {
+ if !isHopByHopHeader(key) && key != "Authorization" {
+ proxyRequest.Header[key] = values
+ }
+ }
+
+ for key, values := range s.httpHeaders {
+ proxyRequest.Header.Del(key)
+ proxyRequest.Header[key] = values
+ }
+
+ proxyRequest.Header.Set("Authorization", "Bearer "+accessToken)
+
+ if accountID := s.getAccountID(); accountID != "" {
+ proxyRequest.Header.Set("ChatGPT-Account-Id", accountID)
+ }
+
+ response, err := s.httpClient.Do(proxyRequest)
+ if err != nil {
+ writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error())
+ return
+ }
+ defer response.Body.Close()
+
+ for key, values := range response.Header {
+ if !isHopByHopHeader(key) {
+ w.Header()[key] = values
+ }
+ }
+ w.WriteHeader(response.StatusCode)
+
+ trackUsage := s.usageTracker != nil && response.StatusCode == http.StatusOK &&
+ (path == "/v1/chat/completions" || strings.HasPrefix(path, "/v1/responses"))
+ if trackUsage {
+ s.handleResponseWithTracking(w, response, path, requestModel, username)
+ } else {
+ mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
+ if err == nil && mediaType != "text/event-stream" {
+ _, _ = io.Copy(w, response.Body)
+ return
+ }
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ s.logger.Error("streaming not supported")
+ return
+ }
+ buffer := make([]byte, buf.BufferSize)
+ for {
+ n, err := response.Body.Read(buffer)
+ if n > 0 {
+ _, writeError := w.Write(buffer[:n])
+ if writeError != nil {
+ s.logger.Error("write streaming response: ", writeError)
+ return
+ }
+ flusher.Flush()
+ }
+ if err != nil {
+ return
+ }
+ }
+ }
+}
+
+func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) {
+ isChatCompletions := path == "/v1/chat/completions"
+ mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
+ isStreaming := err == nil && mediaType == "text/event-stream"
+
+ if !isStreaming {
+ bodyBytes, err := io.ReadAll(response.Body)
+ if err != nil {
+ s.logger.Error("read response body: ", err)
+ return
+ }
+
+ var responseModel string
+ var inputTokens, outputTokens, cachedTokens int64
+
+ if isChatCompletions {
+ var chatCompletion openai.ChatCompletion
+ if json.Unmarshal(bodyBytes, &chatCompletion) == nil {
+ responseModel = chatCompletion.Model
+ inputTokens = chatCompletion.Usage.PromptTokens
+ outputTokens = chatCompletion.Usage.CompletionTokens
+ cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens
+ }
+ } else {
+ var responsesResponse responses.Response
+ if json.Unmarshal(bodyBytes, &responsesResponse) == nil {
+ responseModel = string(responsesResponse.Model)
+ inputTokens = responsesResponse.Usage.InputTokens
+ outputTokens = responsesResponse.Usage.OutputTokens
+ cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens
+ }
+ }
+
+ if inputTokens > 0 || outputTokens > 0 {
+ if responseModel == "" {
+ responseModel = requestModel
+ }
+ if responseModel != "" {
+ s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username)
+ }
+ }
+
+ _, _ = writer.Write(bodyBytes)
+ return
+ }
+
+ flusher, ok := writer.(http.Flusher)
+ if !ok {
+ s.logger.Error("streaming not supported")
+ return
+ }
+
+ var inputTokens, outputTokens, cachedTokens int64
+ var responseModel string
+ buffer := make([]byte, buf.BufferSize)
+ var leftover []byte
+
+ for {
+ n, err := response.Body.Read(buffer)
+ if n > 0 {
+ data := append(leftover, buffer[:n]...)
+ lines := bytes.Split(data, []byte("\n"))
+
+ if err == nil {
+ leftover = lines[len(lines)-1]
+ lines = lines[:len(lines)-1]
+ } else {
+ leftover = nil
+ }
+
+ for _, line := range lines {
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 {
+ continue
+ }
+
+ if bytes.HasPrefix(line, []byte("data: ")) {
+ eventData := bytes.TrimPrefix(line, []byte("data: "))
+ if bytes.Equal(eventData, []byte("[DONE]")) {
+ continue
+ }
+
+ if isChatCompletions {
+ var chatChunk openai.ChatCompletionChunk
+ if json.Unmarshal(eventData, &chatChunk) == nil {
+ if chatChunk.Model != "" {
+ responseModel = chatChunk.Model
+ }
+ if chatChunk.Usage.PromptTokens > 0 {
+ inputTokens = chatChunk.Usage.PromptTokens
+ cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens
+ }
+ if chatChunk.Usage.CompletionTokens > 0 {
+ outputTokens = chatChunk.Usage.CompletionTokens
+ }
+ }
+ } else {
+ var streamEvent responses.ResponseStreamEventUnion
+ if json.Unmarshal(eventData, &streamEvent) == nil {
+ if streamEvent.Type == "response.completed" {
+ completedEvent := streamEvent.AsResponseCompleted()
+ if string(completedEvent.Response.Model) != "" {
+ responseModel = string(completedEvent.Response.Model)
+ }
+ if completedEvent.Response.Usage.InputTokens > 0 {
+ inputTokens = completedEvent.Response.Usage.InputTokens
+ cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens
+ }
+ if completedEvent.Response.Usage.OutputTokens > 0 {
+ outputTokens = completedEvent.Response.Usage.OutputTokens
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _, writeError := writer.Write(buffer[:n])
+ if writeError != nil {
+ s.logger.Error("write streaming response: ", writeError)
+ return
+ }
+ flusher.Flush()
+ }
+
+ if err != nil {
+ if responseModel == "" {
+ responseModel = requestModel
+ }
+
+ if inputTokens > 0 || outputTokens > 0 {
+ if responseModel != "" {
+ s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username)
+ }
+ }
+ return
+ }
+ }
+}
+
+func (s *Service) Close() error {
+ err := common.Close(
+ common.PtrOrNil(s.httpServer),
+ common.PtrOrNil(s.listener),
+ s.tlsConfig,
+ )
+
+ if s.usageTracker != nil {
+ s.usageTracker.cancelPendingSave()
+ saveErr := s.usageTracker.Save()
+ if saveErr != nil {
+ s.logger.Error("save usage statistics: ", saveErr)
+ }
+ }
+
+ return err
+}
diff --git a/sing-box/service/ocm/service_usage.go b/sing-box/service/ocm/service_usage.go
new file mode 100644
index 0000000000..7089f4d391
--- /dev/null
+++ b/sing-box/service/ocm/service_usage.go
@@ -0,0 +1,445 @@
+package ocm
+
+import (
+ "encoding/json"
+ "math"
+ "os"
+ "regexp"
+ "sync"
+ "time"
+
+ "github.com/sagernet/sing-box/log"
+ E "github.com/sagernet/sing/common/exceptions"
+)
+
+type UsageStats struct {
+ RequestCount int `json:"request_count"`
+ InputTokens int64 `json:"input_tokens"`
+ OutputTokens int64 `json:"output_tokens"`
+ CachedTokens int64 `json:"cached_tokens"`
+}
+
+func (u *UsageStats) UnmarshalJSON(data []byte) error {
+ type Alias UsageStats
+ aux := &struct {
+ *Alias
+ PromptTokens int64 `json:"prompt_tokens"`
+ CompletionTokens int64 `json:"completion_tokens"`
+ }{
+ Alias: (*Alias)(u),
+ }
+ err := json.Unmarshal(data, aux)
+ if err != nil {
+ return err
+ }
+ if u.InputTokens == 0 && aux.PromptTokens > 0 {
+ u.InputTokens = aux.PromptTokens
+ }
+ if u.OutputTokens == 0 && aux.CompletionTokens > 0 {
+ u.OutputTokens = aux.CompletionTokens
+ }
+ return nil
+}
+
+type CostCombination struct {
+ Model string `json:"model"`
+ Total UsageStats `json:"total"`
+ ByUser map[string]UsageStats `json:"by_user"`
+}
+
+type AggregatedUsage struct {
+ LastUpdated time.Time `json:"last_updated"`
+ Combinations []CostCombination `json:"combinations"`
+ mutex sync.Mutex
+ filePath string
+ logger log.ContextLogger
+ lastSaveTime time.Time
+ pendingSave bool
+ saveTimer *time.Timer
+ saveMutex sync.Mutex
+}
+
+type UsageStatsJSON struct {
+ RequestCount int `json:"request_count"`
+ InputTokens int64 `json:"input_tokens"`
+ OutputTokens int64 `json:"output_tokens"`
+ CachedTokens int64 `json:"cached_tokens"`
+ CostUSD float64 `json:"cost_usd"`
+}
+
+type CostCombinationJSON struct {
+ Model string `json:"model"`
+ Total UsageStatsJSON `json:"total"`
+ ByUser map[string]UsageStatsJSON `json:"by_user"`
+}
+
+type CostsSummaryJSON struct {
+ TotalUSD float64 `json:"total_usd"`
+ ByUser map[string]float64 `json:"by_user"`
+}
+
+type AggregatedUsageJSON struct {
+ LastUpdated time.Time `json:"last_updated"`
+ Costs CostsSummaryJSON `json:"costs"`
+ Combinations []CostCombinationJSON `json:"combinations"`
+}
+
+type ModelPricing struct {
+ InputPrice float64
+ OutputPrice float64
+ CachedInputPrice float64
+}
+
+type modelFamily struct {
+ pattern *regexp.Regexp
+ pricing ModelPricing
+}
+
+var (
+ gpt4oPricing = ModelPricing{
+ InputPrice: 2.5,
+ OutputPrice: 10.0,
+ CachedInputPrice: 1.25,
+ }
+
+ gpt4oMiniPricing = ModelPricing{
+ InputPrice: 0.15,
+ OutputPrice: 0.6,
+ CachedInputPrice: 0.075,
+ }
+
+ gpt4oAudioPricing = ModelPricing{
+ InputPrice: 2.5,
+ OutputPrice: 10.0,
+ CachedInputPrice: 1.25,
+ }
+
+ o1Pricing = ModelPricing{
+ InputPrice: 15.0,
+ OutputPrice: 60.0,
+ CachedInputPrice: 7.5,
+ }
+
+ o1MiniPricing = ModelPricing{
+ InputPrice: 1.1,
+ OutputPrice: 4.4,
+ CachedInputPrice: 0.55,
+ }
+
+ o3MiniPricing = ModelPricing{
+ InputPrice: 1.1,
+ OutputPrice: 4.4,
+ CachedInputPrice: 0.55,
+ }
+
+ o3Pricing = ModelPricing{
+ InputPrice: 2.0,
+ OutputPrice: 8.0,
+ CachedInputPrice: 1.0,
+ }
+
+ o4MiniPricing = ModelPricing{
+ InputPrice: 1.1,
+ OutputPrice: 4.4,
+ CachedInputPrice: 0.55,
+ }
+
+ gpt41Pricing = ModelPricing{
+ InputPrice: 2.0,
+ OutputPrice: 8.0,
+ CachedInputPrice: 0.5,
+ }
+
+ gpt41MiniPricing = ModelPricing{
+ InputPrice: 0.4,
+ OutputPrice: 1.6,
+ CachedInputPrice: 0.1,
+ }
+
+ gpt41NanoPricing = ModelPricing{
+ InputPrice: 0.1,
+ OutputPrice: 0.4,
+ CachedInputPrice: 0.025,
+ }
+
+ modelFamilies = []modelFamily{
+ {
+ pattern: regexp.MustCompile(`^gpt-4\.1-nano`),
+ pricing: gpt41NanoPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^gpt-4\.1-mini`),
+ pricing: gpt41MiniPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^gpt-4\.1`),
+ pricing: gpt41Pricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^o4-mini`),
+ pricing: o4MiniPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^o3-mini`),
+ pricing: o3MiniPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^o3`),
+ pricing: o3Pricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^o1-mini`),
+ pricing: o1MiniPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^o1`),
+ pricing: o1Pricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^gpt-4o-audio`),
+ pricing: gpt4oAudioPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^gpt-4o-mini`),
+ pricing: gpt4oMiniPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^gpt-4o`),
+ pricing: gpt4oPricing,
+ },
+ {
+ pattern: regexp.MustCompile(`^chatgpt-4o`),
+ pricing: gpt4oPricing,
+ },
+ }
+)
+
+func getPricing(model string) ModelPricing {
+ for _, family := range modelFamilies {
+ if family.pattern.MatchString(model) {
+ return family.pricing
+ }
+ }
+ return gpt4oPricing
+}
+
+func calculateCost(stats UsageStats, model string) float64 {
+ pricing := getPricing(model)
+
+ regularInputTokens := stats.InputTokens - stats.CachedTokens
+ if regularInputTokens < 0 {
+ regularInputTokens = 0
+ }
+
+ cost := (float64(regularInputTokens)*pricing.InputPrice +
+ float64(stats.OutputTokens)*pricing.OutputPrice +
+ float64(stats.CachedTokens)*pricing.CachedInputPrice) / 1_000_000
+
+ return math.Round(cost*100) / 100
+}
+
+func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
+ u.mutex.Lock()
+ defer u.mutex.Unlock()
+
+ result := &AggregatedUsageJSON{
+ LastUpdated: u.LastUpdated,
+ Combinations: make([]CostCombinationJSON, len(u.Combinations)),
+ Costs: CostsSummaryJSON{
+ TotalUSD: 0,
+ ByUser: make(map[string]float64),
+ },
+ }
+
+ for i, combo := range u.Combinations {
+ totalCost := calculateCost(combo.Total, combo.Model)
+
+ result.Costs.TotalUSD += totalCost
+
+ comboJSON := CostCombinationJSON{
+ Model: combo.Model,
+ Total: UsageStatsJSON{
+ RequestCount: combo.Total.RequestCount,
+ InputTokens: combo.Total.InputTokens,
+ OutputTokens: combo.Total.OutputTokens,
+ CachedTokens: combo.Total.CachedTokens,
+ CostUSD: totalCost,
+ },
+ ByUser: make(map[string]UsageStatsJSON),
+ }
+
+ for user, userStats := range combo.ByUser {
+ userCost := calculateCost(userStats, combo.Model)
+ result.Costs.ByUser[user] += userCost
+
+ comboJSON.ByUser[user] = UsageStatsJSON{
+ RequestCount: userStats.RequestCount,
+ InputTokens: userStats.InputTokens,
+ OutputTokens: userStats.OutputTokens,
+ CachedTokens: userStats.CachedTokens,
+ CostUSD: userCost,
+ }
+ }
+
+ result.Combinations[i] = comboJSON
+ }
+
+ result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100
+ for user, cost := range result.Costs.ByUser {
+ result.Costs.ByUser[user] = math.Round(cost*100) / 100
+ }
+
+ return result
+}
+
+func (u *AggregatedUsage) Load() error {
+ u.mutex.Lock()
+ defer u.mutex.Unlock()
+
+ data, err := os.ReadFile(u.filePath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+
+ var temp struct {
+ LastUpdated time.Time `json:"last_updated"`
+ Combinations []CostCombination `json:"combinations"`
+ }
+
+ err = json.Unmarshal(data, &temp)
+ if err != nil {
+ return err
+ }
+
+ u.LastUpdated = temp.LastUpdated
+ u.Combinations = temp.Combinations
+
+ for i := range u.Combinations {
+ if u.Combinations[i].ByUser == nil {
+ u.Combinations[i].ByUser = make(map[string]UsageStats)
+ }
+ }
+
+ return nil
+}
+
+func (u *AggregatedUsage) Save() error {
+ jsonData := u.ToJSON()
+
+ data, err := json.MarshalIndent(jsonData, "", " ")
+ if err != nil {
+ return err
+ }
+
+ tmpFile := u.filePath + ".tmp"
+ err = os.WriteFile(tmpFile, data, 0o644)
+ if err != nil {
+ return err
+ }
+ defer os.Remove(tmpFile)
+ err = os.Rename(tmpFile, u.filePath)
+ if err == nil {
+ u.saveMutex.Lock()
+ u.lastSaveTime = time.Now()
+ u.saveMutex.Unlock()
+ }
+ return err
+}
+
+func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error {
+ if model == "" {
+ return E.New("model cannot be empty")
+ }
+
+ u.mutex.Lock()
+ defer u.mutex.Unlock()
+
+ u.LastUpdated = time.Now()
+
+ var combo *CostCombination
+ for i := range u.Combinations {
+ if u.Combinations[i].Model == model {
+ combo = &u.Combinations[i]
+ break
+ }
+ }
+
+ if combo == nil {
+ newCombo := CostCombination{
+ Model: model,
+ Total: UsageStats{},
+ ByUser: make(map[string]UsageStats),
+ }
+ u.Combinations = append(u.Combinations, newCombo)
+ combo = &u.Combinations[len(u.Combinations)-1]
+ }
+
+ combo.Total.RequestCount++
+ combo.Total.InputTokens += inputTokens
+ combo.Total.OutputTokens += outputTokens
+ combo.Total.CachedTokens += cachedTokens
+
+ if user != "" {
+ userStats := combo.ByUser[user]
+ userStats.RequestCount++
+ userStats.InputTokens += inputTokens
+ userStats.OutputTokens += outputTokens
+ userStats.CachedTokens += cachedTokens
+ combo.ByUser[user] = userStats
+ }
+
+ go u.scheduleSave()
+
+ return nil
+}
+
+func (u *AggregatedUsage) scheduleSave() {
+ const saveInterval = time.Minute
+
+ u.saveMutex.Lock()
+ defer u.saveMutex.Unlock()
+
+ timeSinceLastSave := time.Since(u.lastSaveTime)
+
+ if timeSinceLastSave >= saveInterval {
+ go u.saveAsync()
+ return
+ }
+
+ if u.pendingSave {
+ return
+ }
+
+ u.pendingSave = true
+ remainingTime := saveInterval - timeSinceLastSave
+
+ u.saveTimer = time.AfterFunc(remainingTime, func() {
+ u.saveMutex.Lock()
+ u.pendingSave = false
+ u.saveMutex.Unlock()
+ u.saveAsync()
+ })
+}
+
+func (u *AggregatedUsage) saveAsync() {
+ err := u.Save()
+ if err != nil {
+ if u.logger != nil {
+ u.logger.Error("save usage statistics: ", err)
+ }
+ }
+}
+
+func (u *AggregatedUsage) cancelPendingSave() {
+ u.saveMutex.Lock()
+ defer u.saveMutex.Unlock()
+
+ if u.saveTimer != nil {
+ u.saveTimer.Stop()
+ u.saveTimer = nil
+ }
+ u.pendingSave = false
+}
diff --git a/sing-box/service/ocm/service_user.go b/sing-box/service/ocm/service_user.go
new file mode 100644
index 0000000000..494b981b9b
--- /dev/null
+++ b/sing-box/service/ocm/service_user.go
@@ -0,0 +1,29 @@
+package ocm
+
+import (
+ "sync"
+
+ "github.com/sagernet/sing-box/option"
+)
+
+type UserManager struct {
+ accessMutex sync.RWMutex
+ tokenMap map[string]string
+}
+
+func (m *UserManager) UpdateUsers(users []option.OCMUser) {
+ m.accessMutex.Lock()
+ defer m.accessMutex.Unlock()
+ tokenMap := make(map[string]string, len(users))
+ for _, user := range users {
+ tokenMap[user.Token] = user.Name
+ }
+ m.tokenMap = tokenMap
+}
+
+func (m *UserManager) Authenticate(token string) (string, bool) {
+ m.accessMutex.RLock()
+ username, found := m.tokenMap[token]
+ m.accessMutex.RUnlock()
+ return username, found
+}
diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js
index 3bde752d82..1a982965cc 100644
--- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js
+++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js
@@ -841,7 +841,22 @@ return view.extend({
_('Routing mode of the traffic enters mihomo via firewall rules.'));
so.value('', _('All allowed'));
so.value('bypass_cn', _('Bypass CN'));
- so.value('routing_gfw', _('Routing GFW'));
+ if (features.has_dnsmasq_full)
+ so.value('routing_gfw', _('Routing GFW'));
+ so.validate = function(section_id, value) {
+ const mode = this.section.getOption('routing_mode').formvalue(section_id);
+ let pd = this.section.getUIElement(section_id, 'routing_domain').node.querySelector('input');
+
+ // Force enabled
+ if (mode === 'routing_gfw') {
+ pd.checked = true;
+ pd.disabled = true;
+ } else {
+ pd.removeAttribute('disabled');
+ }
+
+ return true;
+ }
so = ss.taboption('routing_control', form.Flag, 'routing_domain', _('Handle domain'),
_('Routing mode will be handle domain.') + '' +
diff --git a/small/luci-app-passwall/Makefile b/small/luci-app-passwall/Makefile
index c005c43384..02ecc563a7 100644
--- a/small/luci-app-passwall/Makefile
+++ b/small/luci-app-passwall/Makefile
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
-PKG_VERSION:=25.12.13
+PKG_VERSION:=25.12.16
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
diff --git a/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js b/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js
index 993e88f396..94d9fac567 100644
--- a/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js
+++ b/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js
@@ -1 +1 @@
-var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push(' | ');g.push("
")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
\ No newline at end of file
+var QRCode;!function(){function t(t){this.mode=r.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,o=this.data.length;e65536?(i[0]=240|(1835008&n)>>>18,i[1]=128|(258048&n)>>>12,i[2]=128|(4032&n)>>>6,i[3]=128|63&n):n>2048?(i[0]=224|(61440&n)>>>12,i[1]=128|(4032&n)>>>6,i[2]=128|63&n):n>128?(i[0]=192|(1984&n)>>>6,i[1]=128|63&n):i[0]=n,this.parsedData.push(i)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function e(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}t.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=g.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var i=0;i>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=g.getBCHTypeInfo(r),i=0;i<15;i++){var n=!t&&1==(o>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n}for(i=0;i<15;i++){n=!t&&1==(o>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;n>>i&1)),g.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--i&&(n++,i=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(t,r,o){for(var i=m.getRSBlocks(t,r),n=new _,a=0;a8*h)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*h+")");for(n.getLengthInBits()+4<=8*h&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*h||(n.put(e.PAD0,8),n.getLengthInBits()>=8*h));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(t,e){for(var r=0,o=0,i=0,n=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(c):0}}var m=0;for(u=0;u=0;)e^=g.G15<=0;)e^=g.G18<>>=1;return e},getPatternPosition:function(t){return g.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case i:return(e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case s:return(e+r)%3==0;case h:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case l:return e*r%2+e*r%3==0;case u:return(e*r%2+e*r%3)%2==0;case f:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new p([1],0),r=0;r5&&(r+=3+n-5)}for(o=0;o=256;)t-=255;return d.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},c=0;c<8;c++)d.EXP_TABLE[c]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var v=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function C(){var t=!1,e=navigator.userAgent;if(/android/i.test(e)){t=!0;var r=e.toString().match(/android ([0-9]\.[0-9])/i);r&&r[1]&&(t=parseFloat(r[1]))}return t}var w=function(){var t=function(t,e){this._el=t,this._htOption=e};return t.prototype.draw=function(t){var e=this._htOption,r=this._el,o=t.getModuleCount();Math.floor(e.width/o),Math.floor(e.height/o);function i(t,e){var r=document.createElementNS("http://www.w3.org/2000/svg",t);for(var o in e)e.hasOwnProperty(o)&&r.setAttribute(o,e[o]);return r}this.clear();var n=i("svg",{viewBox:"0 0 "+String(o)+" "+String(o),width:"100%",height:"100%",fill:e.colorLight});n.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),r.appendChild(n),n.appendChild(i("rect",{fill:e.colorLight,width:"100%",height:"100%"})),n.appendChild(i("rect",{fill:e.colorDark,width:"1",height:"1",id:"template"}));for(var a=0;a'],s=0;s");for(var h=0;h');a.push("")}a.push(""),r.innerHTML=a.join("");var l=r.childNodes[0],u=(e.width-l.offsetWidth)/2,f=(e.height-l.offsetHeight)/2;u>0&&f>0&&(l.style.margin=f+"px "+u+"px")},t.prototype.clear=function(){this._el.innerHTML=""},t}():function(){function t(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}if(this._android&&this._android<=2.1){var e=1/window.devicePixelRatio,r=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(t,o,i,n,a,s,h,l,u){if("nodeName"in t&&/img/i.test(t.nodeName))for(var f=arguments.length-1;f>=1;f--)arguments[f]=arguments[f]*e;else void 0===l&&(arguments[1]*=e,arguments[2]*=e,arguments[3]*=e,arguments[4]*=e);r.apply(this,arguments)}}var o=function(t,e){this._bIsPainted=!1,this._android=C(),this._htOption=e,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=e.width,this._elCanvas.height=e.height,t.appendChild(this._elCanvas),this._el=t,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.alt="Scan me!",this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return o.prototype.draw=function(t){var e=this._elImage,r=this._oContext,o=this._htOption,i=t.getModuleCount(),n=o.width/i,a=o.height/i,s=Math.round(n),h=Math.round(a);e.style.display="none",this.clear();for(var l=0;lv.length)throw new Error("Too long data");return r}(QRCode=function(t,e){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:o.H},"string"==typeof e&&(e={text:e}),e)for(var r in e)this._htOption[r]=e[r];"string"==typeof t&&(t=document.getElementById(t)),this._htOption.useSVG&&(D=w),this._android=C(),this._el=t,this._oQRCode=null,this._oDrawing=new D(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)}).prototype.makeCode=function(t){this._oQRCode=new e(A(t,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(t),this._oQRCode.make(),this._el.title=t,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=o}(),"undefined"!=typeof module&&(module.exports=QRCode);
diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
index ac109cd95f..e822fb6d65 100644
--- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
+++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
@@ -308,6 +308,17 @@ o = s:option(DummyValue, "switch_mode", " ")
o.template = appname .. "/global/proxy"
o:depends({ _tcp_node_bool = "1" })
+-- Node → DNS Depends Settings
+o = s:option(DummyValue, "_node_sel_shunt", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ tcp_node = "__always__" })
+
+o = s:option(DummyValue, "_node_sel_other", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
+
---- DNS
o = s:option(ListValue, "dns_shunt", "DNS " .. translate("Shunt"))
o.default = "chinadns-ng"
@@ -333,6 +344,7 @@ end
if has_xray then
o:value("xray", "Xray")
end
+o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["tcp_node"]
local id_val = f and f:formvalue(section) or ""
@@ -362,7 +374,9 @@ o:value("tcp", "TCP")
o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
o:depends("dns_mode", "xray")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, ["tcp+doh"] = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "xray" then
@@ -377,7 +391,9 @@ o:value("tcp", "TCP")
o:value("doh", "DoH")
o:depends("dns_mode", "sing-box")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, doh = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "sing-box" then
@@ -444,6 +460,7 @@ o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet"))
o.datatype = "ipaddr"
o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"})
+o:depends({_node_sel_shunt = "1"})
o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS"))
o.default = "none"
@@ -485,6 +502,7 @@ for k, v in pairs(nodes_table) do
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["xray_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
end
if v.type == "sing-box" and has_singbox then
tcp:value(v.id, v["remark"])
@@ -493,17 +511,13 @@ for k, v in pairs(nodes_table) do
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["singbox_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
- end
- if has_xray or has_singbox then
- s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
end
else
tcp:value(v.id, v["remark"])
tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-
- s.fields["dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
end
end
diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 30665ed6dd..de37538e08 100644
--- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -309,6 +309,18 @@ o = s:taboption("Main", Flag, "tcp_node_socks_bind_local", translate("TCP Node")
o.default = "1"
o:depends({ tcp_node = "", ["!reverse"] = true })
+-- Node → DNS Depends Settings
+o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ tcp_node = "__always__" })
+
+o = s:taboption("Main", DummyValue, "_node_sel_other", "")
+o.template = appname .. "/cbi/hidevalue"
+o.value = "1"
+o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
+
+-- [[ DNS Settings ]]--
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shunt"))
@@ -388,8 +400,8 @@ end
if has_xray then
o:value("xray", "Xray")
end
-o:depends({ dns_shunt = "chinadns-ng", tcp_node = "" })
-o:depends({ dns_shunt = "dnsmasq", tcp_node = "" })
+o:depends({ dns_shunt = "chinadns-ng", _node_sel_other = "1" })
+o:depends({ dns_shunt = "dnsmasq", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["smartdns_dns_mode"]
if f and f:formvalue(section) then
@@ -408,7 +420,7 @@ if api.is_finded("smartdns") then
if has_xray then
o:value("xray", "Xray")
end
- o:depends({ dns_shunt = "smartdns", tcp_node = "" })
+ o:depends({ dns_shunt = "smartdns", _node_sel_other = "1" })
o.remove = function(self, section)
local f = s.fields["dns_mode"]
if f and f:formvalue(section) then
@@ -468,7 +480,9 @@ o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
o:depends("dns_mode", "xray")
o:depends("smartdns_dns_mode", "xray")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, ["tcp+doh"] = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "xray" or s.fields["smartdns_dns_mode"]:formvalue(section) == "xray" then
@@ -484,7 +498,9 @@ o:value("doh", "DoH")
o:depends("dns_mode", "sing-box")
o:depends("smartdns_dns_mode", "sing-box")
o.cfgvalue = function(self, section)
- return m:get(section, "v2ray_dns_mode")
+ local v = m:get(section, "v2ray_dns_mode")
+ local key = { udp = true, tcp = true, doh = true }
+ return (v and key[v]) and v or self.default
end
o.write = function(self, section, value)
if s.fields["dns_mode"]:formvalue(section) == "sing-box" or s.fields["smartdns_dns_mode"]:formvalue(section) == "sing-box" then
@@ -548,6 +564,7 @@ o.datatype = "ipaddr"
o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"})
o:depends("dns_shunt", "smartdns")
+o:depends("_node_sel_shunt", "1")
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
o.default = "0"
@@ -557,6 +574,7 @@ o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"})
o:depends({dns_mode = "xray", dns_shunt = "dnsmasq"})
o:depends({dns_mode = "xray", dns_shunt = "chinadns-ng"})
o:depends({smartdns_dns_mode = "xray", dns_shunt = "smartdns"})
+o:depends("_node_sel_shunt", "1")
o.validate = function(self, value, t)
if value and value == "1" then
local _dns_mode = s.fields["dns_mode"]:formvalue(t)
@@ -810,22 +828,15 @@ for k, v in pairs(nodes_table) do
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", tcp_node = v.id })
- s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", tcp_node = v.id })
- s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id })
- s.fields["remote_fakedns"]:depends({ tcp_node = v.id })
+ s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
+ s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", _node_sel_shunt = "1" })
+ s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", _node_sel_shunt = "1" })
end
else
tcp:value(v.id, v["remark"])
tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
udp:value(v.id, v["remark"])
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-
- s.fields["dns_mode"]:depends({ dns_shunt = "chinadns-ng", tcp_node = v.id })
- s.fields["dns_mode"]:depends({ dns_shunt = "dnsmasq", tcp_node = v.id })
- if api.is_finded("smartdns") then
- s.fields["smartdns_dns_mode"]:depends({ dns_shunt = "smartdns", tcp_node = v.id })
- end
end
if v.type == "Socks" then
if has_singbox or has_xray then
diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
index 4c1aae949a..92145d3993 100644
--- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
+++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
@@ -147,7 +147,7 @@ o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node"))
o.rawhtml = true
function o.cfgvalue(self, section)
return string.format(
- [[]],
+ [[]],
translate("Delete All Subscribe Node"))
end
@@ -155,7 +155,7 @@ o = s:option(DummyValue, "_update", translate("Manual subscription All"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
- ]],
+ ]],
translate("Manual subscription All"))
end
@@ -231,7 +231,7 @@ o.rawhtml = true
function o.cfgvalue(self, section)
local remark = m:get(section, "remark") or ""
return string.format(
- [[]],
+ [[]],
remark, translate("Delete the subscribed node"))
end
@@ -239,7 +239,7 @@ o = s:option(DummyValue, "_update", translate("Manual subscription"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
- ]],
+ ]],
section, translate("Manual subscription"))
end
diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua
index 7b7be7cee9..63dc72556e 100644
--- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -201,8 +201,8 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "/",
- headers = (node.ws_host or node.ws_user_agent) and {
- Host = node.ws_host,
+ host = node.ws_host,
+ headers = node.ws_user_agent and {
["User-Agent"] = node.ws_user_agent
} or nil,
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil,
diff --git a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
index 685ea00918..1bcf0ab744 100644
--- a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
+++ b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
@@ -139,7 +139,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
lv_openPanel(cbid,display,panel,listContainer,hiddenSelect,searchInput);
}
});
- lv_adaptiveStyle(cbid); // copy select styles
+ lv_registerAdaptive(cbid);
})();
//]]>
diff --git a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
index 922db7279b..21538a2ee0 100644
--- a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
+++ b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
@@ -244,9 +244,11 @@ local appname = api.appname
return lv_rgbToHex(r, g, b);
}
+ // copy select styles
function lv_adaptiveStyle(cbid) {
const display = document.getElementById(cbid + ".display");
const hiddenSelect = document.getElementById(cbid);
+ const panel = document.getElementById(cbid + ".panel");
if (hiddenSelect && display) {
const elOption = hiddenSelect.getElementsByTagName("option")[0]
const styleSelect = window.getComputedStyle(hiddenSelect)
@@ -466,7 +468,13 @@ local appname = api.appname
}
panel.style.left = rect.left + "px";
panel.style.top = top + "px";
- panel.style.minWidth = rect.width + "px";
+ const panelRect = panel.getBoundingClientRect();
+ const displayWidth = rect.width;
+ const remainingWidth = window.innerWidth - panelRect.left - 12;
+ const maxWidth = Math.max(displayWidth, Math.floor(remainingWidth));
+ panel.style.maxWidth = maxWidth + "px";
+ panel.style.minWidth = displayWidth + "px";
+ panel.style.width = "auto";
panel.style.visibility = "";
}
@@ -601,22 +609,24 @@ local appname = api.appname
if(!li || li === listContainer) return;
const key = li.getAttribute('data-key') || "";
const text = li.querySelector(".lv-item-label")?.textContent || li.textContent || key;
- if (key !== hiddenSelect.value) {
- //动态改值
+
+ const changed = key !== hiddenSelect.value;
+ if (changed) {
+ //改值
hiddenSelect.options[0].value = key;
+ hiddenSelect.options[0].text = key;
hiddenSelect.value = key;
labelSpan.textContent = text;
labelSpan.title = text;
- setTimeout(() => {
- try {
- const evt = new Event('change', { bubbles: true });
- hiddenSelect.dispatchEvent(evt);
- } catch(e){}
- }, 0);
lv_highlightSelectedItem(listContainer, hiddenSelect);
lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput);
}
lv_closePanel(cbid,panel,listContainer,hiddenSelect,searchInput);
+ if (changed) {
+ setTimeout(() => {
+ hiddenSelect.dispatchEvent(new Event('change', { bubbles: true }));
+ }, 0);
+ }
});
// 搜索功能
@@ -635,27 +645,22 @@ local appname = api.appname
}
});
});
-
- // 设置宽度
- panel.style.maxWidth = lv_getPanelMaxWidth(display);
- panel.style.minWidth = display.getBoundingClientRect().width + "px";
- panel.style.width = "auto";
}
- function lv_getPanelMaxWidth(display) {
- if (!display) return 0;
- const rectW = el => el && el.getBoundingClientRect().width;
- const fallback = rectW(display) || 0;
- const cbiValue = display.closest(".cbi-value");
- if (cbiValue) {
- const valueW = rectW(cbiValue);
- const titleW = rectW(cbiValue.querySelector(".cbi-value-title"));
- if (valueW) {
- return Math.floor(titleW ? valueW - titleW : valueW);
- }
- }
- const fieldW = rectW(display.closest(".cbi-value-field"));
- return Math.floor(fieldW || fallback);
+ const lv_adaptiveControls = new Set();
+ function lv_registerAdaptive(cbid) {
+ lv_adaptiveControls.add(cbid);
+ lv_adaptiveStyle(cbid);
}
+ let lv_adaptiveTicking = false;
+ window.addEventListener("resize", () => {
+ if (!lv_adaptiveTicking) {
+ lv_adaptiveTicking = true;
+ requestAnimationFrame(() => {
+ lv_adaptiveControls.forEach(cbid => lv_adaptiveStyle(cbid));
+ lv_adaptiveTicking = false;
+ });
+ }
+ });
//]]>
diff --git a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
index 9a25a3ee24..91f5f410d7 100644
--- a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
+++ b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
@@ -1657,7 +1657,9 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
+
+
-
<%+cbi/valuefooter%>
diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po
index 04d5303a6f..c04c6d1cb0 100644
--- a/small/luci-app-passwall/po/zh-cn/passwall.po
+++ b/small/luci-app-passwall/po/zh-cn/passwall.po
@@ -1121,7 +1121,7 @@ msgid "Are you sure you want to delete all subscribed nodes?"
msgstr "您确定要删除所有已订阅的节点吗?"
msgid "Manual subscription All"
-msgstr "手动订阅全部"
+msgstr "手动订阅全部链接"
msgid "This remark already exists, please change a new remark."
msgstr "此备注已存在,请改一个新的备注。"
diff --git a/small/naiveproxy/Makefile b/small/naiveproxy/Makefile
index 7bdc17150f..9818157faf 100644
--- a/small/naiveproxy/Makefile
+++ b/small/naiveproxy/Makefile
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=naiveproxy
-PKG_VERSION:=140.0.7339.123-3
+PKG_VERSION:=143.0.7499.109-1
PKG_RELEASE:=1
# intel 80386 & riscv64 & cortex-a76
@@ -30,7 +30,7 @@ else ifeq ($(ARCH_PACKAGES),arm_arm1176jzf-s_vfp)
else ifeq ($(ARCH_PACKAGES),arm_arm926ej-s)
PKG_HASH:=27b28beaa032165e9b93d423353ced3cfe594c71353fd9d109699f330b785732
else ifeq ($(ARCH_PACKAGES),arm_cortex-a15_neon-vfpv4)
- PKG_HASH:=14d4929d2b417baa7f31df114eefa2d0a1ae4bc255cf1b3a037558bd67b77faf
+ PKG_HASH:=5e2dd76559c1eef027471ddadb646a5aaff7f4d2b337bf9aa609bc38dde4247b
else ifeq ($(ARCH_PACKAGES),arm_cortex-a5_vfpv4)
PKG_HASH:=49a9c7eecab54155a31c3bfae71cf5193a881189715c7b4d29ce50a8c5c759d6
else ifeq ($(ARCH_PACKAGES),arm_cortex-a7)
@@ -46,9 +46,9 @@ else ifeq ($(ARCH_PACKAGES),arm_cortex-a9)
else ifeq ($(ARCH_PACKAGES),arm_cortex-a9_neon)
PKG_HASH:=ab5a699ef830d65344f014547ae6ce73e0f25103ad02d3e80a5d441e19c7e21e
else ifeq ($(ARCH_PACKAGES),arm_cortex-a9_vfpv3-d16)
- PKG_HASH:=efb7650c31c862564f39be269d67fca4e1a43bf356a5d4c1c158cc83e7c378e2
+ PKG_HASH:=294b676fb05f7042e16ca5871200915f3bcc73a6650c99f17857d651af6bb920
else ifeq ($(ARCH_PACKAGES),arm_mpcore)
- PKG_HASH:=1e0fa7d2f283807a019c85ddb58f99b711bcf86eb4ac759562b221d998091093
+ PKG_HASH:=0498d19350ae3e6c064913758199c53014254c4765a106eac1913fd7642eff41
else ifeq ($(ARCH_PACKAGES),arm_xscale)
PKG_HASH:=40175f13102597b88f92c664bf5c595ab69b248a5b64229a6aba388be77345b4
else ifeq ($(ARCH_PACKAGES),mipsel_24kc)
@@ -60,7 +60,7 @@ else ifeq ($(ARCH_PACKAGES),riscv64)
else ifeq ($(ARCH_PACKAGES),x86)
PKG_HASH:=a3e8aad951330b995273176cc310038971f4f3fff56ab047d5e60e3a0eb67d14
else ifeq ($(ARCH_PACKAGES),x86_64)
- PKG_HASH:=d6c39befccb1f3ad54ffa11c5ae8ad11a90151998eeaae6b1a73cc0702f24966
+ PKG_HASH:=7387ce2af58463735d839f45d8564626cdc1baea3e053c2479ba5fe0f2fa721f
else
PKG_HASH:=dummy
endif
diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile
index f114d64182..6b282d594a 100644
--- a/small/v2ray-geodata/Makefile
+++ b/small/v2ray-geodata/Makefile
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=6878dbacfb1fcb1ee022f63ed6934bcefc95a3c4ba10c88f1131fb88dbf7c337
endef
-GEOSITE_VER:=20251215045505
+GEOSITE_VER:=20251216015506
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
- HASH:=8ac5d1021786c3bcd1ee96219421a8d312cfa5ecb81596f8f2dcf14ef4cd0b82
+ HASH:=a0ed32edde7bc08e5531cee979b4da31ef966347162cd351afe836829df6e5c6
endef
GEOSITE_IRAN_VER:=202512150045
diff --git a/v2rayn/.github/workflows/build-linux.yml b/v2rayn/.github/workflows/build-linux.yml
index d5c40e576f..7ac333f2e6 100644
--- a/v2rayn/.github/workflows/build-linux.yml
+++ b/v2rayn/.github/workflows/build-linux.yml
@@ -50,7 +50,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
- name: Upload build artifacts
- uses: actions/upload-artifact@v5.0.0
+ uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-linux
path: |
@@ -116,7 +116,7 @@ jobs:
fetch-depth: '0'
- name: Restore build artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: v2rayN-linux
path: ${{ github.workspace }}/v2rayN/Release
@@ -137,7 +137,7 @@ jobs:
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
- name: Upload RPM artifacts
- uses: actions/upload-artifact@v5.0.0
+ uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-rpm
path: dist/rpm/**/*.rpm
diff --git a/v2rayn/.github/workflows/build-osx.yml b/v2rayn/.github/workflows/build-osx.yml
index 2143779638..ee01e92ca8 100644
--- a/v2rayn/.github/workflows/build-osx.yml
+++ b/v2rayn/.github/workflows/build-osx.yml
@@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
- uses: actions/upload-artifact@v5.0.0
+ uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-macos
path: |
diff --git a/v2rayn/.github/workflows/build-windows-desktop.yml b/v2rayn/.github/workflows/build-windows-desktop.yml
index 5da5f3dbdf..b2c3f7943d 100644
--- a/v2rayn/.github/workflows/build-windows-desktop.yml
+++ b/v2rayn/.github/workflows/build-windows-desktop.yml
@@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
- uses: actions/upload-artifact@v5.0.0
+ uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-windows-desktop
path: |
diff --git a/v2rayn/.github/workflows/build-windows.yml b/v2rayn/.github/workflows/build-windows.yml
index 82542a6bf1..c4c7e60dc6 100644
--- a/v2rayn/.github/workflows/build-windows.yml
+++ b/v2rayn/.github/workflows/build-windows.yml
@@ -46,7 +46,7 @@ jobs:
- name: Upload build artifacts
- uses: actions/upload-artifact@v5.0.0
+ uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-windows
path: |
diff --git a/yt-dlp/test/helper.py b/yt-dlp/test/helper.py
index e96835fc46..5a937d9617 100644
--- a/yt-dlp/test/helper.py
+++ b/yt-dlp/test/helper.py
@@ -261,7 +261,7 @@ def sanitize_got_info_dict(got_dict):
def expect_info_dict(self, got_dict, expected_dict):
ALLOWED_KEYS_SORT_ORDER = (
# NB: Keep in sync with the docstring of extractor/common.py
- 'id', 'ext', 'direct', 'display_id', 'title', 'alt_title', 'description', 'media_type',
+ 'ie_key', 'url', 'id', 'ext', 'direct', 'display_id', 'title', 'alt_title', 'description', 'media_type',
'uploader', 'uploader_id', 'uploader_url', 'channel', 'channel_id', 'channel_url', 'channel_is_verified',
'channel_follower_count', 'comment_count', 'view_count', 'concurrent_view_count',
'like_count', 'dislike_count', 'repost_count', 'average_rating', 'age_limit', 'duration', 'thumbnail', 'heatmap',
diff --git a/yt-dlp/yt_dlp/extractor/youtube/_tab.py b/yt-dlp/yt_dlp/extractor/youtube/_tab.py
index f991d99759..450b4aa51d 100644
--- a/yt-dlp/yt_dlp/extractor/youtube/_tab.py
+++ b/yt-dlp/yt_dlp/extractor/youtube/_tab.py
@@ -382,7 +382,8 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
('accessibilityText', {lambda x: re.fullmatch(r'(.+), (?:[\d,.]+(?:[KM]| million)?|No) views? - play Short', x)}, 1)), any),
'view_count': ('overlayMetadata', 'secondaryText', 'content', {parse_count}),
}),
- thumbnails=self._extract_thumbnails(renderer, 'thumbnail', final_key='sources'))
+ thumbnails=self._extract_thumbnails(
+ renderer, ('thumbnailViewModel', 'thumbnailViewModel', 'image'), final_key='sources'))
return
def _video_entry(self, video_renderer):
@@ -1585,7 +1586,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_count': 50,
'expected_warnings': ['YouTube Music is not directly supported'],
}, {
- # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
'note': 'unlisted single video playlist',
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
'info_dict': {
@@ -1885,8 +1885,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 30,
}, {
# Shorts url result in shorts tab
- # TODO: Fix channel id extraction
- # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/shorts',
'info_dict': {
'id': 'UCiu-3thuViMebBjw_5nWYrA',
@@ -1915,7 +1913,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'params': {'extract_flat': True},
}, {
# Live video status should be extracted
- # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
'url': 'https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/live',
'info_dict': {
'id': 'UCQvWX73GQygcwXOTSf_VDVg',