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