diff --git a/Caddyfile b/Caddyfile index 3ce3169ef..38c6f5775 100644 --- a/Caddyfile +++ b/Caddyfile @@ -132,6 +132,10 @@ header @html Content-Security-Policy " https://youtube.com https://player.vimeo.com https://www.tiktok.com + https://platform.twitter.com + https://twitter.com + https://platform.x.com + https://x.com https://giphy.com https://open.spotify.com https://embed-standalone.spotify.com diff --git a/src/assets/i18n-02-07-23/en.json b/src/assets/i18n-02-07-23/en.json index d69687db9..95c07ae71 100644 --- a/src/assets/i18n-02-07-23/en.json +++ b/src/assets/i18n-02-07-23/en.json @@ -201,7 +201,7 @@ "multi_post_progress": "Submitting posts:", "post": "Post", "embed_image": "Link to Arweave image", - "embed_video": "Embed Youtube, Vimeo, TikTok, Giphy, Spotify, Mousai, or SoundCloud", + "embed_video": "Embed Youtube, Vimeo, TikTok, Twitter, Giphy, Spotify, Mousai, or SoundCloud", "quotes": { "quote1": "Go ahead, make my day.", "quote2": "The stuff that dreams are made of.", diff --git a/src/assets/i18n-02-07-23/fr.json b/src/assets/i18n-02-07-23/fr.json index 71b56d18d..9a39fbc94 100644 --- a/src/assets/i18n-02-07-23/fr.json +++ b/src/assets/i18n-02-07-23/fr.json @@ -200,7 +200,7 @@ "complete":"Terminé", "post": "Message", "embed_image": "Lien vers une image Arweave", - "embed_video": "Intégrer Youtube, Vimeo, TikTok, Giphy, Spotify ou SoundCloud", + "embed_video": "Intégrer Youtube, Vimeo, TikTok, Twitter, Giphy, Spotify ou SoundCloud", "quotes": { "quote1": "Vas-y, refait ma journée.", "quote2": "Les choses dont les rêves sont fait.", diff --git a/src/assets/i18n-02-07-23/nl.json b/src/assets/i18n-02-07-23/nl.json index ef286a973..3523a73b4 100644 --- a/src/assets/i18n-02-07-23/nl.json +++ b/src/assets/i18n-02-07-23/nl.json @@ -196,7 +196,7 @@ "complete": "Voltooid", "post": "Plaats", "embed_image": "Voeg een link naar een Arweave afbeelding toe", - "embed_video": "Voeg je Youtube, Vimeo, TikTok, Giphy, Spotify of SoundCloud video toe", + "embed_video": "Voeg je Youtube, Vimeo, TikTok, Twitter, Giphy, Spotify of SoundCloud video toe", "quotes": { "quote1": "Het leven is wat je vandaag viert.", "quote2": "Geef wat je ontvangt, Ontvang wat je krijgt, Waardeer wat je hebt.", diff --git a/src/lib/services/embed-url-parser-service/embed-url-parser-service.spec.ts b/src/lib/services/embed-url-parser-service/embed-url-parser-service.spec.ts index 576f29617..eddeae55f 100644 --- a/src/lib/services/embed-url-parser-service/embed-url-parser-service.spec.ts +++ b/src/lib/services/embed-url-parser-service/embed-url-parser-service.spec.ts @@ -69,6 +69,13 @@ describe("EmbedUrlParserService", () => { const validTikTokEmbedURLs = [`https://www.tiktok.com/embed/v2/${tiktokVideoID}`]; + const twitterPostID = "1819749725568110806"; + const validTwitterURLs = [ + `https://twitter.com/teslaownersSV/status/${twitterPostID}`, + `https://x.com/teslaownersSV/status/${twitterPostID}`, + ]; + const validTwitterEmbedURLs = [`https://platform.twitter.com/embed/Tweet.html?id=${twitterPostID}`]; + const giphyID = "J1ABRhlfvQNwIOiAas"; const validGiphyURLs = [ `https://giphy.com/gifs/memecandy-${giphyID}`, @@ -149,6 +156,7 @@ describe("EmbedUrlParserService", () => { "123abc.com/1234556", `https://wwwzyoutube.com/embed/${youtubeVideoID}`, `https://nottiktok.com/embed/v2/${tiktokShortVideoID}`, + `https://notx.com/embed/Tweet.html?id=${twitterPostID}`, `https://giphy.com/gifs/abc-def-${giphyID}-;`, `https://open.notspotify.com/embed/track/${spotifyID}-?;`, `https://w.soundcloud.com/player/?hide_related=true&show_comments=false`, @@ -230,6 +238,22 @@ describe("EmbedUrlParserService", () => { } }); + it("parses twitter URLs from user input correctly and only validates embed urls", () => { + for (const link of validTwitterURLs) { + expect(EmbedUrlParserService.isTwitterLink(link)).toBeTruthy(); + const embedURL = EmbedUrlParserService.constructTwitterEmbedURL(new URL(link)); + expect(embedURL).toEqual(`https://platform.twitter.com/embed/Tweet.html?id=${twitterPostID}`); + expect(EmbedUrlParserService.isValidEmbedURL(embedURL)).toBeTruthy(); + expect(EmbedUrlParserService.isValidEmbedURL(link)).toBeFalsy(); + } + for (const embedLink of validTwitterEmbedURLs) { + expect(EmbedUrlParserService.isTwitterLink(embedLink)).toBeTruthy(); + expect(EmbedUrlParserService.isValidEmbedURL(embedLink)).toBeTruthy(); + const constructedEmbedURL = EmbedUrlParserService.constructTwitterEmbedURL(new URL(embedLink)); + expect(EmbedUrlParserService.isValidEmbedURL(constructedEmbedURL)).toBeTruthy(); + } + }); + it("parses giphy URLs from user input correctly and only validates embed urls", () => { for (const link of validGiphyURLs) { expect(EmbedUrlParserService.isGiphyLink(link)).toBeTruthy(); diff --git a/src/lib/services/embed-url-parser-service/embed-url-parser-service.ts b/src/lib/services/embed-url-parser-service/embed-url-parser-service.ts index 341ced2cf..f5486a085 100644 --- a/src/lib/services/embed-url-parser-service/embed-url-parser-service.ts +++ b/src/lib/services/embed-url-parser-service/embed-url-parser-service.ts @@ -179,6 +179,24 @@ export class EmbedUrlParserService { ); } + static twitterParser(url): string | boolean { + const regExp = /status\/(\d+)/; + const match = url.match(regExp); + if(match && match[1].length >= 1) { + return match[1] + } else { + const regExp2 = /id=(\d+)/; + const match2 = url.match(regExp2); + return match2 && match2[1].length >= 1 ? match2[1] : false; + } + } + + static constructTwitterEmbedURL(url: URL): string { + const twitterPostID = this.twitterParser(url.toString()); + // If we can't find the postID, return the empty string which stops the iframe from loading. + return twitterPostID ? `https://platform.twitter.com/embed/Tweet.html?id=${twitterPostID}` : ""; + } + static getEmbedURL( backendApi: BackendApiService, globalVars: GlobalVarsService, @@ -209,6 +227,9 @@ export class EmbedUrlParserService { if (this.isTiktokFromURL(url)) { return this.constructTikTokEmbedURL(backendApi, globalVars, url); } + if (this.isTwitterFromURL(url)) { + return of(this.constructTwitterEmbedURL(url)); + } if (this.isGiphyFromURL(url)) { return of(this.constructGiphyEmbedURL(url)); } @@ -282,6 +303,20 @@ export class EmbedUrlParserService { return pattern.test(url.hostname); } + static isTwitterLink(link: string): boolean { + try { + const url = new URL(link); + return this.isTwitterFromURL(url); + } catch (e) { + return false; + } + } + + static isTwitterFromURL(url: URL): boolean { + const patterns = [/\btwitter\.com$/, /\bx\.com$/]; + return patterns.some((p) => p.test(url.hostname)); + } + static isGiphyLink(link: string): boolean { try { const url = new URL(link); @@ -358,6 +393,11 @@ export class EmbedUrlParserService { return !!link.match(regExp); } + static isValidTwitterEmbedURL(link: string): boolean { + const regExp = /(https:\/\/platform\.twitter\.com\/embed\/Tweet.html\?id=(\d{0,30}))$/; + return !!link.match(regExp); + } + static isValidGiphyEmbedURL(link: string): boolean { const regExp = /(https:\/\/giphy\.com\/embed\/([A-Za-z0-9]{0,20}))$/; return !!link.match(regExp); @@ -395,6 +435,7 @@ export class EmbedUrlParserService { this.isValidYoutubeEmbedURL(link) || this.isValidMousaiEmbedURL(link) || this.isValidTiktokEmbedURL(link) || + this.isValidTwitterEmbedURL(link) || this.isValidGiphyEmbedURL(link) || this.isValidSpotifyEmbedURL(link) || this.isValidSoundCloudEmbedURL(link) || @@ -407,7 +448,16 @@ export class EmbedUrlParserService { static getEmbedHeight(link: string): number { if (this.isValidTiktokEmbedURL(link)) { - return 700; + return 760; + } + if (this.isTikTokLink(link)) { + return 760; + } + if (this.isValidTwitterEmbedURL(link)) { + return 500; + } + if (this.isTwitterLink(link)) { + return 500; } if (this.isValidSpotifyEmbedURL(link)) { return link.indexOf("embed-podcast") > -1 ? 232 : 380; diff --git a/src/styles.scss b/src/styles.scss index 920f98fc9..2b5b049f3 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -2691,7 +2691,7 @@ bs-dropdown-container { justify-content: center; align-items: center; overflow: hidden; - max-height: 700px; + max-height: 760px; border-radius: 6px; margin-top: 10px; margin-bottom: 5px;