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;