Skip to content

Commit 9e1bc1b

Browse files
committed
feat: get user info
1 parent 496804e commit 9e1bc1b

File tree

11 files changed

+230
-17
lines changed

11 files changed

+230
-17
lines changed

packages/core/src/adapter/client/adapter.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,38 @@ export class ClientAdapter implements ITelegramClientAdapter {
143143
}
144144
}
145145

146-
async getUserInfo(userId: string): Promise<Api.users.UserFull> {
147-
return await this.connectionManager.getClient().invoke(new Api.users.GetFullUser({
148-
id: userId,
146+
async getMeInfo(): Promise<Api.User> {
147+
return this.connectionManager.getMe()
148+
}
149+
150+
async getUserInfo(userId: string): Promise<Api.User> {
151+
const users = await this.connectionManager.getClient().invoke(new Api.users.GetUsers({
152+
id: [userId],
149153
}))
154+
155+
const user = users[0]
156+
157+
// Filter out empty user results
158+
if (!user || user instanceof Api.UserEmpty) {
159+
throw new Error(`User ${userId} not found`)
160+
}
161+
162+
return user
150163
}
151164

152-
async getUsersInfo(userIds: string[]): Promise<Api.TypeUser[]> {
153-
return await this.connectionManager.getClient().invoke(new Api.users.GetUsers({
165+
async getUsersInfo(userIds: string[]): Promise<Api.User[]> {
166+
const users = await this.connectionManager.getClient().invoke(new Api.users.GetUsers({
154167
id: userIds,
155168
}))
169+
170+
// Filter out empty or invalid user results
171+
const validUsers = users.filter(user => user && !(user instanceof Api.UserEmpty))
172+
173+
if (validUsers.length === 0) {
174+
throw new Error('No valid users found')
175+
}
176+
177+
return validUsers as Api.User[]
156178
}
157179

158180
async getHistory(chatId: number): Promise<Api.messages.TypeMessages & { count: number }> {

packages/core/src/adapter/client/connection-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export class ConnectionManager {
4141
return this.client
4242
}
4343

44+
public async getMe(): Promise<Api.User> {
45+
return this.client.getMe()
46+
}
47+
4448
/**
4549
* Check if the client is connected and authorized
4650
*/

packages/core/src/types/adapter.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,22 @@ export interface ITelegramClientAdapter extends BaseTelegramAdapter {
6969
* Check if the client is connected
7070
*/
7171
isConnected: () => Promise<boolean>
72-
getUserInfo: (userId: string) => Promise<Api.users.UserFull>
73-
getUsersInfo: (userIds: string[]) => Promise<Api.TypeUser[]>
72+
73+
/**
74+
* Get me info
75+
*/
76+
getMeInfo: () => Promise<Api.User>
77+
78+
/**
79+
* Get user info
80+
*/
81+
getUserInfo: (userId: string) => Promise<Api.User>
82+
83+
/**
84+
* Get users info
85+
*/
86+
getUsersInfo: (userIds: string[]) => Promise<Api.User[]>
87+
7488
/**
7589
* Send verification code
7690
*/

packages/frontend/src/apis/useAuth.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SuccessResponse } from '@tg-search/server'
1+
import type { SuccessResponse, UserInfoResponse } from '@tg-search/server'
22

33
import { ref } from 'vue'
44

@@ -20,6 +20,14 @@ export function useAuth() {
2020
return response.data.connected
2121
}
2222

23+
async function getMeInfo(): Promise<UserInfoResponse> {
24+
const response = await apiFetch<SuccessResponse<UserInfoResponse>>('/auth/me', {
25+
method: 'GET',
26+
})
27+
28+
return response.data
29+
}
30+
2331
/**
2432
* Logout from Telegram
2533
*/
@@ -45,5 +53,6 @@ export function useAuth() {
4553
error,
4654
logout,
4755
checkStatus,
56+
getMeInfo,
4857
}
4958
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { SuccessResponse, UserInfoResponse } from '@tg-search/server'
2+
3+
import { apiFetch, useApi } from '../composables/api'
4+
5+
/**
6+
* Vue composable for managing Telegram user info operations
7+
*/
8+
export function useUserInfo() {
9+
const { loading, error } = useApi()
10+
11+
/**
12+
* Get info for a single user by ID
13+
*/
14+
async function getUserInfo(id: string): Promise<UserInfoResponse> {
15+
const response = await apiFetch<SuccessResponse<UserInfoResponse>>(`/users/${id}`, {
16+
method: 'GET',
17+
query: {
18+
id,
19+
},
20+
})
21+
22+
return response.data
23+
}
24+
25+
/**
26+
* Get info for multiple users by IDs
27+
*/
28+
async function getUsersInfo(ids: string[]): Promise<UserInfoResponse[]> {
29+
const response = await apiFetch<SuccessResponse<UserInfoResponse[]>>('/users/batch', {
30+
method: 'GET',
31+
query: { ids },
32+
})
33+
34+
return response.data
35+
}
36+
37+
return {
38+
loading,
39+
error,
40+
getUserInfo,
41+
getUsersInfo,
42+
}
43+
}

packages/frontend/src/layouts/default.vue

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import type { UserInfoResponse } from '@tg-search/server'
23
import { onClickOutside } from '@vueuse/core'
34
import { onMounted, ref } from 'vue'
45
import { useI18n } from 'vue-i18n'
@@ -10,7 +11,7 @@ import { useLanguage } from '../composables/useLanguage'
1011
import { useSession } from '../composables/useSession'
1112
1213
const router = useRouter()
13-
const { logout } = useAuth()
14+
const { logout, getMeInfo } = useAuth()
1415
const { isDark } = useDarkStore()
1516
const { checkConnection, isConnected } = useSession()
1617
const { supportedLanguages, setLanguage, locale } = useLanguage()
@@ -19,6 +20,7 @@ const showUserMenu = ref(false)
1920
const showLanguageMenu = ref(false)
2021
const userMenuRef = ref<HTMLElement | null>(null)
2122
const languageMenuRef = ref<HTMLElement | null>(null)
23+
const userInfo = ref<UserInfoResponse | null>(null)
2224
2325
// Use VueUse's onClickOutside to handle closing the menus
2426
onClickOutside(userMenuRef, () => {
@@ -29,16 +31,25 @@ onClickOutside(languageMenuRef, () => {
2931
showLanguageMenu.value = false
3032
})
3133
32-
// 检查用户是否已登录
34+
// Check if user is logged in and get user info
3335
onMounted(async () => {
3436
await checkConnection(false)
37+
if (isConnected.value) {
38+
try {
39+
userInfo.value = await getMeInfo()
40+
}
41+
catch (err) {
42+
console.error('Failed to get user info:', err)
43+
}
44+
}
3545
})
3646
37-
// 处理注销
47+
// Handle logout
3848
async function handleLogout() {
3949
showUserMenu.value = false
4050
const success = await logout()
4151
if (success) {
52+
userInfo.value = null
4253
toast.success(t('header.logout_success'))
4354
router.push('/login')
4455
}
@@ -47,13 +58,13 @@ async function handleLogout() {
4758
}
4859
}
4960
50-
// 处理登录
61+
// Handle login
5162
async function handleLogin() {
5263
showUserMenu.value = false
5364
router.push('/login')
5465
}
5566
56-
// 处理语言切换
67+
// Handle language change
5768
function handleLanguageChange(langCode: string) {
5869
setLanguage(langCode)
5970
showLanguageMenu.value = false
@@ -73,15 +84,15 @@ function handleLanguageChange(langCode: string) {
7384
<div class="flex items-center gap-4">
7485
<ThemeToggle />
7586

76-
<!-- 语言切换 -->
87+
<!-- Language switcher -->
7788
<div ref="languageMenuRef" class="relative">
7889
<IconButton
7990
icon="i-carbon-language"
8091
aria-label="Language"
8192
@click="showLanguageMenu = !showLanguageMenu"
8293
/>
8394

84-
<!-- 语言菜单 -->
95+
<!-- Language menu -->
8596
<div
8697
v-if="showLanguageMenu"
8798
class="absolute right-0 z-50 mt-2 w-48 border border-gray-200 rounded-md bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800"
@@ -111,7 +122,7 @@ function handleLanguageChange(langCode: string) {
111122
@click="router.push('/settings')"
112123
/>
113124

114-
<!-- 用户头像与下拉菜单 -->
125+
<!-- User avatar and dropdown menu -->
115126
<div ref="userMenuRef" class="relative">
116127
<IconButton
117128
icon="i-carbon-user"
@@ -121,11 +132,20 @@ function handleLanguageChange(langCode: string) {
121132
@click="showUserMenu = !showUserMenu"
122133
/>
123134

124-
<!-- 用户菜单 -->
135+
<!-- User menu -->
125136
<div
126137
v-if="showUserMenu"
127138
class="absolute right-0 z-50 mt-2 w-48 border border-gray-200 rounded-md bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800"
128139
>
140+
<div v-if="userInfo" class="px-4 py-2 text-sm text-gray-700 dark:text-gray-200">
141+
<div>{{ userInfo.firstName }} {{ userInfo.lastName }}</div>
142+
<div class="text-xs text-gray-500">
143+
@{{ userInfo.username }}
144+
</div>
145+
</div>
146+
147+
<div class="border-b border-gray-200 dark:border-gray-700 my-2" />
148+
129149
<button
130150
v-if="!isConnected"
131151
class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"

packages/server/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { setupCommandRoutes } from './routes/commands'
1818
import { setupConfigRoutes } from './routes/config'
1919
import { setupMessageRoutes } from './routes/message'
2020
import { setupSearchRoutes } from './routes/search'
21+
import { setupUserInfoRoutes } from './routes/user-info'
2122
import { setupWsAuthRoutes } from './routes/ws-auth'
2223
import { createErrorResponse } from './utils/response'
2324

@@ -62,6 +63,7 @@ function setupRoutes(app: App) {
6263
setupMessageRoutes(app)
6364
setupSearchRoutes(app)
6465
setupWsAuthRoutes(app)
66+
setupUserInfoRoutes(app)
6567
}
6668

6769
// Server configuration
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { App, H3Event } from 'h3'
2+
import type { UserInfoResponse } from '../types/apis/user-info'
3+
4+
import { createError, createRouter, defineEventHandler, getRouterParams } from 'h3'
5+
6+
import { useTelegramClient } from '../services/telegram'
7+
import { createResponse } from '../utils/response'
8+
9+
/**
10+
* Setup user info routes
11+
*/
12+
export function setupUserInfoRoutes(app: App) {
13+
const router = createRouter()
14+
15+
// Get single user info
16+
router.get('/:id', defineEventHandler(async (event: H3Event) => {
17+
const { id } = getRouterParams(event)
18+
const client = await useTelegramClient()
19+
20+
try {
21+
const userInfo = await client.getUserInfo(id)
22+
23+
const response: UserInfoResponse = {
24+
id: userInfo.id.toString(),
25+
firstName: userInfo?.firstName ?? '',
26+
lastName: userInfo?.lastName ?? '',
27+
username: userInfo?.username ?? '',
28+
// photoUrl: userInfo.photoUrl ?? '',
29+
}
30+
31+
return createResponse<UserInfoResponse>(response)
32+
}
33+
catch {
34+
return createError({
35+
statusCode: 404,
36+
message: `User ${id} not found`,
37+
})
38+
}
39+
}))
40+
41+
// Get multiple users info
42+
router.get('/batch', defineEventHandler(async (event: H3Event) => {
43+
const { ids } = getRouterParams(event)
44+
const client = await useTelegramClient()
45+
46+
if (!ids || !Array.isArray(ids)) {
47+
throw createError({
48+
statusCode: 400,
49+
message: 'User IDs array is required',
50+
})
51+
}
52+
53+
try {
54+
const usersInfo = await client.getUsersInfo(ids)
55+
56+
const response: UserInfoResponse[] = usersInfo.map(user => ({
57+
id: user.id.toString(),
58+
firstName: user?.firstName ?? '',
59+
lastName: user?.lastName ?? '',
60+
username: user?.username ?? '',
61+
// photoUrl: user.photoUrl ?? '',
62+
}))
63+
64+
return createResponse<UserInfoResponse[]>(response)
65+
}
66+
catch {
67+
return createError({
68+
statusCode: 404,
69+
message: 'Failed to fetch users info',
70+
})
71+
}
72+
}))
73+
74+
// Mount routes
75+
app.use('/users', router.handler)
76+
}

packages/server/src/routes/ws-auth.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ITelegramClientAdapter } from '@tg-search/core'
22
import type { Peer } from 'crossws'
33
import type { App } from 'h3'
4+
import type { UserInfoResponse } from '../types/apis/user-info'
45
import type { WsMessage } from '../utils/ws'
56

67
import { useLogger } from '@tg-search/common'
@@ -101,6 +102,20 @@ export function setupWsAuthRoutes(app: App) {
101102
}
102103
}))
103104

105+
router.get('/me', defineEventHandler(async () => {
106+
const client = await useTelegramClient()
107+
const userInfo = await client.getMeInfo()
108+
109+
const response: UserInfoResponse = {
110+
id: userInfo.id.toString(),
111+
firstName: userInfo.firstName ?? '',
112+
lastName: userInfo.lastName ?? '',
113+
username: userInfo.username ?? '',
114+
}
115+
116+
return createResponse<UserInfoResponse>(response)
117+
}))
118+
104119
// 将旧的auth路由挂载到/auth路径
105120
app.use('/auth', router.handler)
106121

packages/server/src/types/apis/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './command'
33
export * from './export'
44
export * from './search'
55
export * from './sync'
6+
export * from './user-info'

0 commit comments

Comments
 (0)