78
791. Authenticate with Reddit using OAuth client credentials
802. Fetch the latest 25 posts from /r/hardwareswap
813. Check for duplicates in the database
824. Save new posts to Supabase
99
100- Reddit allows 60 requests per minute for OAuth applications
101- The scraper fetches 25 posts per run, well within limits
102- Recommended cron schedule: every 30 minutes or longer
103
62 const auth = btoa(`${this.clientId}:${this.clientSecret}`);
63
64 const response = await fetch('https://www.reddit.com/api/v1/access_token', {
65 method: 'POST',
66 headers: {
90 }
91
92 async fetchNewPosts(): Promise<RedditPost[]> {
93 try {
94 const accessToken = await this.getAccessToken();
95
96 const response = await fetch('https://oauth.reddit.com/r/hardwareswap/new?limit=25', {
97 headers: {
98 'Authorization': `Bearer ${accessToken}`,
106 const newToken = await this.getAccessToken();
107
108 const retryResponse = await fetch('https://oauth.reddit.com/r/hardwareswap/new?limit=25', {
109 headers: {
110 'Authorization': `Bearer ${newToken}`,
127 return this.parseRedditResponse(data);
128 } catch (error) {
129 console.error('Error fetching posts from Reddit:', error);
130 return [];
131 }
242 async scrapeWithResults() {
243 const startTime = Date.now();
244 const posts = await this.fetchNewPosts();
245 let newPostsCount = 0;
246 let duplicatePostsCount = 0;
135 const lastSunday = today.startOf("week").minus({ days: 1 });
136
137 // Fetch relevant memories using the utility function
138 const memories = await getRelevantMemories();
139
67 const [currentPage, setCurrentPage] = useState(1);
68
69 const fetchMemories = useCallback(async () => {
70 setLoading(true);
71 setError(null);
72 try {
73 const response = await fetch(API_BASE);
74 if (!response.ok) {
75 throw new Error(`HTTP error! status: ${response.status}`);
78 setMemories(data);
79 } catch (e) {
80 console.error("Failed to fetch memories:", e);
81 setError(e.message || "Failed to fetch memories.");
82 } finally {
83 setLoading(false);
86
87 useEffect(() => {
88 fetchMemories();
89 }, [fetchMemories]);
90
91 const handleAddMemory = async (e: React.FormEvent) => {
100
101 try {
102 const response = await fetch(API_BASE, {
103 method: "POST",
104 headers: { "Content-Type": "application/json" },
112 setNewMemoryTags("");
113 setShowAddForm(false);
114 await fetchMemories();
115 } catch (e) {
116 console.error("Failed to add memory:", e);
123
124 try {
125 const response = await fetch(`${API_BASE}/${id}`, {
126 method: "DELETE",
127 });
129 throw new Error(`HTTP error! status: ${response.status}`);
130 }
131 await fetchMemories();
132 } catch (e) {
133 console.error("Failed to delete memory:", e);
155
156 try {
157 const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
158 method: "PUT",
159 headers: { "Content-Type": "application/json" },
164 }
165 setEditingMemory(null);
166 await fetchMemories();
167 } catch (e) {
168 console.error("Failed to update memory:", e);
135 ));
136
137// HTTP vals expect an exported "fetch handler"
138export default app.fetch;
163```
164
1655. **fetchTranspiledJavaScript** - Fetch and transpile TypeScript to JavaScript:
166```ts
167const jsCode = await fetchTranspiledJavaScript("https://esm.town/v/username/project/path/to/file.ts");
168```
169
242
243 // Inject data to avoid extra round-trips
244 const initialData = await fetchInitialData();
245 const dataScript = `<script>
246 window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
300
3015. **API Design:**
302 - `fetch` handler is the entry point for HTTP vals
303 - Run the Hono app with `export default app.fetch // This is the entry point for HTTP vals`
304 - Properly handle CORS if needed for external access
82 const [cookieAndTeaMode, setCookieAndTeaMode] = useState(false);
83
84 // Fetch images from backend instead of blob storage directly
85 useEffect(() => {
86 // Set default background color in case image doesn't load
89 }
90
91 // Fetch avatar image
92 fetch("/api/images/stevens.jpg")
93 .then((response) => {
94 if (response.ok) return response.blob();
103 });
104
105 // Fetch wood background
106 fetch("/api/images/wood.jpg")
107 .then((response) => {
108 if (response.ok) return response.blob();
129 }, []);
130
131 const fetchMemories = useCallback(async () => {
132 setLoading(true);
133 setError(null);
134 try {
135 const response = await fetch(API_BASE);
136 if (!response.ok) {
137 throw new Error(`HTTP error! status: ${response.status}`);
154 }
155 } catch (e) {
156 console.error("Failed to fetch memories:", e);
157 setError(e.message || "Failed to fetch memories.");
158 } finally {
159 setLoading(false);
162
163 useEffect(() => {
164 fetchMemories();
165 }, [fetchMemories]);
166
167 const handleAddMemory = async (e: React.FormEvent) => {
176
177 try {
178 const response = await fetch(API_BASE, {
179 method: "POST",
180 headers: { "Content-Type": "application/json" },
188 setNewMemoryTags("");
189 setShowAddForm(false);
190 await fetchMemories();
191 } catch (e) {
192 console.error("Failed to add memory:", e);
199
200 try {
201 const response = await fetch(`${API_BASE}/${id}`, {
202 method: "DELETE",
203 });
205 throw new Error(`HTTP error! status: ${response.status}`);
206 }
207 await fetchMemories();
208 } catch (e) {
209 console.error("Failed to delete memory:", e);
231
232 try {
233 const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
234 method: "PUT",
235 headers: { "Content-Type": "application/json" },
240 }
241 setEditingMemory(null);
242 await fetchMemories();
243 } catch (e) {
244 console.error("Failed to update memory:", e);
15 };
16
17 const fetchRecord = async () => {
18 const res = await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${VIAJES_TABLE}/${recordId}`, {
19 headers: airtableHeaders,
20 });
21 if (!res.ok) throw new Error(`Airtable fetch failed: ${res.status}`);
22 return (await res.json()).fields;
23 };
28 form.append("upload_preset", CLOUDINARY_UPLOAD_PRESET);
29
30 const res = await fetch(`https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/upload`, {
31 method: "POST",
32 body: form,
42
43 const updateAirtableFields = async (fields: Record<string, any>) => {
44 const res = await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${VIAJES_TABLE}/${recordId}`, {
45 method: "PATCH",
46 headers: airtableHeaders,
50 };
51
52 const fetchFromFacturama = async (id: string, type: "xml" | "pdf") => {
53 const url = `https://apisandbox.facturama.mx/api/Cfdi/${type}/issued/${id}`;
54 const res = await fetch(url, {
55 headers: {
56 Authorization: FACTURAMA_AUTH,
60
61 const body = await res.text();
62 if (!res.ok) throw new Error(`Facturama ${type.toUpperCase()} fetch failed: ${res.status} - ${body}`);
63 return JSON.parse(body)?.Content;
64 };
72
73 try {
74 const data = await fetchRecord();
75 const facturaDate = data?.Fecha ? formatDate(new Date(data.Fecha)) : formatDate(new Date());
76 const folio = data.Folio || "V-00001-GENERADO";
208 };
209
210 const facturamaRes = await fetch("https://apisandbox.facturama.mx/3/cfdis", {
211 method: "POST",
212 headers: {
233
234 const [xmlBase64, pdfBase64] = await Promise.all([
235 fetchFromFacturama(id, "xml"),
236 fetchFromFacturama(id, "pdf"),
237 ]);
238
15 };
16
17 const fetchRecord = async () => {
18 const res = await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${VIAJES_TABLE}/${recordId}`, {
19 headers: airtableHeaders,
20 });
21 if (!res.ok) throw new Error(`Airtable fetch failed: ${res.status}`);
22 return (await res.json()).fields;
23 };
28 form.append("upload_preset", CLOUDINARY_UPLOAD_PRESET);
29
30 const res = await fetch(`https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/upload`, {
31 method: "POST",
32 body: form,
42
43 const updateAirtableFields = async (fields: Record<string, any>) => {
44 const res = await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${VIAJES_TABLE}/${recordId}`, {
45 method: "PATCH",
46 headers: airtableHeaders,
50 };
51
52 const fetchFromFacturama = async (id: string, type: "xml" | "pdf") => {
53 const url = `https://apisandbox.facturama.mx/api/Cfdi/${type}/issued/${id}`;
54 const res = await fetch(url, {
55 headers: {
56 Authorization: FACTURAMA_AUTH,
60
61 const body = await res.text();
62 if (!res.ok) throw new Error(`Facturama ${type.toUpperCase()} fetch failed: ${res.status} - ${body}`);
63 return JSON.parse(body)?.Content;
64 };
72
73 try {
74 const data = await fetchRecord();
75 const facturaDate = data?.Fecha ? formatDate(new Date(data.Fecha)) : formatDate(new Date());
76 const folio = data.Folio || "V-00001-GENERADO";
208 };
209
210 const facturamaRes = await fetch("https://apisandbox.facturama.mx/3/cfdis", {
211 method: "POST",
212 headers: {
233
234 const [xmlBase64, pdfBase64] = await Promise.all([
235 fetchFromFacturama(id, "xml"),
236 fetchFromFacturama(id, "pdf"),
237 ]);
238
24});
25
26export default app.fetch;