15## How to use this Val
16
17🔑 **Important** https://apiflash.com free key required for generating screenshots. (Will update with option to generate on device)
18
19This Val is separated into 2 directories, the `api/` and the `frames/`.
20Take a look at each frame to see an example of what will be displayed on your e-ink display.
21
22The `api/` directory holds endpoints for each Val. These endpoints could be used across projects
23and will soon be moved to their own. Each one is cached server side (on val.town) to reduce traffic to free/public apis.
24
25Remixes and pull requests welcome!
1// Generates a screenshot using APIFlash.com from a url
2// API key required
3
4// TODO: Add caching of image
11 return new Response("No url provided", { status: 500 });
12 }
13 const apiKey = Deno.env.get("API_FLASH_KEY");
14 const generateUrl =
15 `https://api.apiflash.com/v1/urltoimage?access_key=${apiKey}&url=${valUrl}&width=${width}&height=${height}&format=png&fresh=true`;
16
17 try {
18 const response = await fetch(generateUrl);
19 if (!response.ok) {
20 throw new Error(`APIFlash responded with status: ${response.status}`);
21 }
22 return response;
9 <meta charSet="UTF-8" />
10 <title>{title}</title>
11 <link rel="preconnect" href="https://fonts.googleapis.com" />
12 {/* Getting a type error here */}
13 {/* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> */}
14 <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet" />
15 <link href="https://fonts.googleapis.com/css2?family=Doto:wght@100..900&display=swap" rel="stylesheet" />
16 <link href="https://fonts.googleapis.com/css2?family=Rancho&display=swap" rel="stylesheet" />
17 <link href="https://fonts.googleapis.com/css2?family=Permanent+Marker&display=swap" rel="stylesheet" />
18 <link
19 href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
20 rel="stylesheet"
21 />
1import * as linkify from "https://esm.sh/linkifyjs";
2
3// const TWEETS_URL = "https://milei.nulo.lol/api/datasets/all-tweets.jsonl?limit=500";
4const ARCHIVO_API_URL = "https://archivo.nulo.lol/api/crawls";
5const USER_ID = "1728978474881126400";
6
7const ARCHIVO_API_TOKEN = Deno.env.get("ARCHIVO_API_TOKEN");
8const SOCIALDATA_API_KEY = Deno.env.get("SOCIALDATA_API_KEY");
9
10async function fetchLatestTweetUrls(): Promise<string[]> {
13 for (let i = 0; i < 3; i++) {
14 const res = await fetch(
15 "https://api.socialdata.tools/twitter/user/" + USER_ID + "/tweets-and-replies?cursor=" + cursor,
16 { headers: { Authorization: "Bearer " + SOCIALDATA_API_KEY } },
17 );
18 const json = await res.json();
25
26async function checkIfCrawled(url: string): Promise<boolean> {
27 const response = await fetch(ARCHIVO_API_URL, {
28 headers: {
29 "Authorization": ARCHIVO_API_TOKEN,
30 },
31 });
37async function crawlUrls(urls: string[]): Promise<void> {
38 if (urls.length === 0) return;
39 const res = await fetch(ARCHIVO_API_URL, {
40 method: "POST",
41 headers: {
42 "Content-Type": "application/json",
43 "Authorization": ARCHIVO_API_TOKEN,
44 },
45 body: JSON.stringify({ urls }),
8import natural from "npm:natural";
9
10const STEEL_API_KEY = Deno.env.get('STEEL_API_KEY');
11
12function App() {
180
181 const client = new Steel({
182 steelAPIKey: STEEL_API_KEY,
183 });
184
191
192 browser = await puppeteer.connect({
193 browserWSEndpoint: `wss://connect.steel.dev?apiKey=${STEEL_API_KEY}&sessionId=${session.id}`,
194 });
195
252async function searchImage(topic) {
253 try {
254 const response = await fetch(`https://api.unsplash.com/search/photos?query=${encodeURIComponent(topic)}&client_id=YOUR_UNSPLASH_ACCESS_KEY`);
255 const data = await response.json();
256 if (data.results && data.results.length > 0) {
37 async loader(args: LoaderFunctionArgs) {
38 // Fetch recent URLs
39 const response = await fetch(new URL("/api/recent", args.request.url));
40 if (!response.ok) {
41 throw new Error("Failed to fetch recent URLs");
54
55 // Create short URL
56 const response = await fetch(new URL("/api/shorten", args.request.url), {
57 method: "POST",
58 headers: { "Content-Type": "application/json" },
28 }
29
30 // API endpoint to shorten a URL
31 if (path === "/api/shorten" && request.method === "POST") {
32 try {
33 const { longUrl } = await request.json();
48 }
49
50 // API endpoint to get recent URLs
51 if (path === "/api/recent") {
52 try {
53 const recentUrls = await getRecentUrls(import.meta.url);
66
67 // Handle short URL redirects
68 if (path.length > 1 && !path.startsWith("/api") && !path.startsWith("/js") &&
69 !path.includes(".") && !path.startsWith("/about") && !path.startsWith("/create")) {
70 const shortCode = path.slice(1);
28 }
29
30 // API endpoint to shorten a URL
31 if (path === "/api/shorten" && request.method === "POST") {
32 try {
33 const { longUrl } = await request.json();
48 }
49
50 // API endpoint to get recent URLs
51 if (path === "/api/recent") {
52 try {
53 const recentUrls = await getRecentUrls(import.meta.url);
66
67 // Handle short URL redirects
68 if (path.length > 1 && !path.startsWith("/api") && !path.startsWith("/frontend") && !path.startsWith("/shared")) {
69 const shortCode = path.slice(1);
70 try {
15
16 try {
17 const response = await fetch("/api/shorten", {
18 method: "POST",
19 headers: { "Content-Type": "application/json" },
10 useEffect(() => {
11 setIsLoading(true);
12 fetch("/api/recent")
13 .then(response => {
14 if (!response.ok) {