From 4161ac48df1e6a6f618c01f8a292e5129a1602aa Mon Sep 17 00:00:00 2001 From: Ario Barin Ostovary <89481178+Doomsy1@users.noreply.github.com> Date: Thu, 25 Jun 2026 00:41:48 -0400 Subject: [PATCH 1/6] added ics download guidance --- scripts/verify-extension-ui.mjs | 55 ++++++++++++++++ src/background/openCalendar.js | 16 ++++- src/popup.js | 11 +++- tests/openCalendar.test.js | 110 ++++++++++++++++++++++++++++++++ tests/popupUi.test.js | 13 ++++ 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 tests/openCalendar.test.js diff --git a/scripts/verify-extension-ui.mjs b/scripts/verify-extension-ui.mjs index 4cfae94..432954f 100644 --- a/scripts/verify-extension-ui.mjs +++ b/scripts/verify-extension-ui.mjs @@ -766,6 +766,61 @@ async function main() { return { open: openEvidence, restored, scanned }; }); + await runCase(results, "iCalendar download shows open-file guidance", async () => { + await clearExtensionStorage(browser, extensionId); + const page = await browser.newPage(); + await page.goto(`${fixtureServer.origin}/event-page.html`, { + waitUntil: "domcontentloaded", + }); + const popup = await openExtensionPopup(browser, page, extensionId, extension); + const popupDiagnostics = await attachPageDiagnostics(popup, "icalendar-popup"); + await waitForScanAvailability(popup); + await popup.evaluate(async () => { + await chrome.storage.sync.set({ + settings: { + defaultCalendar: "icloud", + }, + }); + }); + + await popup.click("#scanBtn"); + await waitForVisibleResults(popup); + + const firstCardPoint = await popup.$eval(".event-card", (card) => { + card.scrollIntoView({ block: "center", inline: "nearest" }); + const rect = card.getBoundingClientRect(); + return { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + }); + await popup.mouse.click(firstCardPoint.x, firstCardPoint.y); + await popup.waitForFunction( + () => document.querySelectorAll(".event-card.selected").length === 1 && + document.getElementById("addSelectedBtn")?.disabled === false, + { timeout: options.timeoutMs } + ); + + await popup.click("#addSelectedBtn"); + await popup.waitForFunction( + () => { + const toast = document.getElementById("toast"); + return toast?.classList.contains("visible") && + toast.innerText.includes("After it downloads, open the .ics file"); + }, + { timeout: options.timeoutMs } + ); + + const notice = await captureEvidence(popup, reportDir, "icalendar-download-notice"); + assert.equal(notice.state.toast.visible, true); + assert.match(notice.state.toast.text, /After it downloads, open the \.ics file/); + assertNoPopupOverflow(notice.state); + assertNoUnexpectedErrors(popupDiagnostics); + await closePage(popup); + await closePage(page); + return notice; + }); + await runCase(results, "settings button opens usable settings page", async () => { await clearExtensionStorage(browser, extensionId); const page = await browser.newPage(); diff --git a/src/background/openCalendar.js b/src/background/openCalendar.js index d036293..7202e03 100644 --- a/src/background/openCalendar.js +++ b/src/background/openCalendar.js @@ -4,6 +4,15 @@ import { createICSDownloadUrl, createMergedICSDownloadUrl, generateICSFilename } // Track active downloads to revoke blob URLs const activeDownloads = new Map(); +function notifyIcsDownloadStarted(downloadedFiles, onIcsDownloadStarted) { + if (!downloadedFiles.length || typeof onIcsDownloadStarted !== "function") return; + + onIcsDownloadStarted({ + count: downloadedFiles.length, + filenames: downloadedFiles.map((file) => file.filename), + }); +} + // Single listener for all downloads chrome.downloads.onChanged.addListener((delta) => { if (activeDownloads.has(delta.id) && delta.state?.current) { @@ -52,13 +61,15 @@ export async function downloadMergedICS(events) { } } -export async function openCalendarEventsInBackground(events) { +export async function openCalendarEventsInBackground(events, options = {}) { // Load user's calendar preference const result = await chrome.storage.sync.get("settings"); const settings = result.settings || {}; const calendarType = settings.defaultCalendar || "google"; if (calendarType === "icloud") { + const downloadedFiles = []; + // Generate .ics files for iCloud Calendar for (const ev of events) { const icsUrl = createICSDownloadUrl(ev); @@ -74,12 +85,15 @@ export async function openCalendarEventsInBackground(events) { // Track this download for blob URL cleanup activeDownloads.set(downloadId, icsUrl); + downloadedFiles.push({ downloadId, filename }); } catch (e) { console.error("Download failed:", e); // Clean up the blob URL immediately if download fails URL.revokeObjectURL(icsUrl); } } + + notifyIcsDownloadStarted(downloadedFiles, options.onIcsDownloadStarted); } else { // Default to Google Calendar (open URLs in new tabs) for (const ev of events) { diff --git a/src/popup.js b/src/popup.js index 84382a1..d57c354 100644 --- a/src/popup.js +++ b/src/popup.js @@ -859,7 +859,16 @@ addSelectedBtn?.addEventListener("click", async () => { return !Number.isNaN(i) ? currentEvents[i] : null; }) .filter(Boolean); - if (selected.length) await openCalendarEventsInBackground(selected); + if (selected.length) { + await openCalendarEventsInBackground(selected, { + onIcsDownloadStarted: ({ count }) => { + const message = count === 1 + ? "After it downloads, open the .ics file to add it to your calendar." + : "After they download, open the .ics files to add them to your calendar."; + showToast(message, "info"); + }, + }); + } } catch (e) { error(e); } diff --git a/tests/openCalendar.test.js b/tests/openCalendar.test.js new file mode 100644 index 0000000..673615f --- /dev/null +++ b/tests/openCalendar.test.js @@ -0,0 +1,110 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +function makeEvent(overrides = {}) { + return { + title: "Breakfast Meetup", + startDate: "2026-07-01", + startTime: "08:00", + location: "Cafe", + ...overrides, + }; +} + +async function loadOpenCalendarModule({ calendarType = "icloud" } = {}) { + const originalChrome = globalThis.chrome; + const originalCreateObjectURL = URL.createObjectURL; + const originalRevokeObjectURL = URL.revokeObjectURL; + const downloads = []; + const tabs = []; + const revokedUrls = []; + let blobId = 0; + + globalThis.chrome = { + downloads: { + onChanged: { + addListener() {}, + }, + download: async (options) => { + downloads.push(options); + return downloads.length; + }, + }, + storage: { + sync: { + get: async () => ({ settings: { defaultCalendar: calendarType } }), + }, + }, + tabs: { + create: async (options) => { + tabs.push(options); + }, + }, + }; + + URL.createObjectURL = () => `blob:eventy-${++blobId}`; + URL.revokeObjectURL = (url) => { + revokedUrls.push(url); + }; + + const cacheKey = `${Date.now()}-${Math.random()}`; + const calendar = await import(`../src/background/openCalendar.js?test=${cacheKey}`); + + return { + ...calendar, + downloads, + tabs, + revokedUrls, + restore() { + if (originalChrome === undefined) { + delete globalThis.chrome; + } else { + globalThis.chrome = originalChrome; + } + URL.createObjectURL = originalCreateObjectURL; + URL.revokeObjectURL = originalRevokeObjectURL; + }, + }; +} + +test("iCloud downloads report files to open after download", async () => { + const env = await loadOpenCalendarModule({ calendarType: "icloud" }); + + try { + const notices = []; + + await env.openCalendarEventsInBackground([makeEvent()], { + onIcsDownloadStarted: (notice) => { + notices.push(notice); + }, + }); + + assert.equal(env.downloads.length, 1); + assert.equal(notices.length, 1); + assert.equal(notices[0].count, 1); + assert.equal(notices[0].filenames.length, 1); + assert.match(notices[0].filenames[0], /\.ics$/); + } finally { + env.restore(); + } +}); + +test("non iCloud calendars open tabs without iCalendar notice", async () => { + const env = await loadOpenCalendarModule({ calendarType: "google" }); + + try { + const notices = []; + + await env.openCalendarEventsInBackground([makeEvent()], { + onIcsDownloadStarted: (notice) => { + notices.push(notice); + }, + }); + + assert.equal(env.tabs.length, 1); + assert.equal(env.downloads.length, 0); + assert.deepEqual(notices, []); + } finally { + env.restore(); + } +}); diff --git a/tests/popupUi.test.js b/tests/popupUi.test.js index 3a27d59..20cb616 100644 --- a/tests/popupUi.test.js +++ b/tests/popupUi.test.js @@ -56,3 +56,16 @@ test("popup toast leaves layout after it fades out", () => { assert.match(js, /toast\.classList\.add\("hidden"\);/); assert.match(js, /clearTimeout\(Number\(toast\.dataset\.hideTimeoutId\)\);/); }); + +test("popup tells iCalendar users to open downloaded files", () => { + const js = fs.readFileSync(new URL("../src/popup.js", import.meta.url), "utf8"); + const start = js.indexOf("addSelectedBtn?.addEventListener"); + assert.notEqual(start, -1, "add selected handler should exist"); + const end = js.indexOf("// Check if a scan is in progress", start); + assert.notEqual(end, -1, "add selected handler should be bounded"); + const handler = js.slice(start, end); + + assert.match(handler, /onIcsDownloadStarted/); + assert.match(handler, /After it downloads, open the \.ics file/); + assert.match(handler, /showToast\(/); +}); From b1994e904f7070c2df5d1682ce8f41284555af18 Mon Sep 17 00:00:00 2001 From: Ario Barin Ostovary <89481178+Doomsy1@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:23:38 -0400 Subject: [PATCH 2/6] prepared eventy 1.2.1 --- PRIVACY.md | 2 +- README.md | 6 +- TERMS.md | 2 +- docs/assets/eventy-social-preview.png | Bin 0 -> 44806 bytes docs/index.html | 163 +++++++++++++++++++++++++- docs/privacy.html | 9 +- docs/robots.txt | 4 + docs/sitemap.xml | 12 ++ docs/terms.html | 5 +- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- tests/config.test.js | 6 +- tests/eventySeo.test.js | 163 ++++++++++++++++++++++++++ tmp/eventy-seo-checklist.md | 47 ++++++++ tmp/seo-gaps.md | 151 ++++++++++++++++++++++++ webstore-docs/STORE_DESCRIPTION.txt | 9 +- 17 files changed, 567 insertions(+), 20 deletions(-) create mode 100644 docs/assets/eventy-social-preview.png create mode 100644 docs/robots.txt create mode 100644 docs/sitemap.xml create mode 100644 tests/eventySeo.test.js create mode 100644 tmp/eventy-seo-checklist.md create mode 100644 tmp/seo-gaps.md diff --git a/PRIVACY.md b/PRIVACY.md index 182ae4a..3abc1af 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -4,7 +4,7 @@ Eventy processes webpage or selected text only when you use the "Scan" action. T Data handling: -- No third-party trackers are embedded. We do not sell or share your personal information. +- The Eventy extension does not embed third-party trackers or run extension analytics in the browser. The product website may use Cloudflare traffic measurement and security tooling. We do not sell or share your personal information. - Minimal Google scope used: https://www.googleapis.com/auth/calendar.events (create events only). - **BYOK (Bring Your Own Key):** If you provide your own OpenRouter API key, it is transmitted securely via our Cloudflare Worker proxy to authenticate your requests. We do not store your API key on our servers; it is passed through to OpenRouter. - **Image Scanning:** Images you choose to scan are processed by our proxy and LLM provider to extract event details. We do not store these images. diff --git a/README.md b/README.md index 87bb857..ee3618a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Eventy is a Chrome extension that finds event details on webpages, images, and pasted text, then turns them into calendar-ready entries. -Version: v1.2.0 +Version: v1.2.1 Homepage: https://eventy.ariobarin.com/ @@ -102,8 +102,8 @@ npm run deploy:home ## Legal -- [Privacy Policy](https://ariobarin.github.io/Eventy/privacy.html) -- [Terms of Service](https://ariobarin.github.io/Eventy/terms.html) +- [Privacy Policy](https://eventy.ariobarin.com/privacy.html) +- [Terms of Service](https://eventy.ariobarin.com/terms.html) ## License diff --git a/TERMS.md b/TERMS.md index c0ef38b..826763a 100644 --- a/TERMS.md +++ b/TERMS.md @@ -17,7 +17,7 @@ Effective date: 2026-01-26 - Minimal Google scope used: https://www.googleapis.com/auth/calendar.events (create events only, when enabled). - **API Keys (BYOK):** Users providing their own API keys depend on OpenRouter services. We facilitate the request but are not responsible for OpenRouter availability. - Tokens are obtained via Chrome Identity; Eventy does not store tokens on our servers. -- See Privacy Policy for more details: https://ariobarin.github.io/Eventy/privacy.html. +- See Privacy Policy for more details: https://eventy.ariobarin.com/privacy.html. 4. Availability and changes diff --git a/docs/assets/eventy-social-preview.png b/docs/assets/eventy-social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5911150fb023d738dffd71d5bb2129eec9140e GIT binary patch literal 44806 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 - Eventy - + Eventy: AI event extractor for any calendar + + + + + + + + + + + + + + + @@ -616,6 +722,59 @@

Eventy

+ +
+

AI event extraction for the places plans show up.

+

+ Eventy is a Chrome extension for turning messy event details into calendar-ready entries. It works + from web pages, selected text, emails, flyers, screenshots, and images, then lets you review the result + before anything reaches your calendar. +

+ +
+
+

Scan web pages and selected text

+

+ Use Eventy as an AI event extractor on event pages, newsletters, email views, and posts. Scan a + full page or highlight only the text you want parsed. +

+
+
+

Read flyers, screenshots, and images

+

+ Send a flyer, poster, screenshot, or event image through the extension and turn the visible + date, time, title, and location into structured event fields. +

+
+
+

Review before adding

+

+ Eventy separates upcoming and past results, preserves useful details, and gives you a chance to + choose the events you want before creating anything. +

+
+
+

Export to Google Calendar or iCalendar

+

+ Add selected events to Google Calendar or download iCalendar files for Apple Calendar, Outlook, + iCloud, and other calendars that understand .ics files. +

+
+
+
+ +
+

Privacy and control stay part of the workflow.

+

+ Eventy scans only when you ask it to scan. You can use the shared service or bring your own OpenRouter + key, review extracted details before export, and disconnect Google Calendar access from the extension + settings at any time. +

+ +