feat: add screenshot api
This commit is contained in:
@@ -23,10 +23,11 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import BlurImage from "@/components/shared/blur-image";
|
||||
|
||||
export default function MetaScraping() {
|
||||
const { theme } = useTheme();
|
||||
const [currentLink, setCurrentLink] = useState("");
|
||||
const [currentLink, setCurrentLink] = useState("wr.do");
|
||||
const [protocol, setProtocol] = useState("https");
|
||||
const [metaInfo, setMetaInfo] = useState({
|
||||
title: "",
|
||||
@@ -36,12 +37,18 @@ export default function MetaScraping() {
|
||||
url: "",
|
||||
lang: "",
|
||||
author: "",
|
||||
publisher: "",
|
||||
timestamp: "",
|
||||
});
|
||||
const [isScraping, setIsScraping] = useState(false);
|
||||
|
||||
const handleScraping = async () => {
|
||||
const [currentScreenshotLink, setCurrentScreenshotLink] = useState("wr.do");
|
||||
const [screenshotInfo, setScreenshotInfo] = useState({
|
||||
data: "",
|
||||
url: "",
|
||||
timestamp: "",
|
||||
});
|
||||
|
||||
const handleScrapingMeta = async () => {
|
||||
if (currentLink) {
|
||||
setIsScraping(true);
|
||||
const res = await fetch(
|
||||
@@ -57,6 +64,23 @@ export default function MetaScraping() {
|
||||
setIsScraping(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScrapingScreenshot = async () => {
|
||||
if (currentScreenshotLink) {
|
||||
setIsScraping(true);
|
||||
const res = await fetch(
|
||||
`/api/scraping/screenshot?url=${protocol}://${currentScreenshotLink}`,
|
||||
);
|
||||
if (!res.ok || res.status !== 200) {
|
||||
toast.error(res.statusText);
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setScreenshotInfo(data);
|
||||
toast.success("Success!");
|
||||
}
|
||||
setIsScraping(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
@@ -92,15 +116,10 @@ export default function MetaScraping() {
|
||||
value={currentLink}
|
||||
size={100}
|
||||
onChange={(e) => setCurrentLink(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleScraping();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="blue"
|
||||
onClick={handleScraping}
|
||||
onClick={handleScrapingMeta}
|
||||
disabled={isScraping}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
@@ -118,6 +137,76 @@ export default function MetaScraping() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Screenshot</CardTitle>
|
||||
<CardDescription>
|
||||
Automate your website screenshots and turn them into stunning
|
||||
visuals for your applications.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<Select
|
||||
onValueChange={(value: string) => {
|
||||
setProtocol(value);
|
||||
}}
|
||||
name="protocol"
|
||||
defaultValue={"https"}
|
||||
>
|
||||
<SelectTrigger className="h-10 w-24 rounded-r-none shadow-inner">
|
||||
<SelectValue placeholder="Protocol" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem key="https" value="https">
|
||||
https
|
||||
</SelectItem>
|
||||
<SelectItem key="http" value="http">
|
||||
http
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="www.example.com"
|
||||
className="h-10 rounded-none border focus:border-primary active:border-primary"
|
||||
value={currentScreenshotLink}
|
||||
size={100}
|
||||
onChange={(e) => setCurrentScreenshotLink(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
variant="blue"
|
||||
onClick={handleScrapingScreenshot}
|
||||
disabled={isScraping}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
{isScraping ? "Scraping..." : "Send"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 rounded-md border p-3">
|
||||
<JsonView
|
||||
className="max-w-[400px] overflow-hidden"
|
||||
style={theme === "dark" ? vscodeTheme : githubLightTheme}
|
||||
value={screenshotInfo}
|
||||
displayObjectSize={false}
|
||||
displayDataTypes={false}
|
||||
// shortenTextAfterLength={50}
|
||||
/>
|
||||
{screenshotInfo.data && (
|
||||
<BlurImage
|
||||
src={screenshotInfo.data}
|
||||
alt="ligth preview landing"
|
||||
className="my-4 flex rounded-md border object-contain object-center shadow-md dark:hidden"
|
||||
width={1500}
|
||||
height={750}
|
||||
priority
|
||||
// placeholder="blur"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,9 +56,6 @@ export async function GET(req: Request) {
|
||||
const author =
|
||||
$("meta[name='author']").attr("content") ||
|
||||
$("meta[property='author']").attr("content");
|
||||
const publisher =
|
||||
$("meta[name='publisher']").attr("content") ||
|
||||
$("meta[property='publisher']").attr("content");
|
||||
|
||||
return Response.json({
|
||||
title,
|
||||
@@ -68,7 +65,6 @@ export async function GET(req: Request) {
|
||||
url: link,
|
||||
lang,
|
||||
author,
|
||||
publisher,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { isLink } from "@/lib/utils";
|
||||
|
||||
export const revalidate = 60;
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
|
||||
const url = new URL(req.url);
|
||||
const link = url.searchParams.get("url");
|
||||
const full = url.searchParams.get("full") || "false";
|
||||
|
||||
if (!link || !isLink(link)) {
|
||||
return Response.json("Invalid url", {
|
||||
status: 400,
|
||||
statusText: "Invalid url",
|
||||
});
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto(link, { waitUntil: "networkidle2" });
|
||||
const screenshot = await page.screenshot({
|
||||
encoding: "base64",
|
||||
fullPage: full === "true",
|
||||
});
|
||||
await browser.close();
|
||||
|
||||
return Response.json({
|
||||
data: `data:image/png;base64,${screenshot}`,
|
||||
url: link,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Response.json("An error occurred", {
|
||||
status: error.status || 500,
|
||||
statusText: error.statusText || "Server error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,7 @@
|
||||
"next-view-transitions": "^0.3.0",
|
||||
"nodemailer": "^6.9.14",
|
||||
"prop-types": "^15.8.1",
|
||||
"puppeteer": "^22.15.0",
|
||||
"react": "18.3.1",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-day-picker": "^8.10.1",
|
||||
|
||||
Generated
+516
-20
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user