feat: support custom api id/hash in imported sessions
All checks were successful
Docs / build (push) Successful in 2m29s
All checks were successful
Docs / build (push) Successful in 2m29s
This commit is contained in:
parent
ce33cfac9a
commit
e3bbb0c061
9 changed files with 132 additions and 37 deletions
|
@ -1,9 +1,11 @@
|
|||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
||||
import { createEffect, createSignal, on } from 'solid-js'
|
||||
import { createEffect, createSignal, on, Show } from 'solid-js'
|
||||
import { unwrap } from 'solid-js/store'
|
||||
import { Button } from '../../../lib/components/ui/button.tsx'
|
||||
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
|
||||
import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
|
||||
import { CustomApiForm, useCustomApiFormState } from '../login/CustomApiDialog.tsx'
|
||||
|
||||
export function AuthKeyImportDialog(props: {
|
||||
open: boolean
|
||||
|
@ -15,6 +17,9 @@ export function AuthKeyImportDialog(props: {
|
|||
const [error, setError] = createSignal<string | undefined>()
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
|
||||
const [useCustomApi, setUseCustomApi] = createSignal(false)
|
||||
const [customApi, setCustomApi] = useCustomApiFormState()
|
||||
|
||||
let abortController: AbortController | undefined
|
||||
const handleSubmit = async () => {
|
||||
if (!['1', '2', '4', '5'].includes(dcId())) {
|
||||
|
@ -32,6 +37,7 @@ export function AuthKeyImportDialog(props: {
|
|||
dcId: Number(dcId()),
|
||||
testMode: testMode(),
|
||||
abortSignal: abortController.signal,
|
||||
apiOptions: useCustomApi() ? unwrap(customApi) : undefined,
|
||||
})
|
||||
|
||||
props.onClose()
|
||||
|
@ -66,7 +72,7 @@ export function AuthKeyImportDialog(props: {
|
|||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<TextFieldRoot>
|
||||
<TextFieldLabel class="text-foreground">
|
||||
<TextFieldLabel>
|
||||
Datacenter ID
|
||||
</TextFieldLabel>
|
||||
<TextFieldFrame>
|
||||
|
@ -79,7 +85,7 @@ export function AuthKeyImportDialog(props: {
|
|||
</TextFieldRoot>
|
||||
|
||||
<TextFieldRoot class="mt-2" validationState={error() ? 'invalid' : 'valid'}>
|
||||
<TextFieldLabel class="flex flex-row items-center justify-between text-foreground">
|
||||
<TextFieldLabel class="flex flex-row items-center justify-between">
|
||||
Hex-encoded auth key
|
||||
<a
|
||||
href="#"
|
||||
|
@ -97,7 +103,7 @@ export function AuthKeyImportDialog(props: {
|
|||
</TextFieldLabel>
|
||||
<TextFieldFrame class="h-auto">
|
||||
<TextField
|
||||
class="size-full h-40 resize-none font-mono"
|
||||
class="size-full h-20 resize-none font-mono"
|
||||
as="textarea"
|
||||
ref={setAuthKeyInputRef}
|
||||
onInput={() => setError(undefined)}
|
||||
|
@ -114,11 +120,30 @@ export function AuthKeyImportDialog(props: {
|
|||
onChange={setTestMode}
|
||||
>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel class="text-foreground">
|
||||
<CheckboxLabel>
|
||||
Use test servers
|
||||
</CheckboxLabel>
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox
|
||||
class="mt-2 flex flex-row items-center gap-2"
|
||||
checked={useCustomApi()}
|
||||
onChange={setUseCustomApi}
|
||||
>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel>
|
||||
Use custom connection options
|
||||
</CheckboxLabel>
|
||||
</Checkbox>
|
||||
|
||||
<Show when={useCustomApi()}>
|
||||
<CustomApiForm
|
||||
class="mt-2"
|
||||
state={customApi}
|
||||
setState={setCustomApi}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
class="mt-6 w-full"
|
||||
size="sm"
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { type StringSessionLibName, workerInvoke } from 'mtcute-repl-worker/client'
|
||||
import { createEffect, createSignal, on } from 'solid-js'
|
||||
import { createEffect, createSignal, on, Show } from 'solid-js'
|
||||
import { unwrap } from 'solid-js/store'
|
||||
import { Button } from '../../../lib/components/ui/button.tsx'
|
||||
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../lib/components/ui/select.tsx'
|
||||
import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
|
||||
import { CustomApiForm, useCustomApiFormState } from '../login/CustomApiDialog.tsx'
|
||||
|
||||
export const StringSessionDefs: {
|
||||
name: StringSessionLibName
|
||||
|
@ -26,6 +29,9 @@ export function StringSessionImportDialog(props: {
|
|||
const [error, setError] = createSignal<string | undefined>()
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
|
||||
const [useCustomApi, setUseCustomApi] = createSignal(false)
|
||||
const [customApi, setCustomApi] = useCustomApiFormState()
|
||||
|
||||
let abortController: AbortController | undefined
|
||||
const handleSubmit = async () => {
|
||||
abortController?.abort()
|
||||
|
@ -37,6 +43,7 @@ export function StringSessionImportDialog(props: {
|
|||
libraryName: props.chosenLibName,
|
||||
session: inputRef()!.value,
|
||||
abortSignal: abortController.signal,
|
||||
apiOptions: useCustomApi() ? unwrap(customApi) : undefined,
|
||||
})
|
||||
props.onClose()
|
||||
} catch (e) {
|
||||
|
@ -108,7 +115,7 @@ export function StringSessionImportDialog(props: {
|
|||
</TextFieldLabel>
|
||||
<TextFieldFrame class="h-auto">
|
||||
<TextField
|
||||
class="size-full h-40 resize-none font-mono"
|
||||
class="size-full h-20 resize-none font-mono"
|
||||
as="textarea"
|
||||
ref={setInputRef}
|
||||
onInput={() => setError(undefined)}
|
||||
|
@ -119,6 +126,25 @@ export function StringSessionImportDialog(props: {
|
|||
</TextFieldErrorMessage>
|
||||
</TextFieldRoot>
|
||||
|
||||
<Checkbox
|
||||
class="mt-4 flex flex-row items-center gap-2"
|
||||
checked={useCustomApi()}
|
||||
onChange={setUseCustomApi}
|
||||
>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel>
|
||||
Use custom connection options
|
||||
</CheckboxLabel>
|
||||
</Checkbox>
|
||||
|
||||
<Show when={useCustomApi()}>
|
||||
<CustomApiForm
|
||||
class="mt-2"
|
||||
state={customApi}
|
||||
setState={setCustomApi}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
class="mt-6 w-full"
|
||||
size="sm"
|
||||
|
|
|
@ -2,10 +2,13 @@ import type { Tdata } from '@mtcute/convert'
|
|||
import { hex } from '@fuman/utils'
|
||||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
||||
import { createEffect, createSignal, on, Show } from 'solid-js'
|
||||
import { unwrap } from 'solid-js/store'
|
||||
import { Button } from '../../../../lib/components/ui/button.tsx'
|
||||
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../../lib/components/ui/checkbox.tsx'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../../lib/components/ui/dialog.tsx'
|
||||
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
|
||||
import { $accounts } from '../../../../store/accounts.ts'
|
||||
import { CustomApiForm, useCustomApiFormState } from '../../login/CustomApiDialog.tsx'
|
||||
import { TdataDataTable } from './TdataTable.tsx'
|
||||
|
||||
interface TdataAccount {
|
||||
|
@ -25,6 +28,9 @@ export function TdataImportDialog(props: {
|
|||
const [error, setError] = createSignal<string | undefined>('')
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
|
||||
const [useCustomApi, setUseCustomApi] = createSignal(false)
|
||||
const [customApi, setCustomApi] = useCustomApiFormState()
|
||||
|
||||
const accountExists = (id: number) => $accounts.get()?.some(it => it.telegramId === id)
|
||||
|
||||
let abortController: AbortController | undefined
|
||||
|
@ -43,6 +49,7 @@ export function TdataImportDialog(props: {
|
|||
dcId: account.dcId,
|
||||
testMode: false,
|
||||
abortSignal: abortController.signal,
|
||||
apiOptions: unwrap(customApi),
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
|
@ -212,12 +219,31 @@ export function TdataImportDialog(props: {
|
|||
}}
|
||||
/>
|
||||
{error() && (
|
||||
<div class="text-error-foreground mt-2 text-sm">
|
||||
<div class="mt-2 text-sm text-error-foreground">
|
||||
{error()}
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Checkbox
|
||||
class="mt-2 flex flex-row items-center gap-2"
|
||||
checked={useCustomApi()}
|
||||
onChange={setUseCustomApi}
|
||||
>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel>
|
||||
Use custom connection options
|
||||
</CheckboxLabel>
|
||||
</Checkbox>
|
||||
|
||||
<Show when={useCustomApi()}>
|
||||
<CustomApiForm
|
||||
class="mt-2"
|
||||
state={customApi}
|
||||
setState={setCustomApi}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
class="mt-4 w-full"
|
||||
size="sm"
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Button } from '../../../lib/components/ui/button.tsx'
|
|||
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
|
||||
import { Dialog, DialogContent, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
|
||||
import { TextField, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
|
||||
import { cn } from '../../../lib/utils.ts'
|
||||
|
||||
export function useCustomApiFormState() {
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
|
@ -30,27 +31,29 @@ export function CustomApiForm(props: {
|
|||
const [showAdvanced, setShowAdvanced] = createSignal(false)
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
<TextFieldRoot>
|
||||
<TextFieldLabel>API ID</TextFieldLabel>
|
||||
<TextFieldFrame>
|
||||
<TextField
|
||||
placeholder="2040"
|
||||
value={props.state.apiId}
|
||||
onInput={e => props.setState('apiId', e.currentTarget.value.replace(/\D/g, ''))}
|
||||
/>
|
||||
</TextFieldFrame>
|
||||
</TextFieldRoot>
|
||||
<TextFieldRoot>
|
||||
<TextFieldLabel>API Hash</TextFieldLabel>
|
||||
<TextFieldFrame>
|
||||
<TextField
|
||||
placeholder="b18441..."
|
||||
value={props.state.apiHash}
|
||||
onInput={e => props.setState('apiHash', e.currentTarget.value)}
|
||||
/>
|
||||
</TextFieldFrame>
|
||||
</TextFieldRoot>
|
||||
<div class={cn('flex flex-col gap-2', props.class)}>
|
||||
<div class="flex flex-row gap-2">
|
||||
<TextFieldRoot class="flex-1">
|
||||
<TextFieldLabel>API ID</TextFieldLabel>
|
||||
<TextFieldFrame>
|
||||
<TextField
|
||||
placeholder="2040"
|
||||
value={props.state.apiId}
|
||||
onInput={e => props.setState('apiId', e.currentTarget.value.replace(/\D/g, ''))}
|
||||
/>
|
||||
</TextFieldFrame>
|
||||
</TextFieldRoot>
|
||||
<TextFieldRoot class="flex-[2]">
|
||||
<TextFieldLabel>API Hash</TextFieldLabel>
|
||||
<TextFieldFrame>
|
||||
<TextField
|
||||
placeholder="b18441..."
|
||||
value={props.state.apiHash}
|
||||
onInput={e => props.setState('apiHash', e.currentTarget.value)}
|
||||
/>
|
||||
</TextFieldFrame>
|
||||
</TextFieldRoot>
|
||||
</div>
|
||||
|
||||
<Checkbox
|
||||
checked={showAdvanced()}
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import type { CheckboxControlProps } from '@kobalte/core/checkbox'
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic'
|
||||
import type { ValidComponent, VoidProps } from 'solid-js'
|
||||
import type { ComponentProps, ValidComponent, VoidProps } from 'solid-js'
|
||||
import { Checkbox as CheckboxPrimitive } from '@kobalte/core/checkbox'
|
||||
import { splitProps } from 'solid-js'
|
||||
import { cn } from '../../utils.ts'
|
||||
|
||||
export const CheckboxLabel = CheckboxPrimitive.Label
|
||||
export function CheckboxLabel(props: ComponentProps<typeof CheckboxPrimitive.Label>) {
|
||||
const [local, others] = splitProps(props, ['class'])
|
||||
return (
|
||||
<CheckboxPrimitive.Label
|
||||
class={cn(
|
||||
'text-foreground',
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const Checkbox = CheckboxPrimitive
|
||||
export const CheckboxErrorMessage = CheckboxPrimitive.ErrorMessage
|
||||
export const CheckboxDescription = CheckboxPrimitive.Description
|
||||
|
|
|
@ -38,7 +38,7 @@ export function DialogContent<T extends ValidComponent = 'div'>(props: Polymorph
|
|||
/>
|
||||
<DialogPrimitive.Content
|
||||
class={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg data-[closed]:duration-200 data-[expanded]:duration-200 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] max-h-screen overflow-auto gap-4 border bg-background p-6 shadow-lg data-[closed]:duration-200 data-[expanded]:duration-200 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function TextFieldRoot<T extends ValidComponent = 'div'>(props: Polymorph
|
|||
}
|
||||
|
||||
export const textfieldLabel = cva(
|
||||
'text-sm font-medium data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70',
|
||||
'text-sm font-medium text-foreground data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70',
|
||||
{
|
||||
variants: {
|
||||
label: {
|
||||
|
|
|
@ -58,9 +58,10 @@ export async function deleteAccount(accountId: string) {
|
|||
export async function importAccount(
|
||||
session: InputStringSessionData,
|
||||
abortSignal: AbortSignal,
|
||||
apiOptions?: CustomApiFields,
|
||||
): Promise<TelegramAccount> {
|
||||
const accountId = nanoid()
|
||||
const client = createInternalClient(accountId, session.primaryDcs?.main.testMode)
|
||||
const client = createInternalClient(accountId, session.primaryDcs?.main.testMode, apiOptions)
|
||||
|
||||
let is404 = false
|
||||
|
||||
|
|
|
@ -235,8 +235,9 @@ export class ReplWorkerTelegram {
|
|||
dcId: number
|
||||
testMode: boolean
|
||||
abortSignal: AbortSignal
|
||||
apiOptions?: CustomApiFields
|
||||
}) {
|
||||
const { hexAuthKey, dcId, testMode, abortSignal } = params
|
||||
const { hexAuthKey, dcId, testMode, abortSignal, apiOptions } = params
|
||||
|
||||
const authKey = hex.decode(hexAuthKey)
|
||||
if (authKey.length !== 256) {
|
||||
|
@ -247,7 +248,7 @@ export class ReplWorkerTelegram {
|
|||
authKey,
|
||||
testMode,
|
||||
primaryDcs: (testMode ? DC_MAPPING_TEST : DC_MAPPING_PROD)[dcId],
|
||||
}, abortSignal)
|
||||
}, abortSignal, apiOptions)
|
||||
|
||||
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
||||
await deleteAccount(account.id)
|
||||
|
@ -267,6 +268,7 @@ export class ReplWorkerTelegram {
|
|||
libraryName: StringSessionLibName
|
||||
session: string
|
||||
abortSignal: AbortSignal
|
||||
apiOptions?: CustomApiFields
|
||||
}) {
|
||||
let session: StringSessionData
|
||||
switch (params.libraryName) {
|
||||
|
@ -300,7 +302,7 @@ export class ReplWorkerTelegram {
|
|||
throw new Error(`Account already exists (user ID: ${session.self.userId})`)
|
||||
}
|
||||
|
||||
const account = await importAccount(session, params.abortSignal)
|
||||
const account = await importAccount(session, params.abortSignal, params.apiOptions)
|
||||
|
||||
// check if account already exists once again
|
||||
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
||||
|
|
Loading…
Reference in a new issue