From 5ba3ef9f0f65d7e568958a4153531ea6c7fbfa86 Mon Sep 17 00:00:00 2001 From: Skully <117316808+skullysmods@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:59:42 +0100 Subject: [PATCH 1/3] fix(Notion): complete rework of the Activity --- websites/N/Notion/Notion.json | 102 +++++++++ websites/N/Notion/metadata.json | 43 ++-- websites/N/Notion/presence.ts | 362 +++++++++++++++++++++++--------- 3 files changed, 390 insertions(+), 117 deletions(-) create mode 100644 websites/N/Notion/Notion.json diff --git a/websites/N/Notion/Notion.json b/websites/N/Notion/Notion.json new file mode 100644 index 000000000000..c9c4dbfeb64e --- /dev/null +++ b/websites/N/Notion/Notion.json @@ -0,0 +1,102 @@ +{ + "notion.aiResponding": { + "description": "Displayed when the AI is generating a response.", + "message": "Notion AI is responding..." + }, + "notion.browseCategory": { + "description": "Displayed when the user browsing template categories", + "message": "Browsing categories" + }, + "notion.browsingTemplates": { + "description": "Displayed when the user browsing templates", + "message": "Browsing templates" + }, + "notion.category": { + "description": "Displayed when the user viewing a category", + "message": "Category: {0}" + }, + "notion.conversationStats": { + "description": "Displayed to show the number of times the user asked a question and total words used, e.g., 'asked 3 times | 150 words'.", + "message": "asked {0} times | {1} words" + }, + "notion.editAMeetingNote": { + "description": "Displayed when the user editing a meeting note", + "message": "Editing a meeting note" + }, + "notion.editMeetingNote": { + "description": "Displayed when the user editing a specifig meeting note", + "message": "Editing meeting note:" + }, + "notion.editing": { + "description": "Displayed when the user editing", + "message": "Editing" + }, + "notion.editingAPage": { + "description": "Displayed when the user editing a page", + "message": "Editing a page" + }, + "notion.editingPage": { + "description": "Displayed when the user editing a specific page", + "message": "Editing page:" + }, + "notion.reading": { + "description": "Displayed when the user reading", + "message": "Reading" + }, + "notion.readingAPage": { + "description": "Displayed when the user reading a page", + "message": "Reading a page" + }, + "notion.readingBlogs": { + "description": "Displayed when the user reading a blog", + "message": "Reading blogs" + }, + "notion.readingCustomerReview": { + "description": "Displayed when the user reading customer reviews", + "message": "Reading customer reviews" + }, + "notion.readingPage": { + "description": "Displayed when the user reading a specifig page", + "message": "Reading page:" + }, + "notion.searchingTemplate": { + "description": "Displayed when the user searching a template", + "message": "Searching template: {0}" + }, + "notion.startNewConversation": { + "description": "Displayed when the user is starting a new conversation.", + "message": "Start new conversation" + }, + "notion.talkingWithAI": { + "description": "Displayed when the user is engaging in a conversation with the AI.", + "message": "Talking with Notion AI about something" + }, + "notion.template": { + "description": "Displayed when the user viewing a template", + "message": "Template: {0}" + }, + "notion.thinkingOfPrompt": { + "description": "Displayed when the user is thinking of a new prompt.", + "message": "Thinking of a new prompt..." + }, + "notion.unknown": { + "description": "Displayed when the user viewing an unknown page/template/category", + "message": "Unknown" + }, + "notion.viewDownloadsPage": { + "description": "Displayed when the user viewing a download page", + "message": "Viewing Downloads page" + }, + "notion.viewProductsPage": { + "description": "Displayed when the user viewing a product page", + "message": "Viewing Products page" + }, + "notion.viewPublicWebsite": { + "description": "Displayed when the user viewing a public Notion website", + "message": "Viewing a public Notion website" + }, + "notion.viewUpcomingMeeting": { + "description": "Displayed when the user viewing their upcoming meetings", + "message": "Viewing their upcoming meetings" + } +} diff --git a/websites/N/Notion/metadata.json b/websites/N/Notion/metadata.json index 31a3e0031966..ea8b98c3ca29 100644 --- a/websites/N/Notion/metadata.json +++ b/websites/N/Notion/metadata.json @@ -2,18 +2,28 @@ "$schema": "https://schemas.premid.app/metadata/1.16", "apiVersion": 1, "author": { - "name": "Kyrie", - "id": "368399721494216706" + "id": "671037171611729920", + "name": "atom_skully" }, + "contributors": [ + { + "id": "368399721494216706", + "name": "Kyrie" + } + ], "service": "Notion", "description": { - "en": "Notion is a notetaking software and project management software that is used for note-taking, task management, project management, knowledge management, and personal knowledge management. The app uses databases and markdown pages for use in personal and collaboration work." + "en": "Notion is a notetaking software and project management software that is used for note-taking, task management, project management, knowledge management, and personal knowledge management. The app uses databases and markdown pages for use in personal and collaboration work.", + "fr": "Notion est un logiciel de prise de notes et de gestion de projet utilisé pour la prise de notes, la gestion de tâches, la gestion de projet, la gestion des connaissances et la gestion des connaissances personnelles. L'application utilise des bases de données et des pages Markdown pour un usage personnel et collaboratif." }, - "url": "www.notion.so", - "regExp": "^https?[:][/][/](www[.])?notion[.]so[/]", - "version": "1.2.0", - "logo": "https://cdn.rcd.gg/PreMiD/websites/N/Notion/assets/logo.png", - "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/N/Notion/assets/thumbnail.png", + "url": [ + "www.notion.com", + "www.notion.so" + ], + "regExp": "^https?[:][/][/]([a-z0-9-]+[.])*notion[.](so|com|site)[/]", + "version": "2.0.0", + "logo": "https://i.imgur.com/IVQpP2j.png", + "thumbnail": "https://i.imgur.com/zfnIlqS.png", "color": "#000000", "category": "other", "tags": [ @@ -23,19 +33,14 @@ ], "settings": [ { - "id": "title", - "title": "Show document name", - "icon": "fas fa-file-alt", - "value": false + "id": "lang", + "multiLanguage": true }, { - "id": "icon", - "title": "Show document icon", - "icon": "fas fa-images", - "value": false, - "if": { - "title": true - } + "id": "privacy", + "title": "Privacy Mode", + "icon": "fad fa-user-secret", + "value": true } ] } diff --git a/websites/N/Notion/presence.ts b/websites/N/Notion/presence.ts index 74e9fa608ac8..b43ff033645f 100644 --- a/websites/N/Notion/presence.ts +++ b/websites/N/Notion/presence.ts @@ -1,119 +1,285 @@ import { Assets } from 'premid' const presence = new Presence({ - clientId: '926386695354609684', + clientId: '1455312389837684951', }) const browsingTimestamp = Math.floor(Date.now() / 1000) -const shortenedURLs: Record = {} -async function shortenURL(url: string, fallback: string): Promise { - if (!url || url.length < 256) - return url - if (shortenedURLs[url]) - return shortenedURLs[url] - try { - return (shortenedURLs[url] = await ( - await fetch(`https://pd.premid.app/create/${url}`) - ).text()) - } - catch (err) { - presence.error(err as string) - return fallback +enum ActivityAssets { + Logo = 'https://i.imgur.com/IVQpP2j.png', + Calendar = 'https://i.imgur.com/MPUipsT.png', + Mail = 'https://i.imgur.com/vIGQhCL.png', + Talking = 'https://i.imgur.com/aWWXjuc.png', +} + +async function svgToPng(svgUrl: string): Promise { + if (!svgUrl || !svgUrl.includes('.svg')) + return + + const response = await fetch(svgUrl) + const svgText = await response.text() + + const img = new Image() + const svgBlob = new Blob([svgText], { type: 'image/svg+xml' }) + const url = URL.createObjectURL(svgBlob) + img.src = url + + await new Promise((resolve, reject) => { + img.onload = resolve + img.onerror = reject + }) + + const maxSize = Math.max(img.width, img.height) + const canvas = document.createElement('canvas') + canvas.width = maxSize + canvas.height = maxSize + const ctx = canvas.getContext('2d') + + let png: string | undefined + if (ctx) { + ctx.clearRect(0, 0, canvas.width, canvas.height) + + const x = (maxSize - img.width) / 2 + const y = (maxSize - img.height) / 2 + ctx.drawImage(img, x, y, img.width, img.height) + + png = canvas.toDataURL('image/png') } + + URL.revokeObjectURL(url) + return png } presence.on('UpdateData', async () => { const presenceData: PresenceData = { - largeImageKey: 'https://cdn.rcd.gg/PreMiD/websites/N/Notion/assets/logo.png', + largeImageKey: ActivityAssets.Logo, startTimestamp: browsingTimestamp, } - const path = document.location.pathname - const [title, showPageIcon] = await Promise.all([ - presence.getSetting('title'), - presence.getSetting('icon'), - ]) - const overlayTitle = document.querySelector( - 'div.notion-overlay-container.notion-default-overlay-container div[class=\'notranslate\'][contenteditable=\'true\']', - ) - const pageIcon = document.querySelector( - ':is(.notion-frame, .notion-overlay-container.notion-default-overlay-container) .notion-record-icon div > div > img:not(.notion-emoji)', - ) - if (path.startsWith('/product')) { - if (path === '/product') - presenceData.details = 'Viewing Home page' - else presenceData.details = 'Viewing Products page' - } - else if (path.startsWith('/templates')) { - presenceData.details = 'Browsing templates' - } - else if (path.startsWith('/customers')) { - presenceData.details = 'Viewing customers' - } - else if ( - path.startsWith('/desktop') - || path.startsWith('/mobile') - || path.startsWith('/web-clipper') - ) { - presenceData.details = 'Viewing Downloads page' - } - else if (path.startsWith('/blog')) { - presenceData.details = 'Reading blogs' - } - else if (path.startsWith('/guides')) { - presenceData.details = 'Reading Guides & Tutorials' - } - // Clearly not the best solution but it works(?) - else if ( - overlayTitle - || document.querySelector( - 'div.notion-cursor-listener div.notion-frame > div:nth-child(2) > div > div', - ) - ) { - presenceData.details = 'Editing a page' - if (title) { - if (!overlayTitle) { - presenceData.state = document.title + const strings = await presence.getStrings({ + aiResponding: 'notion.aiResponding', + browseCategory: 'notion.browseCategory', + browsingTemplates: 'notion.browsingTemplates', + category: 'notion.category', + conversationStats: 'notion.conversationStats', + editAMeetingNote: 'notion.editAMeetingNote', + editMeetingNote: 'notion.editMeetingNote', + editing: 'notion.editing', + editingAPage: 'notion.editingAPage', + editingPage: 'notion.editingPage', + reading: 'notion.reading', + readingAPage: 'notion.readingAPage', + readingAnArticle: 'general.readingAnArticle', + readingArticle: 'general.readingArticle', + readingBlogs: 'notion.readingBlogs', + readingCustomerReview: 'notion.readingCustomerReview', + readingPage: 'notion.readingPage', + searchingTemplate: 'notion.searchingTemplate', + startNewConversation: 'notion.startNewConversation', + talkingWithAI: 'notion.talkingWithAI', + template: 'notion.template', + thinkingOfPrompt: 'notion.thinkingOfPrompt', + unknown: 'notion.unknown', + viewAProfile: 'general.viewAProfile', + viewDownloadsPage: 'notion.viewDownloadsPage', + viewHome: 'general.viewHome', + viewPage: 'general.viewPage', + viewProductsPage: 'notion.viewProductsPage', + viewProfile: 'general.viewProfile', + viewPublicWebsite: 'notion.viewPublicWebsite', + viewUpcomingMeeting: 'notion.viewUpcomingMeeting', + }) + const { hostname, pathname, search } = document.location + const privacy = await presence.getSetting('privacy') + + switch (true) { + case hostname === 'www.notion.com': + if (/^\/(?:[a-z]{2}(?:-[a-z]{2})?)?$/i.test(pathname) || /^(?:\/[a-zA-Z]{2}(?:-[a-zA-Z]{2})?)?\/product(?:$|\/.*)/.test(pathname)) { + if (/^\/(?:[a-z]{2}(?:-[a-z]{2})?)?$/i.test(pathname) || /^(?:\/[a-zA-Z]{2}(?:-[a-zA-Z]{2})?)?\/product$/.test(pathname)) { + presenceData.details = strings.viewHome + } + else if (pathname.endsWith('/download') || pathname.includes('/download')) { + presenceData.details = strings.viewDownloadsPage + } + else { + presenceData.details = strings.viewProductsPage + } + } + else if (pathname.includes('/desktop') || pathname.includes('/mobile') || pathname.includes('/web-clipper')) { + presenceData.details = strings.viewDownloadsPage + } + else if (pathname.includes('/blog')) { + const blogTitle = document.querySelector('h1[class*="title"]') + if (blogTitle) { + presenceData.details = privacy ? strings.readingAnArticle : strings.readingArticle + presenceData.state = privacy ? '' : blogTitle?.textContent?.trim() || document.title + } + else { + presenceData.details = strings.readingBlogs + } + } + else if (pathname.includes('/customers')) { + presenceData.details = strings.readingCustomerReview + } + else if (pathname.includes('/templates')) { + if (pathname.includes('/category')) { + if (/^(?:\/[a-z]{2}(?:-[a-z]{2})?)?\/templates\/category$/i.test(pathname)) { + presenceData.details = strings.browseCategory + } + else { + presenceData.details = strings.browsingTemplates + presenceData.state = strings.category?.replace('{0}', document.querySelector('h1')?.textContent?.trim() || strings.unknown) + } + } + else if (/^(?:\/[a-z]{2}(?:-[a-z]{2})?)?\/templates\/.*$/i.test(pathname)) { + presenceData.details = strings.browsingTemplates + presenceData.state = strings.template?.replace('{0}', document.querySelector('div[role="dialog"] h1')?.textContent?.trim() || document.querySelector('main h1')?.textContent?.trim() || strings.unknown) + } + else { + presenceData.details = strings.browsingTemplates + } + } + else if (pathname.includes('/@')) { + presenceData.details = privacy ? strings.viewAProfile : strings.viewProfile + presenceData.state = privacy ? '' : document.querySelector('main h3')?.textContent } else { - presenceData.state = overlayTitle.textContent || overlayTitle.getAttribute('placeholder') + presenceData.details = strings.viewPage + presenceData.state = document.title?.split('|')[0]?.trim() } + break + case hostname === 'www.notion.so': { + const isHome = document.querySelector('.layout-home') + if (isHome) { + presenceData.details = strings.viewHome + } + else if (pathname.startsWith('/ai')) { + presenceData.details = strings.startNewConversation + presenceData.state = strings.thinkingOfPrompt + } + else if (pathname.startsWith('/chat') && search.startsWith('?t=')) { + const isTalking = document.querySelector( + 'div[data-testid="agent-stop-inference-button"]', + ) + let wordCount = 0 + for (const element of document.querySelectorAll( + 'div[role="textbox"]', + )) { + const text = element.textContent + ?.replace(/, |,\n|,|\. |\./g, ' ') + // eslint-disable-next-line regexp/no-dupe-disjunctions + .replace(/\d*|[/', ]/g, '') + if (text) { + wordCount += text.split(' ').slice(2, text.split(' ').length).length + } + } + presenceData.details = !privacy ? document.title : strings.talkingWithAI + presenceData.state = isTalking + ? strings.aiResponding + : strings.conversationStats + .replace( + '{0}', + `${Number( + document.querySelectorAll('div[role="textbox"].content-editable-leaf-rtl') + .length, + )}`, + ) + .replace('{1}', `${wordCount}`) + presenceData.smallImageKey = isTalking ? ActivityAssets.Talking : null + } + else if (pathname.startsWith('/chat')) { + presenceData.details = strings.startNewConversation + presenceData.state = strings.thinkingOfPrompt + } + else if (pathname.startsWith('/meet') && search.startsWith('?p=')) { + const pageIcon = document.querySelector('div[style*="display: flow-root"] .notion-record-icon img:not([src^="data:image"]):not([src*="notion-emojis"])') + presenceData.details = privacy ? strings.editAMeetingNote : strings.editMeetingNote + presenceData.state = privacy ? '' : document.querySelector('.notion-peek-renderer .layout-content h1')?.textContent || document.querySelector('h1')?.textContent || strings.unknown + if (pageIcon && !privacy) { + presenceData.smallImageKey = pageIcon?.src?.includes('.svg') ? await svgToPng(pageIcon?.src) : pageIcon?.src + } + else { + presenceData.smallImageKey = Assets.Writing + } + presenceData.smallImageText = strings.editing + } + else if (pathname.startsWith('/meet')) { + presenceData.details = strings.viewUpcomingMeeting + } + else if (pathname.startsWith('/marketplace')) { + presenceData.details = strings.browsingTemplates + if (pathname.startsWith('/marketplace/categories')) { + const categoryPath = document.querySelectorAll('.layout-full[style*="position: sticky"] a') + const categoryName = categoryPath[categoryPath.length - 1]?.textContent?.trim() || document.querySelector('.layout-marketplace > .layout-content')?.textContent?.trim() || strings.unknown + presenceData.state = strings.category?.replace('{0}', categoryName) + } + else if (pathname.startsWith('/marketplace/templates')) { + const templatePath = document.querySelectorAll('.layout-full a') + const templateName = document.querySelector('.layout-marketplace > .layout-content div[role="link"]')?.nextElementSibling?.textContent?.trim() || templatePath[templatePath.length - 1]?.textContent?.trim() || strings.unknown + presenceData.state = strings.template?.replace('{0}', templateName) + } + else if (pathname.startsWith('/marketplace/search')) { + const params = new URLSearchParams(search) + presenceData.state = strings.searchingTemplate?.replace('{0}', params.get('query')!) + } + else if (pathname.startsWith('/marketplace/profiles')) { + const profilePath = document.querySelectorAll('.layout-full a') + const profileName = profilePath[profilePath.length - 1]?.textContent?.trim() || strings.unknown + presenceData.details = privacy ? strings.viewAProfile : strings.viewProfile + presenceData.state = privacy ? '' : profileName + } + else { + presenceData.details = strings.browsingTemplates + } + } + else { + const isEditable = document.querySelector('.notion-page-controls > div[role="button"]') + const topBarIcon = document.querySelectorAll('.notion-topbar .notion-record-icon img:not([src^="data:image"]):not([src*="notion-emojis"])') + const pageIcon = document.querySelector('div[style*="display: flow-root"] .notion-record-icon img:not([src^="data:image"]):not([src*="notion-emojis"])') || topBarIcon[topBarIcon.length - 1] + if (isEditable) { + presenceData.details = privacy ? strings.editingAPage : strings.editingPage + presenceData.state = privacy ? '' : document.querySelector('.notion-overlay-container h1')?.textContent || document.querySelector('.notion-peek-renderer .layout-content h1')?.textContent || document.querySelector('h1')?.textContent || document.querySelector('.notion-topbar div[role="button"] > div.notranslate')?.textContent || document.title + if (pageIcon && !privacy) { + presenceData.smallImageKey = pageIcon?.src?.includes('.svg') ? await svgToPng(pageIcon?.src) : pageIcon?.src + } + else { + presenceData.smallImageKey = Assets.Writing + } + presenceData.smallImageText = strings.editing + } + else { + presenceData.details = privacy ? strings.readingAPage : strings.readingPage + presenceData.state = privacy ? '' : document.querySelector('.notion-overlay-container h1')?.textContent || document.querySelector('.notion-peek-renderer .layout-content h1')?.textContent || document.querySelector('h1')?.textContent || document.querySelector('.notion-topbar div[role="button"] > div.notranslate')?.textContent || document.title + if (pageIcon && !privacy) { + presenceData.smallImageKey = pageIcon?.src?.includes('.svg') ? await svgToPng(pageIcon?.src) : pageIcon?.src + } + else { + presenceData.smallImageKey = Assets.Reading + } + presenceData.smallImageText = strings.reading + } + } + break } - - if (title && showPageIcon) { - presenceData.smallImageKey = pageIcon - ? pageIcon.alt - ? `https://twemoji.maxcdn.com/v/latest/72x72/${pageIcon.alt - .codePointAt(0) - ?.toString(16)}.png` - : await shortenURL(pageIcon.src, 'edit') - : 'edit' - } - else { - presenceData.smallImageKey = Assets.Writing - } - presenceData.smallImageText = 'Editing' - } - else if ( - document.querySelector( - 'div.notion-topbar div.notion-focusable > div[class=\'notranslate\']', - ) - ) { - presenceData.details = 'Reading a page' - if (title) - presenceData.state = document.title - if (title && showPageIcon) { - presenceData.smallImageKey = pageIcon - ? pageIcon.alt - ? `https://twemoji.maxcdn.com/v/latest/72x72/${pageIcon.alt - .codePointAt(0) - ?.toString(16)}.png` - : await shortenURL(pageIcon.src, 'read') - : 'read' - } - else { - presenceData.smallImageKey = Assets.Reading + case /^[a-z0-9-]+\.notion\.site$/.test(hostname): { + const websiteLogo = document.querySelector('div[style*="display: flow-root"] .notion-record-icon img:not([src^="data:image"]):not([src*="notion-emojis"])')?.src || document.querySelector('.notion-topbar .notion-record-icon img:not([src^="data:image"]):not([src*="notion-emojis"])')?.src || ActivityAssets.Logo + presenceData.details = strings.viewPublicWebsite + presenceData.state = privacy ? '' : document.querySelector('h1')?.textContent || document.querySelector('.notion-topbar span')?.textContent || document.title + presenceData.largeImageKey = privacy ? ActivityAssets.Logo : (websiteLogo.includes('.svg') ? await svgToPng(websiteLogo) || ActivityAssets.Logo : websiteLogo || ActivityAssets.Logo) + break } - presenceData.smallImageText = 'Reading' + case hostname === 'calendar.notion.so': + presenceData.name = 'Notion Calendar' + presenceData.largeImageKey = ActivityAssets.Calendar + break + case hostname === 'mail.notion.so': + presenceData.name = 'Notion Mail' + presenceData.largeImageKey = ActivityAssets.Mail + break + default: + presenceData.details = strings.viewPage + presenceData.state = document.title?.split('|')[0]?.trim() + break } + presence.setActivity(presenceData) }) From 46e3a5c915d4945f57a38165ec8a8fe9f03a82f8 Mon Sep 17 00:00:00 2001 From: Skully <117316808+skullysmods@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:32:08 +0100 Subject: [PATCH 2/3] feat(Notion): add compatibility to Notion Calendar & Notion Mail --- websites/N/Notion/Notion.json | 68 ++++++++++++++++++++++ websites/N/Notion/metadata.json | 8 ++- websites/N/Notion/presence.ts | 99 ++++++++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 6 deletions(-) diff --git a/websites/N/Notion/Notion.json b/websites/N/Notion/Notion.json index c9c4dbfeb64e..c5c390435588 100644 --- a/websites/N/Notion/Notion.json +++ b/websites/N/Notion/Notion.json @@ -15,10 +15,18 @@ "description": "Displayed when the user viewing a category", "message": "Category: {0}" }, + "notion.composingEmail": { + "description": "Displayed when the user is composing an email", + "message": "Composing an email" + }, "notion.conversationStats": { "description": "Displayed to show the number of times the user asked a question and total words used, e.g., 'asked 3 times | 150 words'.", "message": "asked {0} times | {1} words" }, + "notion.daySchedule": { + "description": "Displayed when the user is viewing a calendar with day schedule.", + "message": "Viewing the day schedule:" + }, "notion.editAMeetingNote": { "description": "Displayed when the user editing a meeting note", "message": "Editing a meeting note" @@ -35,10 +43,26 @@ "description": "Displayed when the user editing a page", "message": "Editing a page" }, + "notion.editingAnEvent": { + "description": "Displayed when the user is editing an event.", + "message": "Editing an event" + }, "notion.editingPage": { "description": "Displayed when the user editing a specific page", "message": "Editing page:" }, + "notion.editingTheirSettings": { + "description": "Displayed when the user editing their settings", + "message": "Editing their settings" + }, + "notion.lookingForEmail": { + "description": "Displayed when the user is searching for an email", + "message": "Looking for an email" + }, + "notion.monthSchedule": { + "description": "Displayed when the user is viewing a calendar with month schedule.", + "message": "Viewing the month schedule:" + }, "notion.reading": { "description": "Displayed when the user reading", "message": "Reading" @@ -59,6 +83,10 @@ "description": "Displayed when the user reading a specifig page", "message": "Reading page:" }, + "notion.schedulingMeetings": { + "description": "Displayed when the user scheduling meetings", + "message": "Scheduling meetings" + }, "notion.searchingTemplate": { "description": "Displayed when the user searching a template", "message": "Searching template: {0}" @@ -98,5 +126,45 @@ "notion.viewUpcomingMeeting": { "description": "Displayed when the user viewing their upcoming meetings", "message": "Viewing their upcoming meetings" + }, + "notion.viewingAllEmails": { + "description": "Displayed when the user is viewing all their emails", + "message": "Viewing all emails" + }, + "notion.viewingCalendar": { + "description": "Displayed when the user is viewing a calendar.", + "message": "Viewing the calendar" + }, + "notion.viewingCategory": { + "description": "Displayed when the user viewing a category", + "message": "Viewing category" + }, + "notion.viewingDrafts": { + "description": "Displayed when the user is viewing their drafts", + "message": "Viewing drafts" + }, + "notion.viewingEmail": { + "description": "Displayed when the user is viewing an email", + "message": "Viewing an email" + }, + "notion.viewingInbox": { + "description": "Displayed when the user viewing the inbox", + "message": "Viewing inbox" + }, + "notion.viewingSentEmails": { + "description": "Displayed when the user is viewing the emails they have sent", + "message": "Viewing sent emails" + }, + "notion.viewingSpam": { + "description": "Displayed when the user is viewing their spam", + "message": "Viewing spam emails" + }, + "notion.viewingTrash": { + "description": "Displayed when the user is viewing their trash", + "message": "Viewing trash" + }, + "notion.weekSchedule": { + "description": "Displayed when the user is viewing a calendar with week schedule.", + "message": "Viewing the week schedule:" } } diff --git a/websites/N/Notion/metadata.json b/websites/N/Notion/metadata.json index ea8b98c3ca29..01971a8e73dc 100644 --- a/websites/N/Notion/metadata.json +++ b/websites/N/Notion/metadata.json @@ -13,12 +13,14 @@ ], "service": "Notion", "description": { - "en": "Notion is a notetaking software and project management software that is used for note-taking, task management, project management, knowledge management, and personal knowledge management. The app uses databases and markdown pages for use in personal and collaboration work.", - "fr": "Notion est un logiciel de prise de notes et de gestion de projet utilisé pour la prise de notes, la gestion de tâches, la gestion de projet, la gestion des connaissances et la gestion des connaissances personnelles. L'application utilise des bases de données et des pages Markdown pour un usage personnel et collaboratif." + "en": "Notion is your connected workspace where documents, projects, and knowledge come together to streamline team collaboration. This ecosystem now extends to Notion Calendar, which seamlessly syncs your schedule with your priorities, and Notion Mail, designed to integrate your conversations directly into your workflow for a truly unified and frictionless experience.", + "fr": "Notion est votre espace de travail connecté où les documents, les projets et les connaissances convergent pour simplifier la collaboration d'équipe. Cet écosystème s'appuie désormais sur Notion Calendar pour synchroniser votre temps avec vos priorités et sur Notion Mail pour intégrer vos échanges directement dans vos flux de travail, offrant ainsi une plateforme unique et fluide qui centralise l'intégralité de votre vie professionnelle." }, "url": [ "www.notion.com", - "www.notion.so" + "www.notion.so", + "calendar.notion.so", + "mail.notion.so" ], "regExp": "^https?[:][/][/]([a-z0-9-]+[.])*notion[.](so|com|site)[/]", "version": "2.0.0", diff --git a/websites/N/Notion/presence.ts b/websites/N/Notion/presence.ts index b43ff033645f..ea443d4fd2e7 100644 --- a/websites/N/Notion/presence.ts +++ b/websites/N/Notion/presence.ts @@ -60,12 +60,18 @@ presence.on('UpdateData', async () => { browseCategory: 'notion.browseCategory', browsingTemplates: 'notion.browsingTemplates', category: 'notion.category', + composingEmail: 'notion.composingEmail', conversationStats: 'notion.conversationStats', + daySchedule: 'notion.daySchedule', editAMeetingNote: 'notion.editAMeetingNote', editMeetingNote: 'notion.editMeetingNote', editing: 'notion.editing', + editingAnEvent: 'notion.editingAnEvent', editingAPage: 'notion.editingAPage', editingPage: 'notion.editingPage', + editingTheirSettings: 'notion.editingTheirSettings', + lookingForEmail: 'notion.lookingForEmail', + monthSchedule: 'notion.monthSchedule', reading: 'notion.reading', readingAPage: 'notion.readingAPage', readingAnArticle: 'general.readingAnArticle', @@ -73,6 +79,7 @@ presence.on('UpdateData', async () => { readingBlogs: 'notion.readingBlogs', readingCustomerReview: 'notion.readingCustomerReview', readingPage: 'notion.readingPage', + schedulingMeetings: 'notion.schedulingMeetings', searchingTemplate: 'notion.searchingTemplate', startNewConversation: 'notion.startNewConversation', talkingWithAI: 'notion.talkingWithAI', @@ -87,12 +94,22 @@ presence.on('UpdateData', async () => { viewProfile: 'general.viewProfile', viewPublicWebsite: 'notion.viewPublicWebsite', viewUpcomingMeeting: 'notion.viewUpcomingMeeting', + viewingAllEmails: 'notion.viewingAllEmails', + viewingCalendar: 'notion.viewingCalendar', + viewingCategory: 'notion.viewingCategory', + viewingDrafts: 'notion.viewingDrafts', + viewingEmail: 'notion.viewingEmail', + viewingInbox: 'notion.viewingInbox', + viewingSentEmails: 'notion.viewingSentEmails', + viewingSpam: 'notion.viewingSpam', + viewingTrash: 'notion.viewingTrash', + weekSchedule: 'notion.weekSchedule', }) const { hostname, pathname, search } = document.location const privacy = await presence.getSetting('privacy') switch (true) { - case hostname === 'www.notion.com': + case hostname === 'www.notion.com': { if (/^\/(?:[a-z]{2}(?:-[a-z]{2})?)?$/i.test(pathname) || /^(?:\/[a-zA-Z]{2}(?:-[a-zA-Z]{2})?)?\/product(?:$|\/.*)/.test(pathname)) { if (/^\/(?:[a-z]{2}(?:-[a-z]{2})?)?$/i.test(pathname) || /^(?:\/[a-zA-Z]{2}(?:-[a-zA-Z]{2})?)?\/product$/.test(pathname)) { presenceData.details = strings.viewHome @@ -147,6 +164,7 @@ presence.on('UpdateData', async () => { presenceData.state = document.title?.split('|')[0]?.trim() } break + } case hostname === 'www.notion.so': { const isHome = document.querySelector('.layout-home') if (isHome) { @@ -267,14 +285,89 @@ presence.on('UpdateData', async () => { presenceData.largeImageKey = privacy ? ActivityAssets.Logo : (websiteLogo.includes('.svg') ? await svgToPng(websiteLogo) || ActivityAssets.Logo : websiteLogo || ActivityAssets.Logo) break } - case hostname === 'calendar.notion.so': + case hostname === 'calendar.notion.so': { presenceData.name = 'Notion Calendar' presenceData.largeImageKey = ActivityAssets.Calendar + const date = document.title?.split('·')[0]?.trim() + if (pathname === '/') { + presenceData.details = strings.viewingCalendar + } + else { + if (pathname.includes('/day')) { + presenceData.details = strings.daySchedule + presenceData.state = date + } + else if (pathname.includes('/week')) { + presenceData.details = strings.weekSchedule + presenceData.state = date + } + else if (pathname.includes('/month')) { + presenceData.details = strings.monthSchedule + presenceData.state = date + } + else if (pathname.includes('/event')) { + presenceData.details = strings.editingAnEvent + presenceData.state = privacy ? '' : document.querySelector('div[data-floating-context-panel="true"] p')?.textContent?.trim() || document.querySelector('div[data-context-panel-root="true"] p')?.textContent + } + else if (pathname.includes('/settings')) { + presenceData.details = strings.editingTheirSettings + } + else if (pathname.includes('/scheduling')) { + presenceData.details = strings.schedulingMeetings + presenceData.state = privacy ? '' : document.querySelector('div[data-floating-context-panel="true"] p')?.textContent?.trim() || document.querySelector('div[data-context-panel-root="true"] p')?.textContent + } + else { + presenceData.details = strings.viewPage + presenceData.state = document.title?.split('·')[0]?.trim() + } + } break - case hostname === 'mail.notion.so': + } + case hostname === 'mail.notion.so': { + const params = new URLSearchParams(search) + const mailTitle = document.querySelector('div[data-floating-ui-inert] div#thread-content-container span') + const compose = document.querySelector('div#compose-container') presenceData.name = 'Notion Mail' presenceData.largeImageKey = ActivityAssets.Mail + if (mailTitle) { + presenceData.details = strings.viewingEmail + presenceData.state = privacy ? '' : mailTitle?.textContent?.trim() + } + else if (compose) { + presenceData.details = strings.composingEmail + } + else if (params.has('settingLabel')) { + presenceData.details = strings.editingTheirSettings + } + else if (pathname?.includes('/inbox') || pathname === '/') { + presenceData.details = strings.viewingInbox + } + else if (pathname?.includes('/allmail')) { + presenceData.details = strings.viewingAllEmails + } + else if (pathname?.includes('/sent')) { + presenceData.details = strings.viewingSentEmails + } + else if (pathname?.includes('/drafts')) { + presenceData.details = strings.viewingDrafts + } + else if (pathname?.includes('/spam')) { + presenceData.details = strings.viewingSpam + } + else if (pathname?.includes('/trash')) { + presenceData.details = strings.viewingTrash + } + else if (pathname?.includes('/search')) { + presenceData.details = strings.lookingForEmail + presenceData.state = privacy ? '' : document.querySelector('input#search-input')?.textContent?.trim() || params.get('query') || '' + } + else { + const categoryName = document.title?.split('•') + presenceData.details = strings.viewingCategory + presenceData.state = privacy || categoryName.length <= 2 ? '' : (categoryName.length === 3 ? categoryName[0] : categoryName[1]) + } break + } default: presenceData.details = strings.viewPage presenceData.state = document.title?.split('|')[0]?.trim() From eedfa77b746ea75673877b7c53a6758f80c78046 Mon Sep 17 00:00:00 2001 From: Skully <117316808+skullysmods@users.noreply.github.com> Date: Sun, 4 Jan 2026 22:10:16 +0100 Subject: [PATCH 3/3] feat(Notion): cache image conversion --- websites/N/Notion/presence.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/websites/N/Notion/presence.ts b/websites/N/Notion/presence.ts index ea443d4fd2e7..2b2d5fb2b2b7 100644 --- a/websites/N/Notion/presence.ts +++ b/websites/N/Notion/presence.ts @@ -4,6 +4,7 @@ const presence = new Presence({ clientId: '1455312389837684951', }) const browsingTimestamp = Math.floor(Date.now() / 1000) +const svgCache = new Map() enum ActivityAssets { Logo = 'https://i.imgur.com/IVQpP2j.png', @@ -13,6 +14,10 @@ enum ActivityAssets { } async function svgToPng(svgUrl: string): Promise { + if (svgCache.has(svgUrl)) { + return svgCache.get(svgUrl) + } + if (!svgUrl || !svgUrl.includes('.svg')) return @@ -44,6 +49,7 @@ async function svgToPng(svgUrl: string): Promise { ctx.drawImage(img, x, y, img.width, img.height) png = canvas.toDataURL('image/png') + svgCache.set(svgUrl, png) } URL.revokeObjectURL(url)