diff --git a/app/(protected)/dashboard/urls/meta-chart.tsx b/app/(protected)/dashboard/urls/meta-chart.tsx
index 4ec5f65..9914a55 100644
--- a/app/(protected)/dashboard/urls/meta-chart.tsx
+++ b/app/(protected)/dashboard/urls/meta-chart.tsx
@@ -8,7 +8,7 @@ import { Donut, MapData, TopoJSONMap } from "@unovis/ts";
import { WorldMapTopoJSON } from "@unovis/ts/maps";
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
-import { isLink, timeAgo } from "@/lib/utils";
+import { isLink, removeUrlSuffix, timeAgo } from "@/lib/utils";
import {
Card,
CardContent,
@@ -278,9 +278,7 @@ export function StatsList({ data, title }: { data: Stat[]; title: string }) {
className="truncate font-medium hover:opacity-70 hover:after:content-['↗']"
href={ref.dimension}
>
- {ref.dimension.startsWith("http")
- ? ref.dimension.split("//")[1]
- : ref.dimension}
+ {removeUrlSuffix(ref.dimension)}
) : (
{decodeURIComponent(ref.dimension)}
diff --git a/app/(protected)/dashboard/urls/url-list.tsx b/app/(protected)/dashboard/urls/url-list.tsx
index 27bacb7..6c476b1 100644
--- a/app/(protected)/dashboard/urls/url-list.tsx
+++ b/app/(protected)/dashboard/urls/url-list.tsx
@@ -8,7 +8,13 @@ import useSWR, { useSWRConfig } from "swr";
import { siteConfig } from "@/config/site";
import { ShortUrlFormData } from "@/lib/dto/short-urls";
-import { cn, expirationTime, fetcher, timeAgo } from "@/lib/utils";
+import {
+ cn,
+ expirationTime,
+ fetcher,
+ removeUrlSuffix,
+ timeAgo,
+} from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -49,8 +55,8 @@ export interface UrlListProps {
function TableColumnSekleton() {
return (
-
-
+
+
@@ -157,8 +163,8 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
)}
-
-
+
+
Url
@@ -193,9 +199,9 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
<>
-
+
-
-
- {short.target.startsWith("http")
- ? short.target.split("//")[1]
- : short.target}
-
+
+
+
+
+ {short.target}
+
+
+
+ {removeUrlSuffix(short.target)}
+
+
+
+
-
+
diff --git a/app/api/scraping/route.ts b/app/api/scraping/route.ts
new file mode 100644
index 0000000..34d233a
--- /dev/null
+++ b/app/api/scraping/route.ts
@@ -0,0 +1,50 @@
+import cheerio from "cheerio";
+
+export const revalidate = 60;
+
+export async function GET(req: Request) {
+ try {
+ const url = new URL(req.url);
+ const link = url.searchParams.get("link");
+ if (!link) {
+ return Response.json("link is required", {
+ status: 400,
+ statusText: "link is required",
+ });
+ }
+
+ const res = await fetch(link);
+ if (!res.ok) {
+ return Response.json("Failed to fetch url", {
+ status: 405,
+ statusText: "Failed to fetch url",
+ });
+ }
+
+ const html = await res.text();
+ console.log(html);
+
+ const $ = cheerio.load(html);
+ const title =
+ $("title").text() ||
+ $("meta[property='og:title']").attr("content") ||
+ $("meta[name='twitter:title']").attr("content");
+ const description =
+ $("meta[name='description']").attr("content") ||
+ $("meta[property='og:description']").attr("content") ||
+ $("meta[name='twitter:description']").attr("content");
+ const image =
+ $("meta[property='og:image']").attr("content") ||
+ $("meta[name='og:image']").attr("content") ||
+ $("meta[property='twitter:image']").attr("content") ||
+ $("meta[name='twitter:image']").attr("content");
+
+ return Response.json({ title, description, image });
+ } catch (error) {
+ console.log(error);
+ return Response.json(error?.statusText || error, {
+ status: error.status || 500,
+ statusText: error.statusText || "Server error",
+ });
+ }
+}
diff --git a/lib/utils.ts b/lib/utils.ts
index 7c1114d..30c9ae6 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -216,3 +216,7 @@ export function isLink(str: string): boolean {
return false;
}
}
+
+export function removeUrlSuffix(url: string): string {
+ return url.startsWith("http") ? url.split("//")[1] : url;
+}
diff --git a/package.json b/package.json
index e6e6dbb..691b7ad 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
"@vercel/analytics": "^1.3.1",
"@vercel/functions": "^1.4.0",
"@vercel/og": "^0.6.2",
+ "cheerio": "1.0.0-rc.12",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ec934c..bab6ebe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -134,6 +134,9 @@ importers:
'@vercel/og':
specifier: ^0.6.2
version: 0.6.2
+ cheerio:
+ specifier: 1.0.0-rc.12
+ version: 1.0.0-rc.12
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -3170,6 +3173,9 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -3243,6 +3249,13 @@ packages:
character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ cheerio-select@2.1.0:
+ resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
+
+ cheerio@1.0.0-rc.12:
+ resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
+ engines: {node: '>= 6'}
+
chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@@ -3450,9 +3463,16 @@ packages:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
+ css-select@5.1.0:
+ resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+
css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
+ css-what@6.1.0:
+ resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+ engines: {node: '>= 6'}
+
csscolorparser@1.0.3:
resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==}
@@ -5174,6 +5194,9 @@ packages:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
oauth4webapi@2.10.3:
resolution: {integrity: sha512-9FkXEXfzVKzH63GUOZz1zMr3wBaICSzk6DLXx+CGdrQ10ItNk2ePWzYYc1fdmKq1ayGFb2aX97sRCoZ2s0mkDw==}
@@ -5276,6 +5299,9 @@ packages:
parse-unit@1.0.1:
resolution: {integrity: sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==}
+ parse5-htmlparser2-tree-adapter@7.0.0:
+ resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
+
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
@@ -9834,6 +9860,8 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
+ boolbase@1.0.0: {}
+
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@@ -9909,6 +9937,25 @@ snapshots:
character-reference-invalid@2.0.1: {}
+ cheerio-select@2.1.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-select: 5.1.0
+ css-what: 6.1.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.1.0
+
+ cheerio@1.0.0-rc.12:
+ dependencies:
+ cheerio-select: 2.1.0
+ dom-serializer: 2.0.0
+ domhandler: 5.0.3
+ domutils: 3.1.0
+ htmlparser2: 8.0.2
+ parse5: 7.1.2
+ parse5-htmlparser2-tree-adapter: 7.0.0
+
chokidar@3.5.3:
dependencies:
anymatch: 3.1.3
@@ -10121,12 +10168,22 @@ snapshots:
css-color-keywords@1.0.0: {}
+ css-select@5.1.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.1.0
+ domhandler: 5.0.3
+ domutils: 3.1.0
+ nth-check: 2.1.1
+
css-to-react-native@3.2.0:
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
+ css-what@6.1.0: {}
+
csscolorparser@1.0.3: {}
cssesc@3.0.0: {}
@@ -12393,6 +12450,10 @@ snapshots:
dependencies:
path-key: 4.0.0
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
oauth4webapi@2.10.3: {}
oauth4webapi@2.11.1: {}
@@ -12522,6 +12583,11 @@ snapshots:
parse-unit@1.0.1: {}
+ parse5-htmlparser2-tree-adapter@7.0.0:
+ dependencies:
+ domhandler: 5.0.3
+ parse5: 7.1.2
+
parse5@7.1.2:
dependencies:
entities: 4.5.0