返回 AiToEarn
twitter.controller.ts
根目录 / project / aitoearn-electron / server / src / modules / plat / twitter / twitter.controller.ts
1 import { Controller, Get, Post, Body, Query, Res, BadRequestException, Delete, Param, HttpCode, UploadedFile, UseInterceptors } from '@nestjs/common';
2 import { ApiTags, ApiOperation, ApiQuery, ApiBody, ApiParam, ApiConsumes } from '@nestjs/swagger';
3 import { Response } from 'express';
4 import { GetToken, Public } from 'src/auth/auth.guard';
5 import { FileInterceptor } from '@nestjs/platform-express';
6 import { TokenInfo } from 'src/auth/interfaces/auth.interfaces';
7
8 import { TwitterAuthService } from './twitter.auth.service';
9 import { TwitterService } from './twitter.service';
10
11 @ApiTags('plat/twitter - twitter平台')
12 @Controller('plat/twitter')
13 export class TwitterController {
14 constructor(
15 private readonly twitterAuthService: TwitterAuthService,
16 private readonly twitterService: TwitterService,
17 ) {}
18
19 /**
20 * 获取Twitter授权URL
21 */
22 @Get('auth/url')
23 @ApiOperation({ summary: '获取Twitter授权URL' })
24 @ApiQuery({ name: 'mail', required: false, description: '用户邮箱' })
25 async getAuthUrl(
26 @GetToken() systemToken: TokenInfo,
27 @Query('mail') mail: string,
28 ) {
29 if (!systemToken.id || !mail) {
30 throw new BadRequestException('token和mail是必须的');
31 }
32 return this.twitterAuthService.getAuthorizationUrl(systemToken.id, mail);
33 }
34
35 /**
36 * 处理Twitter OAuth回调
37 */
38 @Get('auth/callback')
39 @ApiOperation({ summary: 'Twitter授权回调' })
40 // @ApiQuery({ name: 'code', required: true, description: '授权码' })
41 // @ApiQuery({ name: 'state', required: true, description: '状态值' })
42 @Public()
43 async handleOAuthCallback(
44 // @GetToken() systemToken: TokenInfo,
45 @Query('code') code: string,
46 @Query('state') state: string,
47 @Res() res: Response,
48 ) {
49 if (!code || !state) {
50 throw new BadRequestException('缺少必要的参数');
51 }
52 console.log(code, state);
53 try {
54
55 // 处理授权回调
56 const results = await this.twitterAuthService.handleAuthorizationCallback(code, state);
57
58 // 获取重定向URL,如果存在的话
59 // const redirectUrl = process.env.TWITTER_AUTH_SUCCESS_REDIRECT || 'https://your-frontend-app/auth/success';
60
61 // // 重定向到前端应用,带上必要的参数
62 // return res.redirect(`${redirectUrl}?success=true`);
63 const render_msg = {
64 message: "授权成功! 这里是添加账号成功后的前端页面," ,
65 datas: results
66 };
67
68 return res.render('google/index', render_msg);
69
70 } catch (error) {
71 console.error('Twitter授权回调处理失败:', error);
72
73 // 获取失败重定向URL
74 const failureRedirectUrl = process.env.TWITTER_AUTH_FAILURE_REDIRECT || 'https://your-frontend-app/auth/failure';
75
76 // 重定向到前端失败页面,带上错误信息
77 // return res.redirect(`${failureRedirectUrl}?error=${encodeURIComponent(error.message || 'Unknown error')}`);
78 return false
79 }
80 }
81
82 /**
83 * 检查用户是否已授权Twitter
84 */
85 @Get('auth/check')
86 @ApiOperation({ summary: '检查用户Twitter授权状态' })
87 @ApiQuery({ name: 'accountId', required: true, description: 'Twitter账号ID' })
88 async checkAuthStatus(
89 @GetToken() systemToken: TokenInfo,
90 @Query('accountId') accountId: string
91 ) {
92 if (!accountId) {
93 throw new BadRequestException('accountId是必须的');
94 }
95
96 return await this.twitterAuthService.isAuthorized(accountId);
97 }
98
99 /**
100 * 撤销Twitter授权
101 */
102 @Post('auth/revoke')
103 @ApiOperation({ summary: '撤销Twitter授权' })
104 @ApiBody({
105 schema: {
106 type: 'object',
107 properties: {
108 accountId: { type: 'string', description: 'Twitter账号ID' }
109 }
110 }
111 })
112 async revokeAuth(
113 @GetToken() systemToken: TokenInfo,
114 @Body('accountId') accountId: string
115 ) {
116 if (!accountId) {
117 throw new BadRequestException('accountId是必须的');
118 }
119
120 return await this.twitterAuthService.revokeAuthorization(accountId);
121 }
122
123 /**
124 * 刷新Twitter访问令牌
125 */
126 @Post('auth/refresh')
127 @ApiOperation({ summary: '刷新Twitter访问令牌' })
128 @ApiBody({
129 schema: {
130 type: 'object',
131 properties: {
132 userId: { type: 'string', description: '用户ID' },
133 accountId: { type: 'string', description: 'Twitter账号ID' },
134 refreshToken: { type: 'string', description: '刷新令牌' }
135 }
136 }
137 })
138 async refreshToken(
139 @GetToken() systemToken: TokenInfo,
140 @Body('userId') userId: string,
141 @Body('accountId') accountId: string,
142 @Body('refreshToken') refreshToken: string
143 ) {
144 if (!userId || !accountId || !refreshToken) {
145 throw new BadRequestException('缺少必要的参数');
146 }
147
148 return this.twitterAuthService.refreshAccessToken(userId, accountId, refreshToken);
149 }
150
151 /**
152 * 获取用户的Twitter时间线
153 */
154 @Get('timeline')
155 @ApiOperation({ summary: '获取用户的Twitter时间线' })
156 // @ApiQuery({ name: 'userId', required: true, description: '用户ID' })
157 @ApiQuery({ name: 'accountId', required: true, description: '账号ID' })
158 @ApiQuery({ name: 'maxResults', required: false, description: '最大结果数', type: 'number' })
159 async getUserTimeline(
160 @GetToken() systemToken: TokenInfo,
161 // @Query('userId') userId: string,
162 @Query('accountId') accountId: string,
163 @Query('maxResults') maxResults?: number,
164 ) {
165
166 const userId = systemToken.id;
167 if (!userId || !accountId) {
168 throw new BadRequestException('userId和accountId是必须的');
169 }
170
171 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
172
173 return this.twitterService.getUserTimeline(accessToken, userId, accountId, maxResults);
174 }
175
176 /**
177 * 发布新推文
178 */
179 @Post('tweets/create')
180 @ApiOperation({ summary: '发布新推文' })
181 @ApiBody({
182 schema: {
183 type: 'object',
184 properties: {
185 // userId: { type: 'string', description: '用户ID' },
186 accountId: { type: 'string', description: 'Twitter账号ID' },
187 text: { type: 'string', description: '推文内容' },
188 mediaIds: { type: 'array', items: { type: 'string' }, description: '媒体ID列表(可选)' }
189 },
190 required: ['userId', 'accountId', 'text']
191 }
192 })
193 @HttpCode(201)
194 async createTweet(
195 @GetToken() systemToken: TokenInfo,
196 // @Body('userId') userId: string,
197 @Body('accountId') accountId: string,
198 @Body('text') text: string,
199 @Body('mediaIds') mediaIds?: string[],
200 ) {
201 const userId = systemToken.id;
202 if (!userId || !accountId || !text) {
203 throw new BadRequestException('userId, accountId和text是必须的');
204 }
205
206 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
207 return this.twitterService.createTweet(accessToken, userId, accountId, text, mediaIds);
208 }
209
210 /**
211 * 上传媒体文件
212 */
213 @Post('media/upload')
214 @ApiOperation({ summary: '上传媒体文件' })
215 @ApiConsumes('multipart/form-data')
216 @ApiBody({
217 schema: {
218 type: 'object',
219 properties: {
220 userId: { type: 'string' },
221 accountId: { type: 'string' },
222 file: {
223 type: 'string',
224 format: 'binary',
225 },
226 },
227 },
228 })
229 @UseInterceptors(FileInterceptor('file'))
230 async uploadMedia(
231 @GetToken() systemToken: TokenInfo,
232 // @Body('userId') userId: string,
233 @Body('accountId') accountId: string,
234 @UploadedFile() file: Express.Multer.File,
235 ) {
236 const userId = systemToken.id;
237 if (!userId || !accountId || !file) {
238 throw new BadRequestException('userId, accountId和file是必须的');
239 }
240
241 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
242 return this.twitterService.uploadMedia(accessToken, userId, accountId, file.buffer, file.mimetype);
243 }
244
245 /**
246 * 获取推文详情
247 */
248 @Get('tweets/detail')
249 @ApiOperation({ summary: '获取推文详情' })
250 @ApiQuery({ name: 'tweetId', type: 'string', description: '推文ID' })
251 // @ApiQuery({ name: 'userId', required: true, description: '用户ID' })
252 @ApiQuery({ name: 'accountId', required: true, description: 'Twitter账号ID' })
253 async getTweetDetail(
254 @GetToken() systemToken: TokenInfo,
255 @Query('tweetId') tweetId: string,
256 // @Query('userId') userId: string,
257 @Query('accountId') accountId: string,
258 ) {
259 const userId = systemToken.id;
260 if (!tweetId || !userId || !accountId) {
261 throw new BadRequestException('tweetId, userId和accountId是必须的');
262 }
263
264 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
265 return this.twitterService.getTweetDetail(accessToken, userId, accountId, tweetId);
266 }
267
268 /**
269 * 获取推文统计数据
270 */
271 @Get('tweets/metrics')
272 @ApiOperation({ summary: '获取推文统计数据' })
273 @ApiQuery({ name: 'tweetId', type: 'string', description: '推文ID' })
274 // @ApiQuery({ name: 'userId', required: true, description: '用户ID' })
275 @ApiQuery({ name: 'accountId', required: true, description: 'Twitter账号ID' })
276 async getTweetMetrics(
277 @GetToken() systemToken: TokenInfo,
278 @Query('tweetId') tweetId: string,
279 // @Query('userId') userId: string,
280 @Query('accountId') accountId: string,
281 ) {
282 const userId = systemToken.id;
283 if (!tweetId || !userId || !accountId) {
284 throw new BadRequestException('tweetId, userId和accountId是必须的');
285 }
286
287 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
288 return this.twitterService.getTweetMetrics(accessToken, userId, accountId, tweetId);
289 }
290
291 /**
292 * 删除推文
293 */
294 @Post('tweets/delete')
295 @ApiOperation({ summary: '删除推文' })
296 @ApiBody({
297 schema: {
298 type: 'object',
299 properties: {
300 accountId: { type: 'string', description: '账号ID' },
301 tweetId: { type: 'string', description: '推文ID' }
302 },
303 required: ['accountId', 'tweetId']
304 }
305 })
306
307 async deleteTweet(
308 @GetToken() systemToken: TokenInfo,
309 @Body('tweetId') tweetId: string,
310 @Body('accountId') accountId: string,
311 ) {
312 const userId = systemToken.id;
313 if (!tweetId || !userId || !accountId) {
314 throw new BadRequestException('tweetId, userId和accountId是必须的');
315 }
316
317 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
318 return this.twitterService.deleteTweet(accessToken, userId, accountId, tweetId);
319 }
320
321 /**
322 * 搜索推文
323 */
324 @Get('tweets/search')
325 @ApiOperation({ summary: '搜索推文' })
326 @ApiQuery({ name: 'accountId', required: true, description: 'Twitter账号ID' })
327 @ApiQuery({ name: 'query', required: true, description: '搜索关键词' })
328 @ApiQuery({ name: 'maxResults', required: false, description: '最大结果数', type: 'number' })
329 async searchTweets(
330 @GetToken() systemToken: TokenInfo,
331 @Query('accountId') accountId: string,
332 @Query('query') query: string,
333 @Query('maxResults') maxResults?: number,
334 ) {
335 const userId = systemToken.id;
336 if (!userId || !accountId || !query) {
337 throw new BadRequestException('userId, accountId和query是必须的');
338 }
339
340 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
341 return this.twitterService.searchTweets(accessToken, userId, accountId, query, maxResults);
342 }
343
344 /**
345 * 对推文点赞、取消点赞
346 */
347 @Post('tweets/rate')
348 @ApiOperation({ summary: '对推文点赞、取消点赞' })
349 @ApiBody({
350 schema: {
351 type: 'object',
352 properties: {
353 accountId: { type: 'string', description: '账号ID' },
354 tweetId: { type: 'string', description: '推文ID' },
355 rating: { type: 'string', description: '点赞 like、取消点赞 unlike' }
356 },
357 required: ['accountId', 'tweetId', 'rating']
358 }
359 })
360 async rateTweet(
361 @GetToken() systemToken: TokenInfo,
362 @Body('tweetId') tweetId: string,
363 @Body('accountId') accountId: string,
364 @Body('rating') rating: string,
365 ) {
366 const userId = systemToken.id;
367 if (!tweetId || !userId || !accountId || ! rating) {
368 throw new BadRequestException('tweetId, rating和accountId是必须的');
369 }
370
371 const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);
372 // 在方法开始处验证
373 if (!["like", "unlike"].includes(rating)) {
374 throw new BadRequestException('参数错误: rating必须为like或unlike');
375 }
376
377 // 后续处理
378 if (rating === "like") {
379 return this.twitterService.likeTweet(accessToken, userId, accountId, tweetId);
380 } else {
381 return this.twitterService.unlikeTweet(accessToken, userId, accountId, tweetId);
382 }
383 }
384
385 }
386
386 lines TYPESCRIPT