mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-05 10:30:55 +01:00
Fixed many of the eslint issues
This commit is contained in:
parent
4676cca9b6
commit
9669ff2889
14 changed files with 92 additions and 99 deletions
|
@ -103,7 +103,7 @@ In many ways, FixTweet has richer embeds and does more. Here's a table comparing
|
|||
| Strip Twitter tracking info on redirect | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Show retweet, like, reply counts | :heavy_check_mark: | :heavy_minus_sign: Discord Only³ | :ballot_box_with_check: No replies | :ballot_box_with_check: No replies |
|
||||
| Discord sed replace (`s/`) friendly | :ballot_box_with_check: twittpr.com | N/A | :x: | :heavy_check_mark: |
|
||||
| Tweet fetch API for Developers | Coming soon! | N/A | :x: | :heavy_check_mark: |
|
||||
| Tweet fetch API for Developers | :heavy_check_mark: | N/A | :x: | :heavy_check_mark: |
|
||||
|
||||
¹ Discord will attempt to embed Twitter's video player, but it is unreliable
|
||||
|
||||
|
@ -142,7 +142,7 @@ Once you're set up with your worker on `*.workers.dev`, [add your worker to your
|
|||
### Things to tackle in the future
|
||||
|
||||
- More reliable Multi-Image in Telegram
|
||||
- Reimplement TwitFix API for third-party developers
|
||||
- Discord bot
|
||||
|
||||
### Bugs or issues?
|
||||
|
||||
|
|
25
src/api.ts
25
src/api.ts
|
@ -16,7 +16,7 @@ const processMedia = (media: TweetMedia): APIPhoto | APIVideo | null => {
|
|||
};
|
||||
} else if (media.type === 'video' || media.type === 'animated_gif') {
|
||||
// Find the variant with the highest bitrate
|
||||
let bestVariant = media.video_info?.variants?.reduce?.((a, b) =>
|
||||
const bestVariant = media.video_info?.variants?.reduce?.((a, b) =>
|
||||
(a.bitrate ?? 0) > (b.bitrate ?? 0) ? a : b
|
||||
);
|
||||
return {
|
||||
|
@ -35,8 +35,9 @@ const populateTweetProperties = async (
|
|||
tweet: TweetPartial,
|
||||
conversation: TimelineBlobPartial,
|
||||
language: string | undefined
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): Promise<APITweet> => {
|
||||
let apiTweet = {} as APITweet;
|
||||
const apiTweet = {} as APITweet;
|
||||
|
||||
/* With v2 conversation API we re-add the user object ot the tweet because
|
||||
Twitter stores it separately in the conversation API. This is to consolidate
|
||||
|
@ -72,19 +73,18 @@ const populateTweetProperties = async (
|
|||
|
||||
apiTweet.replying_to = tweet.in_reply_to_screen_name || null;
|
||||
|
||||
let mediaList = Array.from(
|
||||
const mediaList = Array.from(
|
||||
tweet.extended_entities?.media || tweet.entities?.media || []
|
||||
);
|
||||
|
||||
mediaList.forEach(media => {
|
||||
let mediaObject = processMedia(media);
|
||||
const mediaObject = processMedia(media);
|
||||
if (mediaObject) {
|
||||
if (mediaObject.type === 'photo') {
|
||||
apiTweet.twitter_card = 'summary_large_image';
|
||||
apiTweet.media = apiTweet.media || {};
|
||||
apiTweet.media.photos = apiTweet.media.photos || [];
|
||||
apiTweet.media.photos.push(mediaObject);
|
||||
|
||||
} else if (mediaObject.type === 'video' || mediaObject.type === 'gif') {
|
||||
apiTweet.twitter_card = 'player';
|
||||
apiTweet.media = apiTweet.media || {};
|
||||
|
@ -98,14 +98,14 @@ const populateTweetProperties = async (
|
|||
}
|
||||
|
||||
if ((apiTweet.media?.photos?.length || 0) > 1) {
|
||||
let mosaic = await handleMosaic(apiTweet.media?.photos || []);
|
||||
const mosaic = await handleMosaic(apiTweet.media?.photos || []);
|
||||
if (typeof apiTweet.media !== 'undefined' && mosaic !== null) {
|
||||
apiTweet.media.mosaic = mosaic;
|
||||
}
|
||||
}
|
||||
|
||||
if (tweet.card) {
|
||||
let card = await renderCard(tweet.card);
|
||||
const card = await renderCard(tweet.card);
|
||||
if (card.external_media) {
|
||||
apiTweet.twitter_card = 'summary_large_image';
|
||||
apiTweet.media = apiTweet.media || {};
|
||||
|
@ -120,7 +120,7 @@ const populateTweetProperties = async (
|
|||
|
||||
/* If a language is specified, let's try translating it! */
|
||||
if (typeof language === 'string' && language.length === 2 && language !== tweet.lang) {
|
||||
let translateAPI = await translateTweet(
|
||||
const translateAPI = await translateTweet(
|
||||
tweet,
|
||||
conversation.guestToken || '',
|
||||
language
|
||||
|
@ -138,11 +138,10 @@ const populateTweetProperties = async (
|
|||
};
|
||||
|
||||
export const statusAPI = async (
|
||||
event: FetchEvent,
|
||||
status: string,
|
||||
language: string | undefined
|
||||
): Promise<APIResponse> => {
|
||||
const conversation = await fetchUsingGuest(status, event);
|
||||
const conversation = await fetchUsingGuest(status);
|
||||
const tweet = conversation?.globalObjects?.tweets?.[status] || {};
|
||||
|
||||
/* Fallback for if Tweet did not load */
|
||||
|
@ -168,14 +167,14 @@ export const statusAPI = async (
|
|||
return { code: 500, message: 'API_FAIL' };
|
||||
}
|
||||
|
||||
let response: APIResponse = { code: 200, message: 'OK' } as APIResponse;
|
||||
let apiTweet: APITweet = (await populateTweetProperties(
|
||||
const response: APIResponse = { code: 200, message: 'OK' } as APIResponse;
|
||||
const apiTweet: APITweet = (await populateTweetProperties(
|
||||
tweet,
|
||||
conversation,
|
||||
language
|
||||
)) as APITweet;
|
||||
|
||||
let quoteTweet =
|
||||
const quoteTweet =
|
||||
conversation.globalObjects?.tweets?.[tweet.quoted_status_id_str || '0'] || null;
|
||||
if (quoteTweet) {
|
||||
apiTweet.quote = (await populateTweetProperties(
|
||||
|
|
|
@ -3,13 +3,11 @@ import { calculateTimeLeftString } from './pollHelper';
|
|||
export const renderCard = async (
|
||||
card: TweetCard
|
||||
): Promise<{ poll?: APIPoll; external_media?: APIExternalMedia }> => {
|
||||
let str = '\n\n';
|
||||
const values = card.binding_values;
|
||||
|
||||
console.log('rendering card on ', card);
|
||||
|
||||
// Telegram's bars need to be a lot smaller to fit its bubbles
|
||||
let choices: { [label: string]: number } = {};
|
||||
const choices: { [label: string]: number } = {};
|
||||
let totalVotes = 0;
|
||||
|
||||
if (typeof values !== 'undefined') {
|
||||
|
@ -18,7 +16,7 @@ export const renderCard = async (
|
|||
typeof values.choice1_count !== 'undefined' &&
|
||||
typeof values.choice2_count !== 'undefined'
|
||||
) {
|
||||
let poll = {} as APIPoll;
|
||||
const poll = {} as APIPoll;
|
||||
|
||||
if (typeof values.end_datetime_utc !== 'undefined') {
|
||||
poll.ends_at = values.end_datetime_utc.string_value || '';
|
||||
|
|
14
src/fetch.ts
14
src/fetch.ts
|
@ -1,9 +1,6 @@
|
|||
import { Constants } from './constants';
|
||||
|
||||
export const fetchUsingGuest = async (
|
||||
status: string,
|
||||
event: FetchEvent
|
||||
): Promise<TimelineBlobPartial> => {
|
||||
export const fetchUsingGuest = async (status: string): Promise<TimelineBlobPartial> => {
|
||||
let apiAttempts = 0;
|
||||
let cachedTokenFailed = false;
|
||||
|
||||
|
@ -30,7 +27,7 @@ export const fetchUsingGuest = async (
|
|||
while (apiAttempts < 10) {
|
||||
const csrfToken = crypto.randomUUID().replace(/-/g, ''); // Generate a random CSRF token, this doesn't matter, Twitter just cares that header and cookie match
|
||||
|
||||
let headers: { [header: string]: string } = {
|
||||
const headers: { [header: string]: string } = {
|
||||
Authorization: Constants.GUEST_BEARER_TOKEN,
|
||||
...Constants.BASE_HEADERS
|
||||
};
|
||||
|
@ -40,7 +37,7 @@ export const fetchUsingGuest = async (
|
|||
let activate: Response | null = null;
|
||||
|
||||
if (!cachedTokenFailed) {
|
||||
let cachedResponse = await cache.match(guestTokenRequest);
|
||||
const cachedResponse = await cache.match(guestTokenRequest);
|
||||
|
||||
if (cachedResponse) {
|
||||
console.log('Token cache hit');
|
||||
|
@ -65,7 +62,7 @@ export const fetchUsingGuest = async (
|
|||
|
||||
try {
|
||||
activateJson = (await activate.json()) as { guest_token: string };
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -100,7 +97,7 @@ export const fetchUsingGuest = async (
|
|||
}
|
||||
);
|
||||
conversation = await apiRequest.json();
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
/* We'll usually only hit this if we get an invalid response from Twitter.
|
||||
It's rare, but it happens */
|
||||
console.error('Unknown error while fetching conversation from API');
|
||||
|
@ -124,6 +121,7 @@ export const fetchUsingGuest = async (
|
|||
return conversation;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - This is only returned if we completely failed to fetch the conversation
|
||||
return {};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ export const linkFixer = (tweet: TweetPartial, text: string): string => {
|
|||
text = text.replace(url.url, url.expanded_url);
|
||||
});
|
||||
|
||||
text = text.replace(/ ?https\:\/\/t\.co\/\w{10}/, '');
|
||||
text = text.replace(/ ?https:\/\/t\.co\/\w{10}/, '');
|
||||
}
|
||||
|
||||
return text;
|
||||
|
|
|
@ -3,11 +3,11 @@ import { Constants } from './constants';
|
|||
export const handleMosaic = async (
|
||||
mediaList: APIPhoto[]
|
||||
): Promise<APIMosaicPhoto | null> => {
|
||||
let mosaicDomains = Constants.MOSAIC_DOMAIN_LIST;
|
||||
const mosaicDomains = Constants.MOSAIC_DOMAIN_LIST;
|
||||
let selectedDomain: string | null = null;
|
||||
while (selectedDomain === null && mosaicDomains.length > 0) {
|
||||
// fetch /ping on a random domain
|
||||
let domain = mosaicDomains[Math.floor(Math.random() * mosaicDomains.length)];
|
||||
const domain = mosaicDomains[Math.floor(Math.random() * mosaicDomains.length)];
|
||||
// let response = await fetch(`https://${domain}/ping`);
|
||||
// if (response.status === 200) {
|
||||
selectedDomain = domain;
|
||||
|
@ -22,12 +22,12 @@ export const handleMosaic = async (
|
|||
return null;
|
||||
} else {
|
||||
// console.log('mediaList', mediaList);
|
||||
let mosaicMedia = mediaList.map(
|
||||
media => media.url?.match(/(?<=\/media\/)[a-zA-Z0-9_\-]+(?=[\.\?])/g)?.[0] || ''
|
||||
const mosaicMedia = mediaList.map(
|
||||
media => media.url?.match(/(?<=\/media\/)[\w-]+(?=[.?])/g)?.[0] || ''
|
||||
);
|
||||
// console.log('mosaicMedia', mosaicMedia);
|
||||
// TODO: use a better system for this, 0 gets png 1 gets webp, usually
|
||||
let baseUrl = `https://${selectedDomain}/`;
|
||||
const baseUrl = `https://${selectedDomain}/`;
|
||||
let path = '';
|
||||
|
||||
if (mosaicMedia[0]) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Constants } from './constants';
|
|||
|
||||
// https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
||||
const componentToHex = (component: number) => {
|
||||
let hex = component.toString(16);
|
||||
const hex = component.toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ export const handleQuote = (quote: APITweet): string | null => {
|
|||
|
||||
let str = `\n`;
|
||||
str += Strings.QUOTE_TEXT.format({
|
||||
name: quote.author?.name,
|
||||
screen_name: quote.author?.screen_name
|
||||
name: quote.author?.name || '',
|
||||
screen_name: quote.author?.screen_name || ''
|
||||
});
|
||||
|
||||
str += ` \n\n`;
|
||||
|
|
|
@ -14,8 +14,8 @@ const statusRequest = async (
|
|||
const url = new URL(request.url);
|
||||
const userAgent = request.headers.get('User-Agent') || '';
|
||||
|
||||
let isBotUA =
|
||||
userAgent.match(/bot|facebook|embed|got|Firefox\/92|curl|wget/gi) !== null;
|
||||
const isBotUA =
|
||||
userAgent.match(/bot|facebook|embed|got|firefox\/92|curl|wget/gi) !== null;
|
||||
|
||||
if (
|
||||
url.pathname.match(/\/status(es)?\/\d+\.(mp4|png|jpg)/g) !== null ||
|
||||
|
@ -40,8 +40,7 @@ const statusRequest = async (
|
|||
|
||||
let response: Response;
|
||||
|
||||
let statusResponse = await handleStatus(
|
||||
event,
|
||||
const statusResponse = await handleStatus(
|
||||
id?.match(/\d{2,20}/)?.[0] || '0',
|
||||
mediaNumber ? parseInt(mediaNumber) : undefined,
|
||||
userAgent,
|
||||
|
@ -72,16 +71,16 @@ const statusRequest = async (
|
|||
|
||||
return response;
|
||||
} else {
|
||||
console.log('Matched human UA', request.headers.get('User-Agent'));
|
||||
console.log('Matched human UA', userAgent);
|
||||
return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}/status/${id}`, 302);
|
||||
}
|
||||
};
|
||||
|
||||
const profileRequest = async (request: Request, _event: FetchEvent) => {
|
||||
const profileRequest = async (request: Request) => {
|
||||
const { handle } = request.params;
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (handle.match(/[a-z0-9_]{1,15}/gi)?.[0] !== handle) {
|
||||
if (handle.match(/\w{1,15}/gi)?.[0] !== handle) {
|
||||
return Response.redirect(Constants.REDIRECT_URL, 302);
|
||||
} else {
|
||||
return Response.redirect(`${Constants.TWITTER_ROOT}${url.pathname}`, 302);
|
||||
|
@ -106,9 +105,9 @@ router.get('/owoembed', async (request: Request) => {
|
|||
const { searchParams } = new URL(request.url);
|
||||
|
||||
/* Fallbacks */
|
||||
let text = searchParams.get('text') || 'Twitter';
|
||||
let author = searchParams.get('author') || 'dangeredwolf';
|
||||
let status = searchParams.get('status') || '1547514042146865153';
|
||||
const text = searchParams.get('text') || 'Twitter';
|
||||
const author = searchParams.get('author') || 'dangeredwolf';
|
||||
const status = searchParams.get('status') || '1547514042146865153';
|
||||
|
||||
const test = {
|
||||
author_name: decodeURIComponent(text),
|
||||
|
@ -133,7 +132,7 @@ router.get('/owoembed', async (request: Request) => {
|
|||
router.get('/:handle', profileRequest);
|
||||
router.get('/:handle/', profileRequest);
|
||||
|
||||
router.get('*', async (_request: Request) => {
|
||||
router.get('*', async () => {
|
||||
return Response.redirect(Constants.REDIRECT_URL, 307);
|
||||
});
|
||||
|
||||
|
@ -141,7 +140,6 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
|
|||
const { request } = event;
|
||||
const userAgent = request.headers.get('User-Agent') || '';
|
||||
// https://developers.cloudflare.com/workers/examples/cache-api/
|
||||
const url = new URL(request.url);
|
||||
const cacheUrl = new URL(
|
||||
userAgent.includes('Telegram')
|
||||
? `${request.url}&telegram`
|
||||
|
@ -169,7 +167,7 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
|
|||
switch (request.method) {
|
||||
case 'GET':
|
||||
if (cacheUrl.hostname !== Constants.API_HOST) {
|
||||
let cachedResponse = await cache.match(cacheKey);
|
||||
const cachedResponse = await cache.match(cacheKey);
|
||||
|
||||
if (cachedResponse) {
|
||||
console.log('Cache hit');
|
||||
|
@ -179,7 +177,8 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
|
|||
console.log('Cache miss');
|
||||
}
|
||||
|
||||
let response = await router.handle(event.request, event);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const response = await router.handle(event.request, event);
|
||||
|
||||
// Store the fetched response as cacheKey
|
||||
// Use waitUntil so you can return the response without blocking on
|
||||
|
|
|
@ -18,7 +18,6 @@ export const returnError = (error: string): StatusResponse => {
|
|||
};
|
||||
|
||||
export const handleStatus = async (
|
||||
event: FetchEvent,
|
||||
status: string,
|
||||
mediaNumber?: number,
|
||||
userAgent?: string,
|
||||
|
@ -27,7 +26,7 @@ export const handleStatus = async (
|
|||
): Promise<StatusResponse> => {
|
||||
console.log('Direct?', flags?.direct);
|
||||
|
||||
let api = await statusAPI(event, status, language);
|
||||
const api = await statusAPI(status, language);
|
||||
const tweet = api?.tweet as APITweet;
|
||||
|
||||
if (flags?.api) {
|
||||
|
@ -48,8 +47,7 @@ export const handleStatus = async (
|
|||
return returnError(Strings.ERROR_API_FAIL);
|
||||
}
|
||||
|
||||
if (flags?.direct) {
|
||||
if (tweet.media) {
|
||||
if (flags?.direct && tweet.media) {
|
||||
let redirectUrl: string | null = null;
|
||||
if (tweet.media.video) {
|
||||
redirectUrl = tweet.media.video.url;
|
||||
|
@ -60,7 +58,6 @@ export const handleStatus = async (
|
|||
return { response: Response.redirect(redirectUrl, 302) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Use quote media if there is no media */
|
||||
if (!tweet.media && tweet.quote?.media) {
|
||||
|
@ -69,11 +66,11 @@ export const handleStatus = async (
|
|||
}
|
||||
|
||||
let authorText = getAuthorText(tweet) || Strings.DEFAULT_AUTHOR_TEXT;
|
||||
let engagementText = authorText.replace(/ /g, ' ');
|
||||
let siteName = Constants.BRANDING_NAME;
|
||||
const engagementText = authorText.replace(/ {4}/g, ' ');
|
||||
const siteName = Constants.BRANDING_NAME;
|
||||
let newText = tweet.text;
|
||||
|
||||
let headers: string[] = [
|
||||
const headers = [
|
||||
`<meta content="${tweet.color}" property="theme-color"/>`,
|
||||
`<meta name="twitter:card" content="${tweet.twitter_card}"/>`,
|
||||
`<meta name="twitter:site" content="@${tweet.author.screen_name}"/>`,
|
||||
|
@ -84,7 +81,7 @@ export const handleStatus = async (
|
|||
if (tweet.translation) {
|
||||
const { translation } = tweet;
|
||||
|
||||
let formatText =
|
||||
const formatText =
|
||||
language === 'en'
|
||||
? Strings.TRANSLATE_TEXT.format({
|
||||
language: translation.source_lang
|
||||
|
@ -140,9 +137,9 @@ export const handleStatus = async (
|
|||
type: 'photo'
|
||||
};
|
||||
} else if (photos.length > 1) {
|
||||
let photoCounter = Strings.PHOTO_COUNT.format({
|
||||
number: photos.indexOf(photo) + 1,
|
||||
total: photos.length
|
||||
const photoCounter = Strings.PHOTO_COUNT.format({
|
||||
number: String(photos.indexOf(photo) + 1),
|
||||
total: String(photos.length)
|
||||
});
|
||||
|
||||
authorText =
|
||||
|
@ -239,13 +236,13 @@ ${choice.label} (${choice.percentage}%)
|
|||
headers.push(
|
||||
`<link rel="alternate" href="${Constants.HOST_URL}/owoembed?text=${encodeURIComponent(
|
||||
authorText
|
||||
)}&status=${encodeURIComponent(status).substr(1, 240)}&author=${encodeURIComponent(
|
||||
)}&status=${encodeURIComponent(status)}&author=${encodeURIComponent(
|
||||
tweet.author?.screen_name || ''
|
||||
)}" type="application/json+oembed" title="${tweet.author.name}">`
|
||||
);
|
||||
|
||||
/* When dealing with a Tweet of unknown lang, fall back to en */
|
||||
let lang = tweet.lang === 'unk' ? 'en' : tweet.lang || 'en';
|
||||
const lang = tweet.lang === 'unk' ? 'en' : tweet.lang || 'en';
|
||||
|
||||
return {
|
||||
text: Strings.BASE_HTML.format({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
declare global {
|
||||
interface String {
|
||||
format(options: any): string;
|
||||
format(options: { [find: string]: string }): string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ declare global {
|
|||
Useful little function to format strings for us
|
||||
*/
|
||||
|
||||
String.prototype.format = function (options: any) {
|
||||
String.prototype.format = function (options: { [find: string]: string }) {
|
||||
return this.replace(/{([^{}]+)}/g, (match: string, name: string) => {
|
||||
if (options[name] !== undefined) {
|
||||
return options[name];
|
||||
|
|
|
@ -7,7 +7,7 @@ export const translateTweet = async (
|
|||
): Promise<TranslationPartial | null> => {
|
||||
const csrfToken = crypto.randomUUID().replace(/-/g, ''); // Generate a random CSRF token, this doesn't matter, Twitter just cares that header and cookie match
|
||||
|
||||
let headers: { [header: string]: string } = {
|
||||
const headers: { [header: string]: string } = {
|
||||
'Authorization': Constants.GUEST_BEARER_TOKEN,
|
||||
...Constants.BASE_HEADERS,
|
||||
'Cookie': [
|
||||
|
@ -42,7 +42,7 @@ export const translateTweet = async (
|
|||
|
||||
console.log(translationResults);
|
||||
return translationResults;
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('Unknown error while fetching from Translation API', e);
|
||||
return {} as TranslationPartial; // No work to do
|
||||
}
|
||||
|
|
|
@ -100,8 +100,7 @@ type CardValue = {
|
|||
string_value: string;
|
||||
};
|
||||
|
||||
type TweetCard = {
|
||||
binding_values: {
|
||||
type TweetCardBindingValues = {
|
||||
card_url: CardValue;
|
||||
choice1_count?: CardValue;
|
||||
choice2_count?: CardValue;
|
||||
|
@ -119,7 +118,10 @@ type TweetCard = {
|
|||
player_width?: CardValue;
|
||||
player_height?: CardValue;
|
||||
title?: CardValue;
|
||||
};
|
||||
};
|
||||
|
||||
type TweetCard = {
|
||||
binding_values: TweetCardBindingValues;
|
||||
name: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export const sanitizeText = (text: string) => {
|
||||
return text
|
||||
.replace(/\"/g, '"')
|
||||
.replace(/\'/g, ''')
|
||||
.replace(/\</g, '<')
|
||||
.replace(/\>/g, '>');
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue