Skip to content

Commit c537e81

Browse files
committed
feat(infrastructure): convert to bun
1 parent 2369220 commit c537e81

6 files changed

Lines changed: 128 additions & 65 deletions

File tree

src/screens/DevTools/DebugScreen.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useAuthContext } from '@app/context';
44
import { useAppNavigation, useHeader, useDebugOptions } from '@app/hooks';
55
import { AllowedKey, storageService, twitchService } from '@app/services';
66
import { logger } from '@app/utils/logger';
7-
import Clipboard from 'expo-clipboard';
7+
import * as Clipboard from 'expo-clipboard';
88
import { useState, useEffect } from 'react';
99
import { Alert, FlatList, Switch, View } from 'react-native';
1010
import { createStyleSheet, useStyles } from 'react-native-unistyles';
@@ -41,11 +41,25 @@ function TwitchUsernameConverter() {
4141
const { styles } = useStyles(stylesheet);
4242
const [twitchUsername, setTwitchUsername] = useState<string>('');
4343
const [twitchUserId, setTwitchUserId] = useState<string>('');
44+
const [isLoading, setIsLoading] = useState(false);
4445

45-
const handleConvertAndCopy = async () => {
46-
const result = await twitchService.getUser(twitchUsername);
47-
setTwitchUserId(result.id);
48-
await Clipboard.setStringAsync(result.id);
46+
const handleConvert = async () => {
47+
if (!twitchUsername.trim()) {
48+
Alert.alert('Error', 'Please enter a Twitch username');
49+
return;
50+
}
51+
52+
setIsLoading(true);
53+
try {
54+
const result = await twitchService.getUser(twitchUsername, undefined);
55+
if (result.id) {
56+
setTwitchUserId(result.id);
57+
}
58+
} catch {
59+
Alert.alert('Error', 'Failed to convert username');
60+
} finally {
61+
setIsLoading(false);
62+
}
4963
};
5064

5165
return (
@@ -59,16 +73,16 @@ function TwitchUsernameConverter() {
5973
onChangeText={setTwitchUsername}
6074
style={styles.input}
6175
/>
62-
<Button
63-
onPress={() => {
64-
void handleConvertAndCopy();
65-
}}
66-
style={styles.button}
67-
>
68-
<Typography>Convert and Copy</Typography>
76+
<Button onPress={() => void handleConvert()} style={styles.button}>
77+
<Typography>
78+
{isLoading ? 'Converting...' : 'Convert and Copy'}
79+
</Typography>
6980
</Button>
7081
{twitchUserId ? (
71-
<Typography style={styles.userId}>User ID: {twitchUserId}</Typography>
82+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
83+
<Button onPress={() => Clipboard.setStringAsync(twitchUserId)}>
84+
<Typography style={styles.userId}>User ID: {twitchUserId}</Typography>
85+
</Button>
7286
) : null}
7387
</View>
7488
);
@@ -93,10 +107,7 @@ function DisplayAccessToken() {
93107
</Typography>
94108
{authState?.token.accessToken && (
95109
// eslint-disable-next-line @typescript-eslint/no-misused-promises
96-
<Button
97-
onPress={() => handleCopyToClipboard}
98-
style={styles.copyButton}
99-
>
110+
<Button onPress={handleCopyToClipboard} style={styles.copyButton}>
100111
<Typography style={styles.copyButtonText}>Copy</Typography>
101112
</Button>
102113
)}

src/services/api/index.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const twitchBadgeApi = new Client({
1818
// Better Twitch TV API
1919
export const bttvApi = new Client({
2020
baseURL: 'https://api.betterttv.net',
21-
requestInterceptors: [createLoggerInterceptor('api')],
21+
requestInterceptors: [createLoggerInterceptor('bttv')],
2222
});
2323

2424
// cached Better Twitch TV emote API
@@ -36,18 +36,11 @@ export const sevenTvApi = new Client({
3636
// FrankerzFaceZ API
3737
export const ffzApi = new Client({
3838
baseURL: 'https://api.frankerfacez.com/v1',
39-
requestInterceptors: [createLoggerInterceptor('api')],
39+
requestInterceptors: [createLoggerInterceptor('ffz')],
4040
});
4141

4242
// FrankerzFaceZ cached emote API
4343
export const ffzEmoteApi = new Client({
4444
baseURL: 'https://api.betterttv.net/3/cached/frankerfacez',
45-
requestInterceptors: [createLoggerInterceptor('api')],
46-
});
47-
48-
// IVR api - need to move to our own service. @see logs.ivr.fi & api.ivr.fi
49-
//
50-
export const ivrApi = new Client({
51-
baseURL: 'https://api.ivr.fi/v2',
52-
requestInterceptors: [createLoggerInterceptor('api')],
45+
requestInterceptors: [createLoggerInterceptor('ffz')],
5346
});

src/services/ffzService.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@
22
import { logger } from '@app/utils/logger';
33
import { ffzApi } from './api';
44
import { SanitisiedEmoteSet } from './seventTvService';
5-
6-
export interface SanitisedBadgeSet {
7-
id: string;
8-
url: string;
9-
title: string;
10-
color?: string;
11-
owner_username?: string;
12-
type?: string;
13-
setId?: string;
14-
}
5+
import { SanitisedBadgeSet } from '.';
156

167
interface FfzEmoticon {
178
id: number;
@@ -145,8 +136,6 @@ export const ffzService = {
145136
return [];
146137
}
147138

148-
logger.ffz.info('result', result);
149-
150139
if ('sets' in result) {
151140
const emoteSet = result.sets[result.room.set];
152141
const sanitistedSet =
@@ -192,6 +181,8 @@ export const ffzService = {
192181
title: badge.title,
193182
color: badge.color,
194183
owner_username: username,
184+
set: badge.id.toString(),
185+
type: 'FFZ global badge',
195186
});
196187
});
197188
});

src/services/twitchBadgeService.ts

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,103 @@
11
import { OpenStringUnion } from '@app/utils';
2-
import { ivrApi } from './api';
2+
import { twitchApi } from './api';
33

4-
interface TwitchBadge {
4+
interface TwitchBadgeVersion {
55
id: string;
66
image_url_1x: string;
77
image_url_2x: string;
88
image_url_4x: string;
9-
/**
10-
* @example cheer 1
11-
*/
129
title: string;
13-
14-
/**
15-
* @example cheer 1
16-
*/
1710
description: string;
18-
click_action: OpenStringUnion<'visit_url' | 'subscribe_to_channel'> | null;
11+
click_action: string;
1912
click_url: string | null;
2013
}
2114

22-
interface IvrChannelBadges {
23-
set_id: OpenStringUnion<'bits' | 'subscriber'>;
24-
versions: TwitchBadge[];
15+
interface TwitchBadge {
16+
set_id: OpenStringUnion<'subscriber' | 'bits'>;
17+
versions: TwitchBadgeVersion[];
18+
}
19+
20+
export interface SanitisedBadgeSet {
21+
id: string;
22+
url: string;
23+
type: OpenStringUnion<
24+
| 'Twitch Channel Badge'
25+
| 'Twitch Subscriber Badge'
26+
| 'Twitch Bit Badge'
27+
| 'Twitch Global Badge'
28+
>;
29+
title: string;
30+
31+
color?: string;
32+
owner_username?: string;
33+
/**
34+
* The set ID
35+
*/
36+
set: string;
2537
}
2638

2739
export const twitchBadgeService = {
28-
getBadges: async (broadcasterLogin: string) => {
29-
const result = await ivrApi.get<IvrChannelBadges[]>(
30-
'/twitch/badges/channel',
40+
listSanitisedChannelBadges: async (
41+
channelId: string,
42+
): Promise<SanitisedBadgeSet[]> => {
43+
const result = await twitchApi.get<{ data: TwitchBadge[] }>(
44+
'/chat/badges',
3145
{
3246
params: {
33-
login: broadcasterLogin,
47+
broadcaster_id: channelId,
3448
},
3549
},
3650
);
3751

38-
return result;
52+
const sanitisedBadges: SanitisedBadgeSet[] = [];
53+
54+
result.data.forEach(badgeSet => {
55+
if (badgeSet.set_id === 'bits') {
56+
badgeSet.versions.forEach((badge: TwitchBadgeVersion) => {
57+
sanitisedBadges.push({
58+
id: badge.id,
59+
url: badge.image_url_4x,
60+
type: 'Twitch Bit Badge',
61+
title: `Cheer ${badge.id}`,
62+
set: badgeSet.set_id,
63+
});
64+
});
65+
}
66+
if (badgeSet.set_id === 'subscriber') {
67+
badgeSet.versions.forEach((badge: TwitchBadgeVersion) => {
68+
sanitisedBadges.push({
69+
id: badge.id,
70+
url: badge.image_url_4x,
71+
type: 'Twitch Subscriber Badge',
72+
title: badge.title,
73+
set: badgeSet.set_id,
74+
});
75+
});
76+
}
77+
});
78+
79+
return sanitisedBadges;
3980
},
40-
getGlobalBadges: async () => {
41-
const result = await ivrApi.get<IvrChannelBadges[]>(
42-
'/twitch/badges/global',
81+
listSanitisedGlobalBadges: async (): Promise<SanitisedBadgeSet[]> => {
82+
const result = await twitchApi.get<{ data: TwitchBadge[] }>(
83+
'/chat/badges/global',
4384
);
4485

45-
return result;
86+
const sanitisedBadges: SanitisedBadgeSet[] = [];
87+
88+
result.data.forEach(badgeSet => {
89+
if (Object.keys(badgeSet).length > 0) {
90+
badgeSet.versions.forEach(version => {
91+
sanitisedBadges.push({
92+
id: `${badgeSet.set_id}_${version.id}`, // set set_id as id
93+
url: version.image_url_4x,
94+
title: version.title,
95+
type: 'Twitch Global Badge',
96+
set: badgeSet.set_id,
97+
});
98+
});
99+
}
100+
});
101+
return sanitisedBadges;
46102
},
47103
} as const;

src/services/twitchService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ export const twitchService = {
259259
if (userId) {
260260
params.login = userId;
261261
}
262+
262263
if (id) {
263264
params.id = id;
264265
}
@@ -267,7 +268,7 @@ export const twitchService = {
267268
params,
268269
});
269270

270-
return result.data[0] as UserInfoResponse;
271+
return (result.data[0] as UserInfoResponse) ?? '';
271272
},
272273

273274
searchChannels: async (query: string): Promise<SearchChannelResponse[]> => {

src/store/chatStore.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ const chatStoreCreator: StateCreator<ChatState> = set => ({
232232
}
233233

234234
const [
235+
/**
236+
* Emotes
237+
*/
235238
sevenTvChannelEmotes,
236239
sevenTvGlobalEmotes,
237240
twitchChannelEmotes,
@@ -240,6 +243,12 @@ const chatStoreCreator: StateCreator<ChatState> = set => ({
240243
bttvChannelEmotes,
241244
ffzChannelEmotes,
242245
ffzGlobalEmotes,
246+
247+
/**
248+
* Badges
249+
*/
250+
twitchChannelBadges,
251+
twitchGlobalBadges,
243252
] = await Promise.allSettled([
244253
sevenTvService.getSanitisedEmoteSet(sevenTvSetId),
245254
sevenTvService.getSanitisedEmoteSet('global'),
@@ -249,13 +258,13 @@ const chatStoreCreator: StateCreator<ChatState> = set => ({
249258
bttvEmoteService.getSanitisedChannelEmotes(channelId),
250259
ffzService.getSanitisedChannelEmotes(channelId),
251260
ffzService.getSanitisedGlobalEmotes(),
261+
twitchBadgeService.listSanitisedChannelBadges(channelId),
262+
twitchBadgeService.listSanitisedGlobalBadges(),
252263
]);
253264

254265
logger.chat.info('fetched emotes 🚀');
255266

256-
const getValue = (
257-
result: PromiseSettledResult<SanitisiedEmoteSet[]>,
258-
): SanitisiedEmoteSet[] =>
267+
const getValue = <T>(result: PromiseSettledResult<T[]>): T[] =>
259268
result.status === 'fulfilled' ? result.value : [];
260269

261270
// Log empty responses from emote providers
@@ -290,6 +299,8 @@ const chatStoreCreator: StateCreator<ChatState> = set => ({
290299
bttvChannelEmotes: getValue(bttvChannelEmotes),
291300
ffzChannelEmotes: getValue(ffzChannelEmotes),
292301
ffzGlobalEmotes: getValue(ffzGlobalEmotes),
302+
twitchChannelBadges: getValue(twitchChannelBadges),
303+
twitchGlobalBadges: getValue(twitchGlobalBadges),
293304
}));
294305

295306
return true;

0 commit comments

Comments
 (0)