Skip to content

Commit 3e12059

Browse files
committed
More refacto and display channels
1 parent 30b8413 commit 3e12059

File tree

15 files changed

+855
-251
lines changed

15 files changed

+855
-251
lines changed
Lines changed: 7 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,13 @@
11
import { FastifyInstance } from 'fastify'
2-
import { Static, Type } from '@sinclair/typebox'
3-
import { extractCookieForHeader } from '../../other/extractCookieForHeader'
4-
import { EventDao } from '../../dao/eventDao'
5-
6-
const BupherLoginBody = Type.Object({
7-
email: Type.String(),
8-
password: Type.String(),
9-
})
10-
11-
type BupherLoginBodyType = Static<typeof BupherLoginBody>
12-
13-
const BupherLoginReply = Type.Object({
14-
success: Type.Boolean(),
15-
error: Type.Optional(Type.String()),
16-
cookies: Type.Optional(Type.String()),
17-
})
18-
19-
type BupherLoginReplyType = Static<typeof BupherLoginReply>
20-
21-
const bupherDomain = 'login' + '.' + 'bu' + 'f' + 'f' + 'er' + '.com'
22-
23-
const browserHeaders = {
24-
'User-Agent':
25-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
26-
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
27-
'Accept-Language': 'en-US,en;q=0.9',
28-
Origin: 'https://' + bupherDomain,
29-
Host: bupherDomain,
30-
'X-Target-Domain': bupherDomain,
31-
Referer: 'https://' + bupherDomain + '.com/login',
32-
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
33-
'Sec-Ch-Ua-Mobile': '?0',
34-
'Sec-Ch-Ua-Platform': '"macOS"',
35-
'Sec-Fetch-Dest': 'document',
36-
'Sec-Fetch-Mode': 'navigate',
37-
'Sec-Fetch-Site': 'same-origin',
38-
'Sec-Fetch-User': '?1',
39-
'Upgrade-Insecure-Requests': '1',
40-
}
41-
42-
const BASE_URL = 'https://proxy.minix.gresse.io'
2+
import { bupherLoginRoute } from './bupherLoginRoute'
3+
import { bupherChannelsRoute } from './bupherChannelsRoute'
4+
// import { bupherScheduledPostsRoute } from './bupherScheduledPostsRoute'
435

446
export const bupherRoutes = (fastify: FastifyInstance, options: any, done: () => any) => {
45-
fastify.post<{ Body: BupherLoginBodyType; Reply: BupherLoginReplyType }>(
46-
'/v1/:eventId/bupher/login',
47-
{
48-
schema: {
49-
tags: ['bupher'],
50-
summary: 'Login to Bupher using credentials',
51-
querystring: {
52-
type: 'object',
53-
additionalProperties: false,
54-
properties: {
55-
apiKey: {
56-
type: 'string',
57-
description: 'The API key of the event',
58-
},
59-
},
60-
},
61-
body: BupherLoginBody,
62-
response: {
63-
200: BupherLoginReply,
64-
400: Type.Object({
65-
error: Type.String(),
66-
}),
67-
},
68-
security: [
69-
{
70-
apiKey: [],
71-
},
72-
],
73-
},
74-
preHandler: fastify.auth([fastify.verifyApiKey]),
75-
},
76-
async (request, reply) => {
77-
try {
78-
const { eventId } = request.params as { eventId: string }
79-
const { email, password } = request.body
80-
81-
// Fetch the login page
82-
const loginPageResponse = await fetch(`${BASE_URL}/login`, {
83-
headers: {
84-
...browserHeaders,
85-
'Sec-Fetch-Site': 'none',
86-
},
87-
})
88-
89-
if (!loginPageResponse.ok) {
90-
console.log('Bupher first fetch:', loginPageResponse.statusText, loginPageResponse.status)
91-
return reply.code(500).send({
92-
success: false,
93-
error: 'Failed to fetch Bupher login page',
94-
})
95-
}
96-
97-
// Get the HTML content
98-
const htmlContent = await loginPageResponse.text()
99-
100-
// Extract CSRF token from the HTML
101-
const csrfMatch = htmlContent.match(/<input[^>]*name="_csrf"[^>]*value="([^"]*)"/)
102-
if (!csrfMatch) {
103-
return reply.code(500).send({
104-
success: false,
105-
error: 'Could not extract CSRF token',
106-
})
107-
}
108-
const csrfToken = csrfMatch[1]
109-
110-
// Get cookies from the login page response and format them properly
111-
const rawCookies = loginPageResponse.headers.get('set-cookie')
112-
const cookies = rawCookies
113-
?.split(',')
114-
.map((cookie) => cookie.split(';')[0])
115-
.join('; ')
116-
117-
// Submit login form with CSRF token
118-
const loginResponse = await fetch(`${BASE_URL}/login`, {
119-
method: 'POST',
120-
headers: {
121-
...browserHeaders,
122-
'Content-Type': 'application/x-www-form-urlencoded',
123-
Cookie: cookies || '',
124-
},
125-
body: new URLSearchParams({
126-
_csrf: csrfToken,
127-
email: email,
128-
password: password,
129-
}).toString(),
130-
redirect: 'manual',
131-
})
132-
133-
console.log('Bupher login response:', loginResponse.statusText, loginResponse.status)
134-
135-
if (loginResponse.status !== 302) {
136-
return reply.code(401).send({
137-
success: false,
138-
error: 'Invalid credentials or login failed, status: ' + loginResponse.status,
139-
})
140-
}
141-
142-
const loginCookies = loginResponse.headers.get('set-cookie')
143-
144-
const bupherSession = extractCookieForHeader(loginCookies || '')
145-
146-
await EventDao.saveBupherSession(fastify.firebase, eventId, bupherSession)
7+
// Register all Bupher routes
8+
bupherLoginRoute(fastify, options, () => {})
9+
bupherChannelsRoute(fastify, options, () => {})
10+
// bupherScheduledPostsRoute(fastify, options, () => {})
14711

148-
reply.send({
149-
success: true,
150-
})
151-
} catch (error) {
152-
console.error('Bupher login error:', error)
153-
reply.code(500).send({
154-
success: false,
155-
error: 'Internal server error during Bupher login',
156-
})
157-
}
158-
}
159-
)
16012
done()
16113
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { Type } from '@sinclair/typebox'
3+
import { getBupherSession, makePublishRequest, sendErrorResponse } from './bupherUtils'
4+
5+
// Schema definitions
6+
const BupherChannelResponse = Type.Object({
7+
success: Type.Boolean(),
8+
error: Type.Optional(Type.String()),
9+
channels: Type.Optional(
10+
Type.Array(
11+
Type.Object({
12+
type: Type.String(),
13+
handle: Type.String(),
14+
formatted_username: Type.String(),
15+
avatarUrl: Type.Optional(Type.String()),
16+
})
17+
)
18+
),
19+
})
20+
21+
export const bupherChannelsRoute = (fastify: FastifyInstance, options: any, done: () => any) => {
22+
fastify.get(
23+
'/v1/:eventId/bupher/channels',
24+
{
25+
schema: {
26+
tags: ['bupher'],
27+
summary: 'Get Bupher channels list',
28+
querystring: {
29+
type: 'object',
30+
additionalProperties: false,
31+
properties: {
32+
apiKey: {
33+
type: 'string',
34+
description: 'The API key of the event',
35+
},
36+
},
37+
},
38+
response: {
39+
200: BupherChannelResponse,
40+
400: Type.Object({
41+
error: Type.String(),
42+
}),
43+
},
44+
security: [
45+
{
46+
apiKey: [],
47+
},
48+
],
49+
},
50+
preHandler: fastify.auth([fastify.verifyApiKey]),
51+
},
52+
async (request, reply) => {
53+
try {
54+
const { eventId } = request.params as { eventId: string }
55+
56+
// Get the Bupher session
57+
let bupherSession: string
58+
try {
59+
bupherSession = await getBupherSession(fastify.firebase, eventId)
60+
} catch (error) {
61+
return sendErrorResponse(reply, 401, 'No Bupher session found. Please login first.')
62+
}
63+
64+
const channelsResponse = await makePublishRequest(
65+
'/rpc/profiles',
66+
bupherSession,
67+
'POST',
68+
'{"args":"{}"}'
69+
)
70+
71+
if (!channelsResponse.ok) {
72+
console.error('Failed to fetch channels', channelsResponse.status, channelsResponse.statusText)
73+
return sendErrorResponse(reply, 500, 'Failed to fetch channels')
74+
}
75+
76+
const channels = await channelsResponse.json()
77+
78+
reply.send({
79+
success: true,
80+
channels: channels.result.map((channel: any) => ({
81+
type: channel.type,
82+
handle: channel.handle,
83+
formatted_username: channel.formatted_username,
84+
avatarUrl: channel.avatarUrl,
85+
})),
86+
})
87+
} catch (error) {
88+
console.error('Bupher channels error:', error)
89+
reply.code(500).send({
90+
success: false,
91+
error: 'Internal server error while fetching Bupher channels',
92+
})
93+
}
94+
}
95+
)
96+
done()
97+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { Static, Type } from '@sinclair/typebox'
3+
import { loginToBupher } from './bupherUtils'
4+
5+
// Schema definitions
6+
const BupherLoginBody = Type.Object({
7+
email: Type.String(),
8+
password: Type.String(),
9+
})
10+
11+
type BupherLoginBodyType = Static<typeof BupherLoginBody>
12+
13+
const BupherLoginReply = Type.Object({
14+
success: Type.Boolean(),
15+
error: Type.Optional(Type.String()),
16+
cookies: Type.Optional(Type.String()),
17+
})
18+
19+
type BupherLoginReplyType = Static<typeof BupherLoginReply>
20+
21+
export const bupherLoginRoute = (fastify: FastifyInstance, options: any, done: () => any) => {
22+
fastify.post<{ Body: BupherLoginBodyType; Reply: BupherLoginReplyType }>(
23+
'/v1/:eventId/bupher/login',
24+
{
25+
schema: {
26+
tags: ['bupher'],
27+
summary: 'Login to Bupher using credentials',
28+
querystring: {
29+
type: 'object',
30+
additionalProperties: false,
31+
properties: {
32+
apiKey: {
33+
type: 'string',
34+
description: 'The API key of the event',
35+
},
36+
},
37+
},
38+
body: BupherLoginBody,
39+
response: {
40+
200: BupherLoginReply,
41+
400: Type.Object({
42+
error: Type.String(),
43+
}),
44+
},
45+
security: [
46+
{
47+
apiKey: [],
48+
},
49+
],
50+
},
51+
preHandler: fastify.auth([fastify.verifyApiKey]),
52+
},
53+
async (request, reply) => {
54+
try {
55+
const { eventId } = request.params as { eventId: string }
56+
const { email, password } = request.body
57+
58+
const success = await loginToBupher(email, password, fastify.firebase, eventId, reply)
59+
60+
if (success) {
61+
reply.send({
62+
success: true,
63+
})
64+
}
65+
// If not successful, the loginToBupher function will have already sent an error response
66+
} catch (error) {
67+
console.error('Bupher login error:', error)
68+
reply.code(500).send({
69+
success: false,
70+
error: 'Internal server error during Bupher login',
71+
})
72+
}
73+
}
74+
)
75+
done()
76+
}

0 commit comments

Comments
 (0)