Automatically create shortlinks for your Astro site

feat: chhoto-url shortlinker

ovyerus.com c103f38b 35c69e3b

verified
+120
+120
src/chhoto-url.ts
···
··· 1 + import type { AstroIntegrationLogger } from "astro"; 2 + import type { Shortlinker } from "./types.js"; 3 + 4 + interface ChhotoUrlErrorResponse { 5 + success: false; 6 + error: true; 7 + reason: string; 8 + } 9 + 10 + interface ChhotoUrlNewResponse { 11 + success: true; 12 + error: false; 13 + shorturl: string; 14 + expiry_time: number; 15 + } 16 + 17 + interface ChhotoUrlEditResponse { 18 + success: true; 19 + error: false; 20 + reason: string; 21 + } 22 + 23 + interface ChhotoUrlEntry { 24 + shortlink: string; 25 + longlink: string; 26 + hits: number; 27 + expiry_time: number; 28 + } 29 + 30 + type ChhotoUrlAllResponse = ChhotoUrlEntry[]; 31 + 32 + export interface ChhotoUrlOptions { 33 + domain: string; 34 + apiKey?: string; 35 + resetHits?: boolean; 36 + } 37 + 38 + /** 39 + * Shortlinker to create links on a https://github.com/SinTan1729/chhoto-url instance. 40 + */ 41 + export const chhotoUrl = ({ 42 + domain, 43 + apiKey, 44 + resetHits = false, 45 + }: ChhotoUrlOptions): Shortlinker => { 46 + return { 47 + name: "chhoto-url", 48 + async run($mappings, logger) { 49 + const handleError = async (response: Response, action: string) => { 50 + if (response.headers.get("Content-Type") === "application/json") { 51 + const { reason }: ChhotoUrlErrorResponse = await response.json(); 52 + logger.error(`Failed to ${action}: ${reason}`); 53 + } else { 54 + const error = await response.text(); 55 + logger.error( 56 + `Failed to ${action}. Status: ${response.status}, ${error}` 57 + ); 58 + } 59 + 60 + return false; 61 + }; 62 + 63 + const headers: Record<string, string> = apiKey 64 + ? { "X-API-Key": apiKey } 65 + : {}; 66 + // chhoto-url doesn't allow slashes at the start of shortlinks, so clean 67 + // it up for users. 68 + const mappings = $mappings.map(({ longlink, shortlink }) => ({ 69 + longlink, 70 + shortlink: shortlink.replace(/^\//, ""), 71 + })); 72 + 73 + const listRes = await fetch(new URL("/api/all", domain), { headers }); 74 + 75 + if (listRes.status === 401) { 76 + logger.error("Provided apiKey is either missing or incorrect."); 77 + return false; 78 + } else if (!listRes.ok) { 79 + const err = await listRes.text(); 80 + logger.error(`Failed to list all shortlinks: ${err}`); 81 + return false; 82 + } 83 + 84 + const list: ChhotoUrlAllResponse = await listRes.json(); 85 + // Find ones that need to be updated because the longlink has changed. 86 + const existingLinksToUpdate = mappings.filter((map) => 87 + list.find( 88 + (item) => 89 + item.shortlink === map.shortlink && item.longlink !== map.longlink 90 + ) 91 + ); 92 + // Find new ones that need to be created. 93 + const newLinks = mappings.filter( 94 + (map) => !list.find((item) => item.shortlink === map.shortlink) 95 + ); 96 + 97 + for (const link of newLinks) { 98 + const newRes = await fetch(new URL("/api/new", domain), { 99 + method: "POST", 100 + headers: { ...headers, "Content-Type": "application/json" }, 101 + body: JSON.stringify(link), 102 + }); 103 + 104 + if (!newRes.ok) 105 + return handleError(newRes, `create shortlink "${link.shortlink}"`); 106 + } 107 + 108 + for (const link of existingLinksToUpdate) { 109 + const editRes = await fetch(new URL("/api/edit", domain), { 110 + method: "PUT", 111 + headers: { ...headers, "Content-Type": "application/json" }, 112 + body: JSON.stringify({ ...link, reset_hits: resetHits }), 113 + }); 114 + 115 + if (!editRes.ok) 116 + return handleError(editRes, `edit shortlink "${link.shortlink}"`); 117 + } 118 + }, 119 + }; 120 + };