Compare commits
2 commits
3f4dcf2c4f
...
e3bbb0c061
Author | SHA1 | Date | |
---|---|---|---|
e3bbb0c061 | |||
ce33cfac9a |
13 changed files with 446 additions and 56 deletions
|
@ -1,24 +1,26 @@
|
||||||
import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
|
import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
|
||||||
import type { TooltipTriggerProps } from '@kobalte/core/tooltip'
|
import type { TooltipTriggerProps } from '@kobalte/core/tooltip'
|
||||||
import type { TelegramAccount } from 'mtcute-repl-worker/client'
|
import type { CustomApiFields, TelegramAccount } from 'mtcute-repl-worker/client'
|
||||||
import type { LoginStep } from './login/Login.tsx'
|
import type { LoginStep } from './login/Login.tsx'
|
||||||
import { timers, unknownToError } from '@fuman/utils'
|
import { timers, unknownToError } from '@fuman/utils'
|
||||||
import {
|
import {
|
||||||
LucideBot,
|
LucideBot,
|
||||||
|
LucideChevronDown,
|
||||||
LucideChevronRight,
|
LucideChevronRight,
|
||||||
LucideEllipsis,
|
LucideEllipsis,
|
||||||
|
LucideFlaskConical,
|
||||||
LucideFolderUp,
|
LucideFolderUp,
|
||||||
LucideLogIn,
|
LucideLogIn,
|
||||||
LucidePlus,
|
|
||||||
LucideRefreshCw,
|
LucideRefreshCw,
|
||||||
LucideSearch,
|
LucideSearch,
|
||||||
|
LucideServerCog,
|
||||||
LucideTrash,
|
LucideTrash,
|
||||||
LucideTriangleAlert,
|
LucideTriangleAlert,
|
||||||
LucideUser,
|
LucideUser,
|
||||||
LucideX,
|
LucideX,
|
||||||
} from 'lucide-solid'
|
} from 'lucide-solid'
|
||||||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
|
||||||
|
|
||||||
|
import { workerInvoke } from 'mtcute-repl-worker/client'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from 'solid-js'
|
||||||
import { toast } from 'solid-sonner'
|
import { toast } from 'solid-sonner'
|
||||||
|
@ -36,11 +38,13 @@ import { useStore } from '../../store/use-store.ts'
|
||||||
import { AccountAvatar } from '../AccountAvatar.tsx'
|
import { AccountAvatar } from '../AccountAvatar.tsx'
|
||||||
import { ImportDropdown } from './import/ImportDropdown.tsx'
|
import { ImportDropdown } from './import/ImportDropdown.tsx'
|
||||||
import { StringSessionDefs } from './import/StringSessionImportDialog.tsx'
|
import { StringSessionDefs } from './import/StringSessionImportDialog.tsx'
|
||||||
|
import { CustomApiDialog } from './login/CustomApiDialog.tsx'
|
||||||
import { LoginForm } from './login/Login.tsx'
|
import { LoginForm } from './login/Login.tsx'
|
||||||
|
|
||||||
function AddAccountDialog(props: {
|
function AddAccountDialog(props: {
|
||||||
show: boolean
|
show: boolean
|
||||||
testMode: boolean
|
testMode: boolean
|
||||||
|
apiOptions?: CustomApiFields
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) {
|
}) {
|
||||||
const [accountId, setAccountId] = createSignal<string | undefined>(undefined)
|
const [accountId, setAccountId] = createSignal<string | undefined>(undefined)
|
||||||
|
@ -81,6 +85,7 @@ function AddAccountDialog(props: {
|
||||||
await workerInvoke('telegram', 'createClient', {
|
await workerInvoke('telegram', 'createClient', {
|
||||||
accountId: accountId()!,
|
accountId: accountId()!,
|
||||||
testMode: props.testMode,
|
testMode: props.testMode,
|
||||||
|
apiOptions: props.apiOptions,
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -281,17 +286,71 @@ function AccountRow(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddAccountMode = 'test' | 'custom-api' | 'normal'
|
||||||
|
function LoginButton(props: {
|
||||||
|
size: 'xs' | 'sm'
|
||||||
|
onAddAccount: (mode: AddAccountMode) => void
|
||||||
|
}) {
|
||||||
|
const [showDropdown, setShowDropdown] = createSignal(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size={props.size}
|
||||||
|
onClick={() => props.onAddAccount('normal')}
|
||||||
|
class="rounded-r-none"
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu
|
||||||
|
open={showDropdown()}
|
||||||
|
onOpenChange={setShowDropdown}
|
||||||
|
>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size={props.size}
|
||||||
|
class="w-6 rounded-l-none border-l-0"
|
||||||
|
>
|
||||||
|
<LucideChevronDown class="size-3 shrink-0" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem class="py-1 text-xs" onClick={() => props.onAddAccount('test')}>
|
||||||
|
<LucideFlaskConical class="mr-2 size-3.5 stroke-[1.5px]" />
|
||||||
|
Use test server
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem class="py-1 text-xs" onClick={() => props.onAddAccount('custom-api')}>
|
||||||
|
<LucideServerCog class="mr-2 size-3.5 stroke-[1.5px]" />
|
||||||
|
Use custom API
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AccountsTab() {
|
export function AccountsTab() {
|
||||||
const accounts = useStore($accounts)
|
const accounts = useStore($accounts)
|
||||||
const activeAccountId = useStore($activeAccountId)
|
const activeAccountId = useStore($activeAccountId)
|
||||||
const [showAddAccount, setShowAddAccount] = createSignal(false)
|
const [showAddAccount, setShowAddAccount] = createSignal(false)
|
||||||
const [addAccountTestMode, setAddAccountTestMode] = createSignal(false)
|
const [addAccountTestMode, setAddAccountTestMode] = createSignal(false)
|
||||||
|
const [addAccountOptions, setAddAccountOptions] = createSignal<CustomApiFields>()
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = createSignal('')
|
const [searchQuery, setSearchQuery] = createSignal('')
|
||||||
|
|
||||||
function handleAddAccount(e: MouseEvent) {
|
const [showCustomApi, setShowCustomApi] = createSignal(false)
|
||||||
|
|
||||||
|
function handleAddAccount(mode: AddAccountMode) {
|
||||||
|
if (mode === 'custom-api') {
|
||||||
|
setShowCustomApi(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setShowAddAccount(true)
|
setShowAddAccount(true)
|
||||||
setAddAccountTestMode(e.ctrlKey || e.metaKey)
|
setAddAccountTestMode(mode === 'test')
|
||||||
|
setAddAccountOptions(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredAccounts = createMemo(() => {
|
const filteredAccounts = createMemo(() => {
|
||||||
|
@ -329,14 +388,7 @@ export function AccountsTab() {
|
||||||
|
|
||||||
No accounts yet
|
No accounts yet
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<Button
|
<LoginButton size="sm" onAddAccount={handleAddAccount} />
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleAddAccount}
|
|
||||||
>
|
|
||||||
<LucidePlus class="mr-2 size-4" />
|
|
||||||
Log in
|
|
||||||
</Button>
|
|
||||||
<ImportDropdown size="sm" />
|
<ImportDropdown size="sm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -363,15 +415,7 @@ export function AccountsTab() {
|
||||||
</TextFieldFrame>
|
</TextFieldFrame>
|
||||||
</TextFieldRoot>
|
</TextFieldRoot>
|
||||||
|
|
||||||
<Button
|
<LoginButton size="xs" onAddAccount={handleAddAccount} />
|
||||||
variant="outline"
|
|
||||||
size="xs"
|
|
||||||
onClick={handleAddAccount}
|
|
||||||
>
|
|
||||||
<LucidePlus class="mr-2 size-3" />
|
|
||||||
Log in
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<ImportDropdown size="xs" />
|
<ImportDropdown size="xs" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex max-w-full flex-col gap-1 overflow-hidden">
|
<div class="flex max-w-full flex-col gap-1 overflow-hidden">
|
||||||
|
@ -391,8 +435,19 @@ export function AccountsTab() {
|
||||||
<AddAccountDialog
|
<AddAccountDialog
|
||||||
show={showAddAccount()}
|
show={showAddAccount()}
|
||||||
testMode={addAccountTestMode()}
|
testMode={addAccountTestMode()}
|
||||||
|
apiOptions={addAccountOptions()}
|
||||||
onClose={() => setShowAddAccount(false)}
|
onClose={() => setShowAddAccount(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CustomApiDialog
|
||||||
|
visible={showCustomApi()}
|
||||||
|
setVisible={setShowCustomApi}
|
||||||
|
onSubmit={(options) => {
|
||||||
|
setShowCustomApi(false)
|
||||||
|
setAddAccountOptions(options)
|
||||||
|
setShowAddAccount(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
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 { Button } from '../../../lib/components/ui/button.tsx'
|
||||||
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
|
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.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 { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
|
||||||
|
import { CustomApiForm, useCustomApiFormState } from '../login/CustomApiDialog.tsx'
|
||||||
|
|
||||||
export function AuthKeyImportDialog(props: {
|
export function AuthKeyImportDialog(props: {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
@ -15,6 +17,9 @@ export function AuthKeyImportDialog(props: {
|
||||||
const [error, setError] = createSignal<string | undefined>()
|
const [error, setError] = createSignal<string | undefined>()
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const [useCustomApi, setUseCustomApi] = createSignal(false)
|
||||||
|
const [customApi, setCustomApi] = useCustomApiFormState()
|
||||||
|
|
||||||
let abortController: AbortController | undefined
|
let abortController: AbortController | undefined
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!['1', '2', '4', '5'].includes(dcId())) {
|
if (!['1', '2', '4', '5'].includes(dcId())) {
|
||||||
|
@ -32,6 +37,7 @@ export function AuthKeyImportDialog(props: {
|
||||||
dcId: Number(dcId()),
|
dcId: Number(dcId()),
|
||||||
testMode: testMode(),
|
testMode: testMode(),
|
||||||
abortSignal: abortController.signal,
|
abortSignal: abortController.signal,
|
||||||
|
apiOptions: useCustomApi() ? unwrap(customApi) : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
props.onClose()
|
props.onClose()
|
||||||
|
@ -66,7 +72,7 @@ export function AuthKeyImportDialog(props: {
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<TextFieldRoot>
|
<TextFieldRoot>
|
||||||
<TextFieldLabel class="text-foreground">
|
<TextFieldLabel>
|
||||||
Datacenter ID
|
Datacenter ID
|
||||||
</TextFieldLabel>
|
</TextFieldLabel>
|
||||||
<TextFieldFrame>
|
<TextFieldFrame>
|
||||||
|
@ -79,7 +85,7 @@ export function AuthKeyImportDialog(props: {
|
||||||
</TextFieldRoot>
|
</TextFieldRoot>
|
||||||
|
|
||||||
<TextFieldRoot class="mt-2" validationState={error() ? 'invalid' : 'valid'}>
|
<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
|
Hex-encoded auth key
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
@ -97,7 +103,7 @@ export function AuthKeyImportDialog(props: {
|
||||||
</TextFieldLabel>
|
</TextFieldLabel>
|
||||||
<TextFieldFrame class="h-auto">
|
<TextFieldFrame class="h-auto">
|
||||||
<TextField
|
<TextField
|
||||||
class="size-full h-40 resize-none font-mono"
|
class="size-full h-20 resize-none font-mono"
|
||||||
as="textarea"
|
as="textarea"
|
||||||
ref={setAuthKeyInputRef}
|
ref={setAuthKeyInputRef}
|
||||||
onInput={() => setError(undefined)}
|
onInput={() => setError(undefined)}
|
||||||
|
@ -114,11 +120,30 @@ export function AuthKeyImportDialog(props: {
|
||||||
onChange={setTestMode}
|
onChange={setTestMode}
|
||||||
>
|
>
|
||||||
<CheckboxControl />
|
<CheckboxControl />
|
||||||
<CheckboxLabel class="text-foreground">
|
<CheckboxLabel>
|
||||||
Use test servers
|
Use test servers
|
||||||
</CheckboxLabel>
|
</CheckboxLabel>
|
||||||
</Checkbox>
|
</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
|
<Button
|
||||||
class="mt-6 w-full"
|
class="mt-6 w-full"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { type StringSessionLibName, workerInvoke } from 'mtcute-repl-worker/client'
|
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 { 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 { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../lib/components/ui/select.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 { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
|
||||||
|
import { CustomApiForm, useCustomApiFormState } from '../login/CustomApiDialog.tsx'
|
||||||
|
|
||||||
export const StringSessionDefs: {
|
export const StringSessionDefs: {
|
||||||
name: StringSessionLibName
|
name: StringSessionLibName
|
||||||
|
@ -26,6 +29,9 @@ export function StringSessionImportDialog(props: {
|
||||||
const [error, setError] = createSignal<string | undefined>()
|
const [error, setError] = createSignal<string | undefined>()
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const [useCustomApi, setUseCustomApi] = createSignal(false)
|
||||||
|
const [customApi, setCustomApi] = useCustomApiFormState()
|
||||||
|
|
||||||
let abortController: AbortController | undefined
|
let abortController: AbortController | undefined
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
abortController?.abort()
|
abortController?.abort()
|
||||||
|
@ -37,6 +43,7 @@ export function StringSessionImportDialog(props: {
|
||||||
libraryName: props.chosenLibName,
|
libraryName: props.chosenLibName,
|
||||||
session: inputRef()!.value,
|
session: inputRef()!.value,
|
||||||
abortSignal: abortController.signal,
|
abortSignal: abortController.signal,
|
||||||
|
apiOptions: useCustomApi() ? unwrap(customApi) : undefined,
|
||||||
})
|
})
|
||||||
props.onClose()
|
props.onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -108,7 +115,7 @@ export function StringSessionImportDialog(props: {
|
||||||
</TextFieldLabel>
|
</TextFieldLabel>
|
||||||
<TextFieldFrame class="h-auto">
|
<TextFieldFrame class="h-auto">
|
||||||
<TextField
|
<TextField
|
||||||
class="size-full h-40 resize-none font-mono"
|
class="size-full h-20 resize-none font-mono"
|
||||||
as="textarea"
|
as="textarea"
|
||||||
ref={setInputRef}
|
ref={setInputRef}
|
||||||
onInput={() => setError(undefined)}
|
onInput={() => setError(undefined)}
|
||||||
|
@ -119,6 +126,25 @@ export function StringSessionImportDialog(props: {
|
||||||
</TextFieldErrorMessage>
|
</TextFieldErrorMessage>
|
||||||
</TextFieldRoot>
|
</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
|
<Button
|
||||||
class="mt-6 w-full"
|
class="mt-6 w-full"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
@ -2,10 +2,13 @@ import type { Tdata } from '@mtcute/convert'
|
||||||
import { hex } from '@fuman/utils'
|
import { hex } from '@fuman/utils'
|
||||||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
import { workerInvoke } from 'mtcute-repl-worker/client'
|
||||||
import { createEffect, createSignal, on, Show } 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 { 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 { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../../lib/components/ui/dialog.tsx'
|
||||||
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
|
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
|
||||||
import { $accounts } from '../../../../store/accounts.ts'
|
import { $accounts } from '../../../../store/accounts.ts'
|
||||||
|
import { CustomApiForm, useCustomApiFormState } from '../../login/CustomApiDialog.tsx'
|
||||||
import { TdataDataTable } from './TdataTable.tsx'
|
import { TdataDataTable } from './TdataTable.tsx'
|
||||||
|
|
||||||
interface TdataAccount {
|
interface TdataAccount {
|
||||||
|
@ -25,6 +28,9 @@ export function TdataImportDialog(props: {
|
||||||
const [error, setError] = createSignal<string | undefined>('')
|
const [error, setError] = createSignal<string | undefined>('')
|
||||||
const [loading, setLoading] = createSignal(false)
|
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)
|
const accountExists = (id: number) => $accounts.get()?.some(it => it.telegramId === id)
|
||||||
|
|
||||||
let abortController: AbortController | undefined
|
let abortController: AbortController | undefined
|
||||||
|
@ -43,6 +49,7 @@ export function TdataImportDialog(props: {
|
||||||
dcId: account.dcId,
|
dcId: account.dcId,
|
||||||
testMode: false,
|
testMode: false,
|
||||||
abortSignal: abortController.signal,
|
abortSignal: abortController.signal,
|
||||||
|
apiOptions: unwrap(customApi),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
|
@ -212,12 +219,31 @@ export function TdataImportDialog(props: {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{error() && (
|
{error() && (
|
||||||
<div class="text-error-foreground mt-2 text-sm">
|
<div class="mt-2 text-sm text-error-foreground">
|
||||||
{error()}
|
{error()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</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
|
<Button
|
||||||
class="mt-4 w-full"
|
class="mt-4 w-full"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
179
packages/repl/src/components/settings/login/CustomApiDialog.tsx
Normal file
179
packages/repl/src/components/settings/login/CustomApiDialog.tsx
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
import type { CustomApiFields } from 'mtcute-repl-worker/client'
|
||||||
|
import type { SetStoreFunction } from 'solid-js/store'
|
||||||
|
import { createSignal, Show } from 'solid-js'
|
||||||
|
import { createStore, 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, 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
|
||||||
|
return createStore<CustomApiFields>({
|
||||||
|
apiId: '',
|
||||||
|
apiHash: '',
|
||||||
|
deviceModel: '',
|
||||||
|
systemVersion: '',
|
||||||
|
appVersion: '',
|
||||||
|
systemLangCode: '',
|
||||||
|
langPack: '',
|
||||||
|
langCode: '',
|
||||||
|
extraJson: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CustomApiForm(props: {
|
||||||
|
class?: string
|
||||||
|
state: CustomApiFields
|
||||||
|
setState: SetStoreFunction<CustomApiFields>
|
||||||
|
}) {
|
||||||
|
const [showAdvanced, setShowAdvanced] = createSignal(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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()}
|
||||||
|
onChange={setShowAdvanced}
|
||||||
|
class="my-2 flex flex-row items-center gap-2 text-sm"
|
||||||
|
>
|
||||||
|
<CheckboxControl />
|
||||||
|
<CheckboxLabel>
|
||||||
|
Show advanced fields
|
||||||
|
</CheckboxLabel>
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
<Show when={showAdvanced()}>
|
||||||
|
<div class="flex w-full flex-row gap-2">
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>Device model</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="iPhone14,5"
|
||||||
|
value={props.state.deviceModel}
|
||||||
|
onInput={e => props.setState('deviceModel', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>Language pack</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="ios"
|
||||||
|
value={props.state.langPack}
|
||||||
|
onInput={e => props.setState('langPack', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full flex-row gap-2">
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>System version</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="15.4"
|
||||||
|
value={props.state.systemVersion}
|
||||||
|
onInput={e => props.setState('systemVersion', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>App version</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="4.0.1"
|
||||||
|
value={props.state.appVersion}
|
||||||
|
onInput={e => props.setState('appVersion', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full flex-row gap-2">
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>System language code</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="en"
|
||||||
|
value={props.state.systemLangCode}
|
||||||
|
onInput={e => props.setState('systemLangCode', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>Language code</TextFieldLabel>
|
||||||
|
<TextFieldFrame>
|
||||||
|
<TextField
|
||||||
|
placeholder="en"
|
||||||
|
value={props.state.langCode}
|
||||||
|
onInput={e => props.setState('langCode', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
</div>
|
||||||
|
<TextFieldRoot class="w-full">
|
||||||
|
<TextFieldLabel>Extra options (JSON)</TextFieldLabel>
|
||||||
|
<TextFieldFrame class="h-auto">
|
||||||
|
<TextField
|
||||||
|
as="textarea"
|
||||||
|
class="h-20 resize-none font-mono"
|
||||||
|
placeholder={'{"tz_offset": 3600}'}
|
||||||
|
value={props.state.extraJson}
|
||||||
|
onInput={e => props.setState('extraJson', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</TextFieldFrame>
|
||||||
|
</TextFieldRoot>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CustomApiDialog(props: {
|
||||||
|
visible: boolean
|
||||||
|
setVisible: (visible: boolean) => void
|
||||||
|
onSubmit: (options: CustomApiFields) => void
|
||||||
|
}) {
|
||||||
|
const [state, setState] = useCustomApiFormState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={props.visible} onOpenChange={props.setVisible}>
|
||||||
|
<DialogContent class="flex flex-col gap-2">
|
||||||
|
<DialogHeader class="font-medium">
|
||||||
|
Custom connection options
|
||||||
|
</DialogHeader>
|
||||||
|
<CustomApiForm state={state} setState={setState} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={!state.apiId || !state.apiHash}
|
||||||
|
onClick={() => props.onSubmit(unwrap(state))}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { unknownToError } from '@fuman/utils'
|
||||||
import { LucideChevronRight, LucideLockKeyhole, MessageSquareMore } from 'lucide-solid'
|
import { LucideChevronRight, LucideLockKeyhole, MessageSquareMore } from 'lucide-solid'
|
||||||
import { workerInvoke, workerOn } from 'mtcute-repl-worker/client'
|
import { workerInvoke, workerOn } from 'mtcute-repl-worker/client'
|
||||||
import { createSignal, For, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
import { createSignal, For, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
||||||
|
import { toast } from 'solid-sonner'
|
||||||
import { Button } from '../../../lib/components/ui/button.tsx'
|
import { Button } from '../../../lib/components/ui/button.tsx'
|
||||||
import { OTPField, OTPFieldGroup, OTPFieldInput, OTPFieldSlot } from '../../../lib/components/ui/otp-field.tsx'
|
import { OTPField, OTPFieldGroup, OTPFieldInput, OTPFieldSlot } from '../../../lib/components/ui/otp-field.tsx'
|
||||||
import { Spinner } from '../../../lib/components/ui/spinner.tsx'
|
import { Spinner } from '../../../lib/components/ui/spinner.tsx'
|
||||||
|
@ -55,6 +56,7 @@ function QrLoginStep(props: StepProps<'qr'>) {
|
||||||
cleanup2()
|
cleanup2()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
const result = await workerInvoke('telegram', 'signInQr', {
|
const result = await workerInvoke('telegram', 'signInQr', {
|
||||||
accountId: props.accountId,
|
accountId: props.accountId,
|
||||||
abortSignal: abortController.signal,
|
abortSignal: abortController.signal,
|
||||||
|
@ -64,6 +66,9 @@ function QrLoginStep(props: StepProps<'qr'>) {
|
||||||
} else {
|
} else {
|
||||||
props.setStep('done', { account: result })
|
props.setStep('done', { account: result })
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(unknownToError(e).message)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
onCleanup(() => abortController.abort())
|
onCleanup(() => abortController.abort())
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
import type { CheckboxControlProps } from '@kobalte/core/checkbox'
|
import type { CheckboxControlProps } from '@kobalte/core/checkbox'
|
||||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic'
|
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 { Checkbox as CheckboxPrimitive } from '@kobalte/core/checkbox'
|
||||||
import { splitProps } from 'solid-js'
|
import { splitProps } from 'solid-js'
|
||||||
import { cn } from '../../utils.ts'
|
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 Checkbox = CheckboxPrimitive
|
||||||
export const CheckboxErrorMessage = CheckboxPrimitive.ErrorMessage
|
export const CheckboxErrorMessage = CheckboxPrimitive.ErrorMessage
|
||||||
export const CheckboxDescription = CheckboxPrimitive.Description
|
export const CheckboxDescription = CheckboxPrimitive.Description
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function DialogContent<T extends ValidComponent = 'div'>(props: Polymorph
|
||||||
/>
|
/>
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
class={cn(
|
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,
|
local.class,
|
||||||
)}
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function TextFieldRoot<T extends ValidComponent = 'div'>(props: Polymorph
|
||||||
}
|
}
|
||||||
|
|
||||||
export const textfieldLabel = cva(
|
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: {
|
variants: {
|
||||||
label: {
|
label: {
|
||||||
|
@ -131,7 +131,7 @@ export function TextField<T extends ValidComponent = 'input'>(props: Polymorphic
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextFieldPrimitive.Input
|
<TextFieldPrimitive.Input
|
||||||
class={cn('border-none outline-none placeholder:text-muted-foreground bg-transparent', local.class)}
|
class={cn('border-none outline-none placeholder:text-muted-foreground bg-transparent min-w-0 w-full', local.class)}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { ReplWorker } from './worker/main.ts'
|
||||||
import type { ReplWorkerEvents } from './worker/utils.ts'
|
import type { ReplWorkerEvents } from './worker/utils.ts'
|
||||||
import { Deferred, unknownToError } from '@fuman/utils'
|
import { Deferred, unknownToError } from '@fuman/utils'
|
||||||
|
|
||||||
export type { TelegramAccount } from './store/accounts.ts'
|
export type { CustomApiFields, TelegramAccount } from './store/accounts.ts'
|
||||||
export type { StringSessionLibName } from './worker/telegram.ts'
|
export type { StringSessionLibName } from './worker/telegram.ts'
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-namespace
|
// eslint-disable-next-line ts/no-namespace
|
||||||
|
|
|
@ -9,6 +9,19 @@ export interface TelegramAccount {
|
||||||
testMode: boolean
|
testMode: boolean
|
||||||
telegramId: number
|
telegramId: number
|
||||||
dcId: number
|
dcId: number
|
||||||
|
apiOptions?: CustomApiFields
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomApiFields {
|
||||||
|
apiId: string
|
||||||
|
apiHash: string
|
||||||
|
deviceModel: string
|
||||||
|
systemVersion: string
|
||||||
|
appVersion: string
|
||||||
|
systemLangCode: string
|
||||||
|
langPack: string
|
||||||
|
langCode: string
|
||||||
|
extraJson: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountSchema = v.object({
|
const AccountSchema = v.object({
|
||||||
|
@ -19,6 +32,17 @@ const AccountSchema = v.object({
|
||||||
name: v.string(),
|
name: v.string(),
|
||||||
testMode: v.boolean(),
|
testMode: v.boolean(),
|
||||||
dcId: v.number(),
|
dcId: v.number(),
|
||||||
|
apiOptions: v.object({
|
||||||
|
apiId: v.string(),
|
||||||
|
apiHash: v.string(),
|
||||||
|
deviceModel: v.string(),
|
||||||
|
systemVersion: v.string(),
|
||||||
|
appVersion: v.string(),
|
||||||
|
systemLangCode: v.string(),
|
||||||
|
langPack: v.string(),
|
||||||
|
langCode: v.string(),
|
||||||
|
extraJson: v.string(),
|
||||||
|
}).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const $accounts = persistentAtom<TelegramAccount[]>('repl:accounts', [], {
|
export const $accounts = persistentAtom<TelegramAccount[]>('repl:accounts', [], {
|
||||||
|
|
|
@ -1,17 +1,49 @@
|
||||||
import type { InputStringSessionData } from '@mtcute/web/utils.js'
|
import type { tl } from '@mtcute/web'
|
||||||
import type { TelegramAccount } from '../store/accounts.ts'
|
import type { CustomApiFields, TelegramAccount } from '../store/accounts.ts'
|
||||||
import { asNonNull } from '@fuman/utils'
|
import { asNonNull } from '@fuman/utils'
|
||||||
import { BaseTelegramClient, IdbStorage, TransportError } from '@mtcute/web'
|
import { BaseTelegramClient, IdbStorage, TransportError } from '@mtcute/web'
|
||||||
import { getMe } from '@mtcute/web/methods.js'
|
import { getMe } from '@mtcute/web/methods.js'
|
||||||
|
import { type InputStringSessionData, jsonToTlJson } from '@mtcute/web/utils.js'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
export function createInternalClient(accountId: string, testMode?: boolean) {
|
export function createInternalClient(
|
||||||
|
accountId: string,
|
||||||
|
testMode?: boolean,
|
||||||
|
apiOptions?: CustomApiFields,
|
||||||
|
) {
|
||||||
|
let initConnectionOptions: Partial<tl.RawInitConnectionRequest> | undefined
|
||||||
|
if (apiOptions) {
|
||||||
|
initConnectionOptions = {}
|
||||||
|
if (apiOptions.deviceModel) {
|
||||||
|
initConnectionOptions.deviceModel = apiOptions.deviceModel
|
||||||
|
}
|
||||||
|
if (apiOptions.systemVersion) {
|
||||||
|
initConnectionOptions.systemVersion = apiOptions.systemVersion
|
||||||
|
}
|
||||||
|
if (apiOptions.appVersion) {
|
||||||
|
initConnectionOptions.appVersion = apiOptions.appVersion
|
||||||
|
}
|
||||||
|
if (apiOptions.langCode) {
|
||||||
|
initConnectionOptions.langCode = apiOptions.langCode
|
||||||
|
}
|
||||||
|
if (apiOptions.langPack) {
|
||||||
|
initConnectionOptions.langPack = apiOptions.langPack
|
||||||
|
}
|
||||||
|
if (apiOptions.systemLangCode) {
|
||||||
|
initConnectionOptions.systemLangCode = apiOptions.systemLangCode
|
||||||
|
}
|
||||||
|
if (apiOptions.extraJson) {
|
||||||
|
initConnectionOptions.params = jsonToTlJson(JSON.parse(apiOptions.extraJson))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new BaseTelegramClient({
|
return new BaseTelegramClient({
|
||||||
apiId: Number(import.meta.env.VITE_API_ID),
|
apiId: Number(apiOptions?.apiId ?? import.meta.env.VITE_API_ID),
|
||||||
apiHash: import.meta.env.VITE_API_HASH,
|
apiHash: apiOptions?.apiHash ?? import.meta.env.VITE_API_HASH,
|
||||||
storage: new IdbStorage(`mtcute:${accountId}`),
|
storage: new IdbStorage(`mtcute:${accountId}`),
|
||||||
testMode,
|
testMode,
|
||||||
logLevel: import.meta.env.DEV ? 5 : 2,
|
logLevel: import.meta.env.DEV ? 5 : 2,
|
||||||
|
initConnectionOptions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +58,10 @@ export async function deleteAccount(accountId: string) {
|
||||||
export async function importAccount(
|
export async function importAccount(
|
||||||
session: InputStringSessionData,
|
session: InputStringSessionData,
|
||||||
abortSignal: AbortSignal,
|
abortSignal: AbortSignal,
|
||||||
|
apiOptions?: CustomApiFields,
|
||||||
): Promise<TelegramAccount> {
|
): Promise<TelegramAccount> {
|
||||||
const accountId = nanoid()
|
const accountId = nanoid()
|
||||||
const client = createInternalClient(accountId, session.primaryDcs?.main.testMode)
|
const client = createInternalClient(accountId, session.primaryDcs?.main.testMode, apiOptions)
|
||||||
|
|
||||||
let is404 = false
|
let is404 = false
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { BaseTelegramClient, SentCode, User } from '@mtcute/web'
|
import type { BaseTelegramClient, SentCode, User } from '@mtcute/web'
|
||||||
import type { StringSessionData } from '@mtcute/web/utils.js'
|
import type { StringSessionData } from '@mtcute/web/utils.js'
|
||||||
import type { TelegramAccount } from '../store/accounts.ts'
|
import type { CustomApiFields, TelegramAccount } from '../store/accounts.ts'
|
||||||
import { assert, hex } from '@fuman/utils'
|
import { assert, hex } from '@fuman/utils'
|
||||||
import { DC_MAPPING_PROD, DC_MAPPING_TEST } from '@mtcute/convert'
|
import { DC_MAPPING_PROD, DC_MAPPING_TEST } from '@mtcute/convert'
|
||||||
import { tl } from '@mtcute/web'
|
import { tl } from '@mtcute/web'
|
||||||
|
@ -31,7 +31,9 @@ function getClient(accountId: string) {
|
||||||
function getTmpClient(accountId: string): [BaseTelegramClient, () => Promise<void>] {
|
function getTmpClient(accountId: string): [BaseTelegramClient, () => Promise<void>] {
|
||||||
const client = clients.get(accountId)
|
const client = clients.get(accountId)
|
||||||
if (!client) {
|
if (!client) {
|
||||||
const tmpClient = createInternalClient(accountId)
|
const accountInfo = $accounts.get().find(it => it.id === accountId)
|
||||||
|
if (!accountInfo) throw new Error('Account not found')
|
||||||
|
const tmpClient = createInternalClient(accountId, accountInfo.testMode, accountInfo.apiOptions)
|
||||||
return [tmpClient, () => tmpClient.close()]
|
return [tmpClient, () => tmpClient.close()]
|
||||||
} else {
|
} else {
|
||||||
return [client, () => Promise.resolve()]
|
return [client, () => Promise.resolve()]
|
||||||
|
@ -73,8 +75,9 @@ export class ReplWorkerTelegram {
|
||||||
async createClient(params: {
|
async createClient(params: {
|
||||||
accountId: string
|
accountId: string
|
||||||
testMode?: boolean
|
testMode?: boolean
|
||||||
|
apiOptions?: CustomApiFields
|
||||||
}) {
|
}) {
|
||||||
const client = createInternalClient(params.accountId, params.testMode)
|
const client = createInternalClient(params.accountId, params.testMode, params.apiOptions)
|
||||||
clients.set(params.accountId, client)
|
clients.set(params.accountId, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,8 +235,9 @@ export class ReplWorkerTelegram {
|
||||||
dcId: number
|
dcId: number
|
||||||
testMode: boolean
|
testMode: boolean
|
||||||
abortSignal: AbortSignal
|
abortSignal: AbortSignal
|
||||||
|
apiOptions?: CustomApiFields
|
||||||
}) {
|
}) {
|
||||||
const { hexAuthKey, dcId, testMode, abortSignal } = params
|
const { hexAuthKey, dcId, testMode, abortSignal, apiOptions } = params
|
||||||
|
|
||||||
const authKey = hex.decode(hexAuthKey)
|
const authKey = hex.decode(hexAuthKey)
|
||||||
if (authKey.length !== 256) {
|
if (authKey.length !== 256) {
|
||||||
|
@ -244,7 +248,7 @@ export class ReplWorkerTelegram {
|
||||||
authKey,
|
authKey,
|
||||||
testMode,
|
testMode,
|
||||||
primaryDcs: (testMode ? DC_MAPPING_TEST : DC_MAPPING_PROD)[dcId],
|
primaryDcs: (testMode ? DC_MAPPING_TEST : DC_MAPPING_PROD)[dcId],
|
||||||
}, abortSignal)
|
}, abortSignal, apiOptions)
|
||||||
|
|
||||||
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
||||||
await deleteAccount(account.id)
|
await deleteAccount(account.id)
|
||||||
|
@ -264,6 +268,7 @@ export class ReplWorkerTelegram {
|
||||||
libraryName: StringSessionLibName
|
libraryName: StringSessionLibName
|
||||||
session: string
|
session: string
|
||||||
abortSignal: AbortSignal
|
abortSignal: AbortSignal
|
||||||
|
apiOptions?: CustomApiFields
|
||||||
}) {
|
}) {
|
||||||
let session: StringSessionData
|
let session: StringSessionData
|
||||||
switch (params.libraryName) {
|
switch (params.libraryName) {
|
||||||
|
@ -297,7 +302,7 @@ export class ReplWorkerTelegram {
|
||||||
throw new Error(`Account already exists (user ID: ${session.self.userId})`)
|
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
|
// check if account already exists once again
|
||||||
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
if ($accounts.get().some(it => it.telegramId === account.telegramId)) {
|
||||||
|
|
Loading…
Reference in a new issue