Skip to content

Commit 87bf6d4

Browse files
committed
feat: added isTwitterUrl and isInstagramUrl
1 parent 670b1dd commit 87bf6d4

7 files changed

Lines changed: 324 additions & 0 deletions

File tree

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,42 @@ isFacebookUrl("ftp://facebook.com"); // false
127127
isFacebookUrl("not-a-url"); // false
128128
```
129129

130+
### `isTwitterUrl`
131+
132+
Validates if a given string is a valid Twitter/X URL.
133+
134+
**Usage:**
135+
136+
```typescript
137+
import { isTwitterUrl } from "largs-utils";
138+
139+
isTwitterUrl("https://twitter.com/username"); // true
140+
isTwitterUrl("https://www.twitter.com/status/123"); // true
141+
isTwitterUrl("https://x.com/username"); // true
142+
isTwitterUrl("https://www.x.com/status/123"); // true
143+
isTwitterUrl("https://mobile.twitter.com/username"); // true
144+
isTwitterUrl("https://google.com"); // false
145+
isTwitterUrl("ftp://twitter.com"); // false
146+
isTwitterUrl("not-a-url"); // false
147+
```
148+
149+
### `isInstagramUrl`
150+
151+
Validates if a given string is a valid Instagram URL.
152+
153+
**Usage:**
154+
155+
```typescript
156+
import { isInstagramUrl } from "largs-utils";
157+
158+
isInstagramUrl("https://instagram.com/username"); // true
159+
isInstagramUrl("https://www.instagram.com/p/ABC123"); // true
160+
isInstagramUrl("https://mobile.instagram.com/username"); // true
161+
isInstagramUrl("https://google.com"); // false
162+
isInstagramUrl("ftp://instagram.com"); // false
163+
isInstagramUrl("not-a-url"); // false
164+
```
165+
130166
### `isValidHttpUrl`
131167

132168
Validates if a given string is a valid URL
File renamed without changes.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { isInstagramUrl } from "../isInstagramUrl";
2+
3+
describe("isInstagramUrl", () => {
4+
describe("Valid Instagram URLs", () => {
5+
it("should return true for instagram.com URLs with http protocol", () => {
6+
expect(isInstagramUrl("http://instagram.com")).toBe(true);
7+
expect(isInstagramUrl("http://instagram.com/username")).toBe(true);
8+
expect(isInstagramUrl("http://instagram.com/p/ABC123")).toBe(true);
9+
});
10+
11+
it("should return true for instagram.com URLs with https protocol", () => {
12+
expect(isInstagramUrl("https://instagram.com")).toBe(true);
13+
expect(isInstagramUrl("https://instagram.com/username")).toBe(true);
14+
expect(isInstagramUrl("https://instagram.com/p/ABC123")).toBe(true);
15+
});
16+
17+
it("should return true for www.instagram.com URLs", () => {
18+
expect(isInstagramUrl("http://www.instagram.com")).toBe(true);
19+
expect(isInstagramUrl("https://www.instagram.com")).toBe(true);
20+
expect(isInstagramUrl("https://www.instagram.com/username")).toBe(true);
21+
});
22+
23+
it("should return true for subdomains of instagram.com", () => {
24+
expect(isInstagramUrl("https://mobile.instagram.com")).toBe(true);
25+
expect(isInstagramUrl("https://api.instagram.com")).toBe(true);
26+
expect(isInstagramUrl("https://developer.instagram.com")).toBe(true);
27+
expect(isInstagramUrl("http://custom.instagram.com/page")).toBe(true);
28+
});
29+
30+
it("should handle URLs with trailing whitespace", () => {
31+
expect(isInstagramUrl(" https://instagram.com ")).toBe(true);
32+
expect(isInstagramUrl("\t https://www.instagram.com \n")).toBe(true);
33+
});
34+
});
35+
36+
describe("Invalid Instagram URLs", () => {
37+
it("should return false for non-Instagram URLs", () => {
38+
expect(isInstagramUrl("https://google.com")).toBe(false);
39+
expect(isInstagramUrl("https://facebook.com")).toBe(false);
40+
expect(isInstagramUrl("https://twitter.com")).toBe(false);
41+
expect(isInstagramUrl("https://example.com")).toBe(false);
42+
expect(isInstagramUrl("https://notinstagram.com")).toBe(false);
43+
});
44+
45+
it("should return false for URLs with invalid protocols", () => {
46+
expect(isInstagramUrl("ftp://instagram.com")).toBe(false);
47+
expect(isInstagramUrl("file://instagram.com")).toBe(false);
48+
expect(isInstagramUrl("ws://instagram.com")).toBe(false);
49+
expect(isInstagramUrl("instagram.com")).toBe(false); // missing protocol
50+
});
51+
52+
it("should return false for domains that only contain 'instagram' but are not Instagram domains", () => {
53+
expect(isInstagramUrl("https://instagram-clone.com")).toBe(false);
54+
expect(isInstagramUrl("https://myinstagram.com")).toBe(false);
55+
expect(isInstagramUrl("https://instagram.example.com")).toBe(false);
56+
expect(isInstagramUrl("https://instagram.net")).toBe(false);
57+
});
58+
59+
it("should return false for malformed URLs", () => {
60+
expect(isInstagramUrl("not-a-url")).toBe(false);
61+
expect(isInstagramUrl("://instagram.com")).toBe(false);
62+
expect(isInstagramUrl("https://")).toBe(false);
63+
expect(isInstagramUrl("invalid://instagram.com")).toBe(false);
64+
});
65+
66+
it("should return false for empty or whitespace-only strings", () => {
67+
expect(isInstagramUrl("")).toBe(false);
68+
expect(isInstagramUrl(" ")).toBe(false);
69+
expect(isInstagramUrl("\t")).toBe(false);
70+
expect(isInstagramUrl("\n")).toBe(false);
71+
});
72+
73+
it("should return false for URLs with instagram.com as path but different domain", () => {
74+
expect(isInstagramUrl("https://example.com/instagram.com")).toBe(false);
75+
expect(isInstagramUrl("https://evil.com/instagram.com/login")).toBe(
76+
false
77+
);
78+
});
79+
});
80+
81+
describe("Edge cases", () => {
82+
it("should handle case-insensitive domain matching", () => {
83+
expect(isInstagramUrl("https://INSTAGRAM.COM")).toBe(true);
84+
expect(isInstagramUrl("https://Instagram.Com")).toBe(true);
85+
expect(isInstagramUrl("https://WWW.INSTAGRAM.COM")).toBe(true);
86+
});
87+
88+
it("should handle complex Instagram URLs", () => {
89+
expect(
90+
isInstagramUrl("https://instagram.com/username/p/ABC123DEF456/")
91+
).toBe(true);
92+
expect(
93+
isInstagramUrl(
94+
"https://www.instagram.com/p/ABC123/?utm_source=ig_web_copy_link"
95+
)
96+
).toBe(true);
97+
expect(
98+
isInstagramUrl("https://instagram.com/reel/ABC123DEF456/?igshid=abc123")
99+
).toBe(true);
100+
});
101+
102+
it("should handle URLs with ports", () => {
103+
expect(isInstagramUrl("https://instagram.com:443")).toBe(true);
104+
expect(isInstagramUrl("http://instagram.com:80")).toBe(true);
105+
expect(isInstagramUrl("https://instagram.com:8080")).toBe(true);
106+
});
107+
108+
it("should handle URLs with authentication info", () => {
109+
expect(isInstagramUrl("https://user:pass@instagram.com")).toBe(true);
110+
expect(isInstagramUrl("https://user@instagram.com")).toBe(true);
111+
});
112+
});
113+
});

src/__tests__/isTwitterUrl.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { isTwitterUrl } from "../isXUrl";
2+
3+
describe("isTwitterUrl", () => {
4+
describe("Valid Twitter/X URLs", () => {
5+
it("should return true for twitter.com URLs with http protocol", () => {
6+
expect(isTwitterUrl("http://twitter.com")).toBe(true);
7+
expect(isTwitterUrl("http://twitter.com/username")).toBe(true);
8+
expect(isTwitterUrl("http://twitter.com/status/123")).toBe(true);
9+
});
10+
11+
it("should return true for twitter.com URLs with https protocol", () => {
12+
expect(isTwitterUrl("https://twitter.com")).toBe(true);
13+
expect(isTwitterUrl("https://twitter.com/username")).toBe(true);
14+
expect(isTwitterUrl("https://twitter.com/status/123")).toBe(true);
15+
});
16+
17+
it("should return true for www.twitter.com URLs", () => {
18+
expect(isTwitterUrl("http://www.twitter.com")).toBe(true);
19+
expect(isTwitterUrl("https://www.twitter.com")).toBe(true);
20+
expect(isTwitterUrl("https://www.twitter.com/username")).toBe(true);
21+
});
22+
23+
it("should return true for x.com URLs", () => {
24+
expect(isTwitterUrl("http://x.com")).toBe(true);
25+
expect(isTwitterUrl("https://x.com")).toBe(true);
26+
expect(isTwitterUrl("https://x.com/username")).toBe(true);
27+
});
28+
29+
it("should return true for www.x.com URLs", () => {
30+
expect(isTwitterUrl("http://www.x.com")).toBe(true);
31+
expect(isTwitterUrl("https://www.x.com")).toBe(true);
32+
expect(isTwitterUrl("https://www.x.com/username")).toBe(true);
33+
});
34+
35+
it("should return true for subdomains of twitter.com", () => {
36+
expect(isTwitterUrl("https://mobile.twitter.com")).toBe(true);
37+
expect(isTwitterUrl("https://api.twitter.com")).toBe(true);
38+
expect(isTwitterUrl("https://developer.twitter.com")).toBe(true);
39+
expect(isTwitterUrl("http://custom.twitter.com/page")).toBe(true);
40+
});
41+
42+
it("should handle URLs with trailing whitespace", () => {
43+
expect(isTwitterUrl(" https://twitter.com ")).toBe(true);
44+
expect(isTwitterUrl("\t https://www.x.com \n")).toBe(true);
45+
});
46+
});
47+
48+
describe("Invalid Twitter/X URLs", () => {
49+
it("should return false for non-Twitter/X URLs", () => {
50+
expect(isTwitterUrl("https://google.com")).toBe(false);
51+
expect(isTwitterUrl("https://facebook.com")).toBe(false);
52+
expect(isTwitterUrl("https://example.com")).toBe(false);
53+
expect(isTwitterUrl("https://nottwitter.com")).toBe(false);
54+
});
55+
56+
it("should return false for URLs with invalid protocols", () => {
57+
expect(isTwitterUrl("ftp://twitter.com")).toBe(false);
58+
expect(isTwitterUrl("file://twitter.com")).toBe(false);
59+
expect(isTwitterUrl("ws://twitter.com")).toBe(false);
60+
expect(isTwitterUrl("twitter.com")).toBe(false); // missing protocol
61+
});
62+
63+
it("should return false for domains that only contain 'twitter' but are not Twitter domains", () => {
64+
expect(isTwitterUrl("https://twitter-clone.com")).toBe(false);
65+
expect(isTwitterUrl("https://mytwitter.com")).toBe(false);
66+
expect(isTwitterUrl("https://twitter.example.com")).toBe(false);
67+
expect(isTwitterUrl("https://twitter.net")).toBe(false);
68+
});
69+
70+
it("should return false for domains that only contain 'x' but are not X domains", () => {
71+
expect(isTwitterUrl("https://x-clone.com")).toBe(false);
72+
expect(isTwitterUrl("https://myx.com")).toBe(false);
73+
expect(isTwitterUrl("https://x.example.com")).toBe(false);
74+
expect(isTwitterUrl("https://x.net")).toBe(false);
75+
});
76+
77+
it("should return false for malformed URLs", () => {
78+
expect(isTwitterUrl("not-a-url")).toBe(false);
79+
expect(isTwitterUrl("://twitter.com")).toBe(false);
80+
expect(isTwitterUrl("https://")).toBe(false);
81+
expect(isTwitterUrl("invalid://twitter.com")).toBe(false);
82+
});
83+
84+
it("should return false for empty or whitespace-only strings", () => {
85+
expect(isTwitterUrl("")).toBe(false);
86+
expect(isTwitterUrl(" ")).toBe(false);
87+
expect(isTwitterUrl("\t")).toBe(false);
88+
expect(isTwitterUrl("\n")).toBe(false);
89+
});
90+
91+
it("should return false for URLs with twitter.com as path but different domain", () => {
92+
expect(isTwitterUrl("https://example.com/twitter.com")).toBe(false);
93+
expect(isTwitterUrl("https://evil.com/twitter.com/login")).toBe(false);
94+
});
95+
});
96+
97+
describe("Edge cases", () => {
98+
it("should handle case-insensitive domain matching", () => {
99+
expect(isTwitterUrl("https://TWITTER.COM")).toBe(true);
100+
expect(isTwitterUrl("https://Twitter.Com")).toBe(true);
101+
expect(isTwitterUrl("https://WWW.X.COM")).toBe(true);
102+
expect(isTwitterUrl("https://X.COM")).toBe(true);
103+
});
104+
105+
it("should handle complex Twitter/X URLs", () => {
106+
expect(
107+
isTwitterUrl("https://twitter.com/username/status/1234567890123456789")
108+
).toBe(true);
109+
expect(
110+
isTwitterUrl("https://x.com/username/status/1234567890123456789")
111+
).toBe(true);
112+
expect(
113+
isTwitterUrl(
114+
"https://twitter.com/i/status/1234567890123456789?s=20&t=abc123"
115+
)
116+
).toBe(true);
117+
});
118+
119+
it("should handle URLs with ports", () => {
120+
expect(isTwitterUrl("https://twitter.com:443")).toBe(true);
121+
expect(isTwitterUrl("http://twitter.com:80")).toBe(true);
122+
expect(isTwitterUrl("https://x.com:8080")).toBe(true);
123+
});
124+
125+
it("should handle URLs with authentication info", () => {
126+
expect(isTwitterUrl("https://user:pass@twitter.com")).toBe(true);
127+
expect(isTwitterUrl("https://user@x.com")).toBe(true);
128+
});
129+
});
130+
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export { generatePrefixedId } from "./generatePrefixedId";
66
export { generateSlug } from "./generateSlug";
77
export { getYoutubeThumbnail } from "./getYoutubeThumbnail";
88
export { isFacebookUrl } from "./isFacebookUrl";
9+
export { isInstagramUrl } from "./isInstagramUrl";
10+
export { isTwitterUrl } from "./isXUrl";
911
export { isValidEmail } from "./isValidEmail";
1012
export { isValidGoogleMapsUrl } from "./isValidGoogleMapsUrl";
1113
export { isValidHttpUrl } from "./isValidHttpUrl";

src/isInstagramUrl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const isInstagramUrl = (instagramUrl: string): boolean => {
2+
try {
3+
const trimmed = instagramUrl.trim();
4+
const url = new URL(trimmed);
5+
const hostname = url.hostname.toLowerCase();
6+
const protocol = url.protocol;
7+
8+
const validProtocols = ["http:", "https:"];
9+
const validDomains = ["instagram.com", "www.instagram.com"];
10+
11+
const isValidProtocol = validProtocols.includes(protocol);
12+
const isInstagramDomain =
13+
validDomains.includes(hostname) || hostname.endsWith(".instagram.com");
14+
15+
return isValidProtocol && isInstagramDomain;
16+
} catch {
17+
return false;
18+
}
19+
};

src/isXUrl.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const isTwitterUrl = (twitterUrl: string): boolean => {
2+
try {
3+
const trimmed = twitterUrl.trim();
4+
const url = new URL(trimmed);
5+
const hostname = url.hostname.toLowerCase();
6+
const protocol = url.protocol;
7+
8+
const validProtocols = ["http:", "https:"];
9+
const validDomains = [
10+
"twitter.com",
11+
"www.twitter.com",
12+
"x.com",
13+
"www.x.com",
14+
];
15+
16+
const isValidProtocol = validProtocols.includes(protocol);
17+
const isTwitterDomain =
18+
validDomains.includes(hostname) || hostname.endsWith(".twitter.com");
19+
20+
return isValidProtocol && isTwitterDomain;
21+
} catch {
22+
return false;
23+
}
24+
};

0 commit comments

Comments
 (0)