base2getNewsBatches.js1 match
3import { sqlite } from "https://esm.town/v/YOUR_USERNAME/database"; // ⚠️ CHANGE YOUR_USERNAME
45export default async function(req: Request) {
6const results = await sqlite.execute(`
7SELECT batch_id, MIN(fetched_at) as fetch_time, COUNT(*) as article_count
base2getNewsByBatch.js1 match
4import { sqlite } from "https://esm.town/v/YOUR_USERNAME/database"; // ⚠️ CHANGE YOUR_USERNAME
56export default async function(req: Request) {
7const url = new URL(req.url);
8const batchId = url.pathname.split("/").pop(); // Get batchId from path
base2getLatestNews.js1 match
3import { sqlite } from "https://esm.town/v/YOUR_USERNAME/database"; // ⚠️ CHANGE YOUR_USERNAME
45export default async function(req: Request) {
6const latestBatch = await sqlite.execute(`SELECT batch_id FROM news ORDER BY fetched_at DESC LIMIT 1`);
7if (latestBatch.rows.length === 0) {
base2fetchAndStoreNews.js5 matches
1// Val Name: fetchAndStoreNews.js
2// Type: Scheduled Function (e.g., every 3 hours)
34import { blob } from "https://esm.town/v/std/blob";
9const GNEWS_API_KEY = "973de7360b49225b484c14a98fcc0a7f"; // It's better to store this as an environment variable in Val Town
1011// Function to generate TTS and save it to blob storage
12async function generateTtsAudio(text, newsId) {
13// Check if audio already exists
14const existing = await sqlite.execute({
46}
4748// Main function to be scheduled
49export default async function fetchAndStoreNews() {
50console.log("Fetching fresh news...");
51const response = await fetch(
base2database.js2 matches
2import { sqlite } from "https://esm.town/v/std/sqlite";
34// This function runs only once to set up the tables if they don't exist.
5async function initialize() {
6await sqlite.execute(`
7CREATE TABLE IF NOT EXISTS news (
base2index.html37 matches
211const API_NEWS_BY_BATCH = '/api/news/batch/';
212213async function init() {
214await lucide.createIcons();
215
251}
252253function setupEventListeners() {
254prevBtn.addEventListener('click', goToPrevious);
255nextBtn.addEventListener('click', goToNext);
282}
283
284function handleAudioEnded() {
285audioEnded = true;
286console.log("Audio ended");
297
298
299function speakNewsItem() {
300audioEnded = false;
301ttsAbortController.abort();
424425
426function stopSpeaking() {
427ttsAbortController.abort();
428ttsAudio.pause();
437
438
439function userActive() {
440carouselContainer.classList.remove('user-inactive');
441carouselContainer.classList.add('user-active');
450}
451
452function startUserActivityMonitor() {
453userActive();
454}
455456function updateHeadline() {
457if (newsItems.length === 0) {
458headline.textContent = 'No news available.';
463}
464
465function updateFullContent() {
466if (newsItems.length === 0) return;
467
489}
490491async function goToNext() {
492if (isTransitioning) return;
493isTransitioning = true;
497}
498
499async function goToPrevious() {
500if (isTransitioning) return;
501isTransitioning = true;
505}
506507function updateCarousel() {
508updateHeadline();
509updateFullContent();
515516
517async function restartVideo() {
518stopSpeaking();
519audioEnded = false;
535}
536537function togglePlayPause() {
538isPlaying = !isPlaying;
539
559560
561function updatePlayPauseIcon() {
562const btn = document.getElementById('play-pause-btn');
563btn.innerHTML = `<i data-lucide="${isPlaying ? 'pause' : 'play'}"></i>`;
565}
566
567function startAutoAdvance() {
568stopAutoAdvance();
569if (isPlaying) {
576}
577578function stopAutoAdvance() {
579if (autoAdvanceInterval) {
580clearInterval(autoAdvanceInterval);
583}
584585function toggleMute() {
586if (video.volume > 0) {
587lastVolume = video.volume;
597userActive();
598}
599function updateVolume() {
600const volume = parseFloat(volumeSlider.value);
601video.volume = volume;
610}
611}
612function updateVolumeIcon(volume) {
613const btn = document.getElementById('mute-btn');
614let iconName = 'volume-x';
620}
621622function updateProgressBar() {
623if (video.duration) {
624const progress = (video.currentTime / video.duration) * 100;
628}
629630function seekVideo(e) {
631const rect = progressContainer.getBoundingClientRect();
632const percent = (e.clientX - rect.left) / rect.width;
637}
638639function updateTimeDisplay() {
640if (video.duration) {
641const currentTime = formatTime(video.currentTime);
645}
646647function updateVideoDurationDisplay() {
648if (video.duration) {
649timeDisplay.textContent = `0:00 / ${formatTime(video.duration)}`;
653}
654655function formatTime(seconds) {
656if (!seconds || isNaN(seconds)) return '0:00';
657
661}
662663function handleVideoEnded() {
664// Let the audio ended event handle navigation
665}
666667function showContentPanel() {
668contentVisible = true;
669newsContent.classList.add('active');
676}
677
678function hideContentPanel() {
679contentVisible = false;
680newsContent.classList.remove('active');
682}
683684async function openHistoryPanel() {
685loadingOverlay.style.display = 'flex';
686loadingOverlay.querySelector('.loading-text').textContent = 'Loading news history...';
707}
708
709function populateHistoryPanel() {
710historyList.innerHTML = '';
711
741}
742743async function loadNewsBatch(batchId) {
744loadingOverlay.style.display = 'flex';
745loadingOverlay.querySelector('.loading-text').textContent = 'Loading news batch...';
777}
778779function closeHistoryPanel() {
780historyPanel.classList.remove('active');
781userActive();
782}
783784function toggleFullscreen() {
785if (!document.fullscreenElement) {
786document.querySelector('.carousel-container').requestFullscreen().catch(err => {
793}
794795function updateFullscreenIcon() {
796const icon = document.fullscreenElement ? 'minimize' : 'maximize';
797fullscreenBtn.innerHTML = `<i data-lucide="${icon}"></i>`;
799}
800801function handleMediaError(e) {
802console.error('Media error:', e);
803const mediaElement = e.target;
813}
814815function showError(message) {
816errorMessage.textContent = message;
817errorMessage.classList.add('active');
822}
823824function handleKeyboardShortcuts(e) {
825if (e.code === 'Space') {
826e.preventDefault();
1export default async function (req: Request): Promise<Response> {
2return Response.json({ ok: true })
3}
32break;
33case 2:
34function c(e) {
35var a;
36for (var r = 2; r !== 11;) {
41case 6:
42a = Y7j7u.M2F(
43Y7j7u.N_m(B, function() {
44for (var e = 2; e !== 1;) {
45switch (e) {
95var $ = Y7j7u.s$f($, "{");
96var k = 0;
97function J(e) {
98for (var a = 2; a !== 23;) {
99switch (a) {
188}
189}
190function n(e) {
191for (var a = 2; a !== 1;) {
192switch (a) {
2021// Initialize database
22async function initDatabase() {
23// Create gardens table
24await sqlite.execute(`CREATE TABLE IF NOT EXISTS ${GARDENS_TABLE_NAME} (
Gardenonindex.html24 matches
488
489// Load all gardens
490async function loadGardens() {
491try {
492const response = await fetch('/api/gardens');
520
521// Load current garden and its plants
522async function loadCurrentGarden() {
523if (!currentGarden) return;
524
532
533// Fetch plants for current garden
534async function fetchPlants() {
535if (!currentGarden) {
536plants = [];
551
552// Render plants on the map and in the list
553function renderPlants() {
554// Clear existing plants (but preserve placeholder)
555gardenMap.querySelectorAll('.plant:not(.placeholder)').forEach(el => el.remove());
628
629// Create a new garden
630async function createGarden() {
631const name = newGardenNameInput.value.trim();
632const width = parseInt(newGardenWidthInput.value);
674
675// Update current garden settings
676async function updateGardenSettings() {
677if (!currentGarden) return;
678
722
723// Delete current garden
724async function deleteGarden() {
725if (!currentGarden) return;
726
759
760// Create a placeholder plant on the map
761function createPlaceholderPlant(x, y) {
762// Remove existing placeholder if any
763removePlaceholderPlant();
792
793// Remove placeholder plant from the map
794function removePlaceholderPlant() {
795if (placeholderPlant && placeholderPlant.element) {
796placeholderPlant.element.remove();
800
801// Select a plant to edit
802function selectPlant(plant) {
803// Cancel move mode if active
804if (isMovingPlant) {
833
834// Clear the form
835function clearForm() {
836// Remove placeholder plant if it exists
837removePlaceholderPlant();
859
860// Start move mode
861function startMoveMode() {
862if (!selectedPlant) {
863alert('Please select a plant to move.');
873
874// Cancel move mode
875function cancelMoveMode() {
876isMovingPlant = false;
877mapInstructions.textContent = 'Tap anywhere on the map to add a plant';
882
883// Move plant to new position
884function movePlantTo(x, y) {
885if (!selectedPlant) return;
886
906
907// Add a new plant at the clicked position
908function addPlantAt(x, y) {
909if (!currentGarden) {
910alert('Please select a garden first.');
937
938// Save plant data
939async function savePlant(plantData) {
940if (!currentGarden) {
941alert('Please select a garden first.');
984
985// Update placeholder plant when diameter changes
986function updatePlaceholderPlant() {
987if (placeholderPlant && placeholderPlant.element) {
988const newDiameter = parseFloat(plantDiameterInput.value) || 50;
994
995// Delete a plant
996async function deletePlant(id) {
997if (!confirm('Are you sure you want to delete this plant?')) {
998return;
1020}
1021
1022// Modal functions
1023function openGardenSettingsModal() {
1024if (!currentGarden) {
1025alert('Please select a garden first.');
1033}
1034
1035function closeGardenSettingsModal() {
1036gardenSettingsModal.style.display = 'none';
1037}
1038
1039function openNewGardenModal() {
1040newGardenNameInput.value = '';
1041newGardenWidthInput.value = 700;
1044}
1045
1046function closeNewGardenModal() {
1047newGardenModal.style.display = 'none';
1048}
1176
1177// Initialize
1178async function initialize() {
1179await loadGardens();
1180}