blob_adminmain.tsx25 matches
73const menuRef = useRef(null);
74const isPublic = blob.key.startsWith("__public/");
75const publicUrl = isPublic ? `${window.location.origin}/api/public/${encodeURIComponent(blob.key.slice(9))}` : null;
7677useEffect(() => {
237setLoading(true);
238try {
239const response = await fetch(`/api/blobs?prefix=${encodeKey(searchPrefix)}&limit=${limit}`);
240const data = await response.json();
241setBlobs(data);
264setBlobContentLoading(true);
265try {
266const response = await fetch(`/api/blob?key=${encodeKey(clickedBlob.key)}`);
267const content = await response.text();
268setSelectedBlob({ ...clickedBlob, key: decodeKey(clickedBlob.key) });
278const handleSave = async () => {
279try {
280await fetch(`/api/blob?key=${encodeKey(selectedBlob.key)}`, {
281method: "PUT",
282body: editContent,
290const handleDelete = async (key) => {
291try {
292await fetch(`/api/blob?key=${encodeKey(key)}`, { method: "DELETE" });
293setBlobs(blobs.filter(b => b.key !== key));
294if (selectedBlob && selectedBlob.key === key) {
307const key = `${searchPrefix}${file.name}`;
308formData.append("key", encodeKey(key));
309await fetch("/api/blob", { method: "POST", body: formData });
310const newBlob = { key, size: file.size, lastModified: new Date().toISOString() };
311setBlobs([newBlob, ...blobs]);
329try {
330const fullKey = `${searchPrefix}${key}`;
331await fetch(`/api/blob?key=${encodeKey(fullKey)}`, {
332method: "PUT",
333body: "",
344const handleDownload = async (key) => {
345try {
346const response = await fetch(`/api/blob?key=${encodeKey(key)}`);
347const blob = await response.blob();
348const url = window.URL.createObjectURL(blob);
363if (newKey && newKey !== oldKey) {
364try {
365const response = await fetch(`/api/blob?key=${encodeKey(oldKey)}`);
366const content = await response.blob();
367await fetch(`/api/blob?key=${encodeKey(newKey)}`, {
368method: "PUT",
369body: content,
370});
371await fetch(`/api/blob?key=${encodeKey(oldKey)}`, { method: "DELETE" });
372setBlobs(blobs.map(b => b.key === oldKey ? { ...b, key: newKey } : b));
373if (selectedBlob && selectedBlob.key === oldKey) {
383const newKey = `__public/${key}`;
384try {
385const response = await fetch(`/api/blob?key=${encodeKey(key)}`);
386const content = await response.blob();
387await fetch(`/api/blob?key=${encodeKey(newKey)}`, {
388method: "PUT",
389body: content,
390});
391await fetch(`/api/blob?key=${encodeKey(key)}`, { method: "DELETE" });
392setBlobs(blobs.map(b => b.key === key ? { ...b, key: newKey } : b));
393if (selectedBlob && selectedBlob.key === key) {
402const newKey = key.slice(9); // Remove "__public/" prefix
403try {
404const response = await fetch(`/api/blob?key=${encodeKey(key)}`);
405const content = await response.blob();
406await fetch(`/api/blob?key=${encodeKey(newKey)}`, {
407method: "PUT",
408body: content,
409});
410await fetch(`/api/blob?key=${encodeKey(key)}`, { method: "DELETE" });
411setBlobs(blobs.map(b => b.key === key ? { ...b, key: newKey } : b));
412if (selectedBlob && selectedBlob.key === key) {
557onClick={() =>
558copyToClipboard(
559`${window.location.origin}/api/public/${encodeURIComponent(selectedBlob.key.slice(9))}`,
560)}
561className="text-blue-400 hover:text-blue-300 text-sm"
580>
581<img
582src={`/api/blob?key=${encodeKey(selectedBlob.key)}`}
583alt="Blob content"
584className="max-w-full h-auto"
660661// Public route without authentication
662app.get("/api/public/:id", async (c) => {
663const key = `__public/${c.req.param("id")}`;
664const { blob } = await import("https://esm.town/v/std/blob");
778};
779780app.get("/api/blobs", checkAuth, async (c) => {
781const prefix = c.req.query("prefix") || "";
782const limit = parseInt(c.req.query("limit") || "20", 10);
787});
788789app.get("/api/blob", checkAuth, async (c) => {
790const key = c.req.query("key");
791if (!key) return c.text("Missing key parameter", 400);
795});
796797app.put("/api/blob", checkAuth, async (c) => {
798const key = c.req.query("key");
799if (!key) return c.text("Missing key parameter", 400);
804});
805806app.delete("/api/blob", checkAuth, async (c) => {
807const key = c.req.query("key");
808if (!key) return c.text("Missing key parameter", 400);
812});
813814app.post("/api/blob", checkAuth, async (c) => {
815const { file, key } = await c.req.parseBody();
816if (!file || !key) return c.text("Missing file or key", 400);
redditAlertREADME.md10 matches
72. Send notifications to your preferred platform (Discord, Slack, email, etc.)
89Reddit does not have an API that allows users to scrape data, so we are doing this with the Google Search API, [Serp](https://serpapi.com/).
1011---
2930---
31### 3. Get a SerpApi Key
32This template requires a [SerpApi](https://serpapi.com/) key to search Reddit posts via Google search results.
33341. **Get a SerpApi key**:
35- Sign up at [SerpApi](https://serpapi.com/) to create an account.
36- Generate an API key from your account dashboard.
37382. **Add the SerpApi key to your environment variables**:
39- Go to your [Val Town environment variables](https://www.val.town/settings/environment-variables).
40- Add a new key:
41- Key: `SERP_API_KEY`
42- Value: Your SERP API key.
4344Without this key, the val will not function correctly.
7576### NOTE: Usage Limits
77- **SerpApi:** Free SerpApi accounts have monthly call limits.
redditAlertmain.tsx9 matches
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
34// Customize your search parameters
5const KEYWORDS = "\"node\" OR \"node.js\"";
6const DISCORD_API_KEY = Deno.env.get("mentionsDiscord");
7const SERP_API_KEY = Deno.env.get("SERP_API_KEY");
89// Set isProd = false for testing and = true for production
1314export async function redditAlert({ lastRunAt }: Interval) {
15if (!SERP_API_KEY || !DISCORD_API_KEY) {
16console.error("Missing SERP_API_KEY or Discord webhook URL. Exiting.");
17return;
18}
1920// Determine the time frame for the search
21// Details on as_qdr: https://serpapi.com/advanced-google-query-parameters#api-parameters-advanced-search-query-parameters-as-qdr
22const timeFrame = isProd
23? lastRunAt
2728try {
29const response = await searchWithSerpApi({
30query: KEYWORDS,
31site: "reddit.com",
32apiKey: SERP_API_KEY,
33as_qdr: timeFrame,
34});
62if (isProd) {
63await discordWebhook({
64url: DISCORD_API_KEY,
65content,
66});
redditAlertREADME.md10 matches
72. Send notifications to your preferred platform (Discord, Slack, email, etc.)
89Reddit does not have an API that allows users to scrape data, so we are doing this with the Google Search API, [Serp](https://serpapi.com/).
1011---
2930---
31### 3. Get a SerpApi Key
32This template requires a [SerpApi](https://serpapi.com/) key to search Reddit posts via Google search results.
33341. **Get a SerpApi key**:
35- Sign up at [SerpApi](https://serpapi.com/) to create an account.
36- Generate an API key from your account dashboard.
37382. **Add the SerpApi key to your environment variables**:
39- Go to your [Val Town environment variables](https://www.val.town/settings/environment-variables).
40- Add a new key:
41- Key: `SERP_API_KEY`
42- Value: Your SERP API key.
4344Without this key, the val will not function correctly.
7576### NOTE: Usage Limits
77- **SerpApi:** Free SerpApi accounts have monthly call limits.
redditAlertmain.tsx9 matches
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
34// Customize your search parameters
5const KEYWORDS = "\"node\" OR \"node.js\"";
6const DISCORD_API_KEY = Deno.env.get("mentionsDiscord");
7const SERP_API_KEY = Deno.env.get("SERP_API_KEY");
89// Set isProd = false for testing and = true for production
1314export async function redditAlert({ lastRunAt }: Interval) {
15if (!SERP_API_KEY || !DISCORD_API_KEY) {
16console.error("Missing SERP_API_KEY or Discord webhook URL. Exiting.");
17return;
18}
1920// Determine the time frame for the search
21// Details on as_qdr: https://serpapi.com/advanced-google-query-parameters#api-parameters-advanced-search-query-parameters-as-qdr
22const timeFrame = isProd
23? lastRunAt
2728try {
29const response = await searchWithSerpApi({
30query: KEYWORDS,
31site: "reddit.com",
32apiKey: SERP_API_KEY,
33as_qdr: timeFrame,
34});
62if (isProd) {
63await discordWebhook({
64url: DISCORD_API_KEY,
65content,
66});
reactHonoExampleREADME.md5 matches
8## Hono
910This app uses [Hono](https://hono.dev/) as the API framework. You can think of Hono as a replacement for [ExpressJS](https://expressjs.com/) that works in serverless environments like Val Town or Cloudflare Workers. If you come from Python or Ruby, Hono is also a lot like [Flask](https://github.com/pallets/flask) or [Sinatra](https://github.com/sinatra/sinatra), respectively.
1112## Serving assets to the frontend
20### `index.html`
2122The most complicated part of this backend API is serving index.html. In this app (like most apps) we serve it at the root, ie `GET /`.
2324We *bootstrap* `index.html` with some initial data from the server, so that it gets dynamically injected JSON data without having to make another round-trip request to the server to get that data on the frontend. This is a common pattern for client-side rendered apps.
2526## CRUD API Routes
2728This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.
2930## Errors
3132Hono and other API frameworks have a habit of swallowing up Errors. We turn off this default behavior by re-throwing errors, because we think most of the time you'll want to see the full stack trace instead of merely "Internal Server Error". You can customize how you want errors to appear.
redditKeywordSMSmain.tsx5 matches
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import sendSMS from "https://esm.town/v/charmaine/sendMessageWithTwilio";
31112// Get environment variables
13const apiKey = Deno.env.get("SERP_API_KEY");
14const twilioSid = Deno.env.get("TWILIO_ACCOUNT_SID");
15const twilioToken = Deno.env.get("TWILIO_AUTH_TOKEN");
1819// Check if we have all required credentials
20if (!apiKey || !twilioSid || !twilioToken || !fromNumber || !toNumber) {
21console.error("Missing required environment variables");
22return;
26// Search for Reddit posts from the last week
27console.log("Searching for Reddit posts...");
28const posts = await searchWithSerpApi({
29query: "\"val.town\" OR \"val.run\" OR \"val town\"",
30site: "reddit.com",
31apiKey,
32as_qdr: "d7", // Get posts from the last week
33});
memorableCoralCheetahmain.tsx8 matches
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import sendSMS from "https://esm.town/v/charmaine/sendMessageWithTwilio";
378// Get environment variables
9const apiKey = Deno.env.get("SERP_API_KEY");
10const twilioSid = Deno.env.get("TWILIO_ACCOUNT_SID");
11const twilioToken = Deno.env.get("TWILIO_AUTH_TOKEN");
1415// Check if we have all required credentials
16if (!apiKey || !twilioSid || !twilioToken || !fromNumber || !toNumber) {
17console.error("Missing required environment variables:", {
18hasApiKey: !!apiKey,
19hasTwilioSid: !!twilioSid,
20hasTwilioToken: !!twilioToken,
29try {
30// Search for Reddit posts
31console.log("Initiating SERP API search...");
32const posts = await searchWithSerpApi({
33query: "\"node\" OR \"node.js\"",
34site: "reddit.com",
35apiKey,
36as_qdr: "d7", // Get posts from the last week
37});
96console.error("Error in Reddit Alert Test:", error);
97if (error.response) {
98console.error("API error response:", JSON.stringify(error.response.data || error.response, null, 2));
99}
100if (error.request) {
memorableCoralCheetahREADME.md2 matches
14- Twilio account (for SMS notifications)
15- You can create an account with $15 free credits at https://www.twilio.com/ without putting in your credit card
16- SerpApi key (for searching Reddit)
1718---
20**Customize all of these according to your preferences:**
21```
22const apiKey = Deno.env.get("SERP_API_KEY");
23const twilioSid = Deno.env.get("TWILIO_ACCOUNT_SID");
24const twilioToken = Deno.env.get("TWILIO_AUTH_TOKEN");
1Re-implements Unsplash Source which was a service hosted at source.unsplash.com that served random photos from Unsplash.
23To use this, you'll want to fork it, provide your own Unsplash API key, and configure the allowed domains.
45Read more: https://paul.af/reimplementing-unsplash-source