-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat(Dealabs): add activity #10324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(Dealabs): add activity #10324
Changes from 2 commits
6f8cf13
dfb995e
b0b9c15
1b26e1f
9f60b1f
4dd5af1
019f6b3
2637b94
642d9bd
293c9d4
7c07b34
d1d6dc5
669bb04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||
| { | ||||||
| "$schema": "https://schemas.premid.app/metadata/1.16", | ||||||
| "apiVersion": 1, | ||||||
| "author": { | ||||||
| "id": "188370726279970816", | ||||||
| "name": "linkredible" | ||||||
| }, | ||||||
| "service": "Dealabs", | ||||||
| "description": { | ||||||
| "en": "The largest community for sharing deals and coupons in France.", | ||||||
| "fr": "La plus grande communauté de partage de bons plans et codes promo en France." | ||||||
| }, | ||||||
| "url": "dealabs.com", | ||||||
| "regExp": "^https?://(www\\.)?dealabs\\.com/.*", | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| "version": "1.0.0", | ||||||
| "logo": "https://www.dealabs.com/assets/img/appicon_cbea2.png", | ||||||
|
||||||
| "thumbnail": "https://www.dealabs.com/assets/img/appicon_cbea2.png", | ||||||
| "color": "#36bca1", | ||||||
| "category": "socials", | ||||||
| "tags": [ | ||||||
| "deals", | ||||||
| "shopping", | ||||||
| "community", | ||||||
| "france" | ||||||
| ], | ||||||
| "settings": [ | ||||||
| { | ||||||
| "id": "lang", | ||||||
| "multiLanguage": true | ||||||
| }, | ||||||
| { | ||||||
| "id": "privacyMode", | ||||||
| "title": "Mode Confidentiel (Global)", | ||||||
| "icon": "fas fa-user-secret", | ||||||
| "placeholder": "Masquer tous les détails (titres, prix, images)", | ||||||
| "value": false | ||||||
| }, | ||||||
| { | ||||||
| "id": "hideDealTitles", | ||||||
| "title": "Masquer titres (Bons plans)", | ||||||
| "icon": "fas fa-shopping-bag", | ||||||
| "placeholder": "Remplace le titre du produit par un texte générique", | ||||||
| "value": false | ||||||
| }, | ||||||
| { | ||||||
| "id": "hideDiscussionTitles", | ||||||
| "title": "Masquer titres (Forum)", | ||||||
| "icon": "fas fa-comments", | ||||||
| "placeholder": "Remplace le sujet de la discussion par un texte générique", | ||||||
| "value": false | ||||||
| }, | ||||||
| { | ||||||
| "id": "hideImages", | ||||||
| "title": "Images génériques uniquement", | ||||||
| "icon": "fas fa-image", | ||||||
| "placeholder": "Affiche toujours le logo Dealabs au lieu des produits", | ||||||
| "value": false | ||||||
| }, | ||||||
| { | ||||||
| "id": "hidePrices", | ||||||
| "title": "Masquer les prix", | ||||||
| "icon": "fas fa-tag", | ||||||
| "placeholder": "N'affiche pas le prix du deal dans le statut", | ||||||
| "value": false | ||||||
| } | ||||||
maximedeprince marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ] | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| import type { DealabsSettings, Resolver } from './util/interfaces.js' | ||
| import dealResolver from './sources/deal.js' | ||
|
|
||
| import discussionResolver from './sources/discussion.js' | ||
| import groupResolver from './sources/group.js' | ||
| import listingResolver from './sources/listing.js' | ||
| import merchantResolver from './sources/merchant.js' | ||
| import { activityAssets, presence } from './util/index.js' | ||
|
|
||
| const browsingTimestamp = Math.floor(Date.now() / 1000) | ||
|
|
||
| async function getStrings() { | ||
| const fr = { | ||
| navigating: 'Navigue sur Dealabs', | ||
| hunting: 'Chasse les bons plans', | ||
| viewDeal: 'Regarde un bon plan', | ||
| onDealabs: 'Sur Dealabs', | ||
| onForum: 'Sur le forum', | ||
| readDiscuss: 'Lit une discussion', | ||
| searchCode: 'Cherche un code promo', | ||
| viewCodes: 'Voir les codes', | ||
| browseDiscuss: 'Parcourt les discussions', | ||
| detail: 'Détail', | ||
| free: 'GRATUIT', | ||
| hiddenDeal: 'Un bon plan', | ||
| hiddenTopic: 'Une discussion', | ||
| viewPage: 'Voir la page', | ||
| exploreCat: 'Explore une catégorie', | ||
| homepage: 'Sur la page d\'accueil', | ||
| forYou: 'Pour vous', | ||
| searchDeal: 'Cherche un bon plan', | ||
| newDeals: 'Nouveaux deals 🕒', | ||
| hotDeals: 'Hot & Tendance 🔥', | ||
| topDeals: 'Les plus Hot 🌶️', | ||
| searching: 'Recherche', | ||
| searchingEllipsis: 'Recherche...', | ||
| } | ||
|
|
||
| const en = { | ||
| navigating: 'Browsing Dealabs', | ||
| hunting: 'Hunting for deals', | ||
| viewDeal: 'Viewing a deal', | ||
| onDealabs: 'On Dealabs', | ||
| onForum: 'On the Forum', | ||
| readDiscuss: 'Reading a discussion', | ||
| searchCode: 'Looking for a promo code', | ||
| viewCodes: 'View codes', | ||
| browseDiscuss: 'Browsing discussions', | ||
| detail: 'Detail', | ||
| free: 'FREE', | ||
| hiddenDeal: 'A deal', | ||
| hiddenTopic: 'A discussion', | ||
| viewPage: 'View page', | ||
| exploreCat: 'Browsing a category', | ||
| homepage: 'On the homepage', | ||
| forYou: 'For You', | ||
| searchDeal: 'Searching for a deal', | ||
| newDeals: 'New Deals 🕒', | ||
| hotDeals: 'Hot & Trending 🔥', | ||
| topDeals: 'Hottest Deals 🌶️', | ||
| searching: 'Searching', | ||
| searchingEllipsis: 'Searching...', | ||
| } | ||
|
|
||
| let lang = await presence.getSetting<string>('lang').catch(() => null) | ||
|
|
||
| if (!lang) { | ||
| lang = navigator.language.startsWith('fr') ? 'fr' : 'en' | ||
| } | ||
|
|
||
| return lang === 'fr' ? fr : en | ||
| } | ||
maximedeprince marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| presence.on('UpdateData', async () => { | ||
| const settings: DealabsSettings = { | ||
| privacyMode: await presence.getSetting('privacyMode'), | ||
| hideDealTitles: await presence.getSetting('hideDealTitles'), | ||
| hideDiscussionTitles: await presence.getSetting('hideDiscussionTitles'), | ||
| hideImages: await presence.getSetting('hideImages'), | ||
| hidePrices: await presence.getSetting('hidePrices'), | ||
|
Comment on lines
+21
to
+25
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe use a Promise.all? |
||
| } | ||
|
|
||
| const strings = await getStrings() | ||
| const t: any = strings | ||
|
||
| const presenceData: PresenceData = { | ||
| largeImageKey: activityAssets.logo, | ||
| startTimestamp: browsingTimestamp, | ||
| type: 3, // Watching | ||
maximedeprince marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| details: strings?.navigating, | ||
| state: strings?.hunting, | ||
| } | ||
|
|
||
| const pathname = document.location.pathname | ||
|
|
||
| if (settings.privacyMode) { | ||
| if (pathname.includes('/bons-plans/') || pathname.includes('/codes-promo/')) { | ||
| presenceData.details = strings?.viewDeal | ||
| presenceData.state = strings?.onDealabs | ||
| } | ||
| else if (pathname.includes('/discussions/') || pathname.includes('/groupe/')) { | ||
| presenceData.details = strings?.onForum | ||
| presenceData.state = strings?.readDiscuss | ||
| } | ||
| else { | ||
| presenceData.details = strings?.navigating | ||
| presenceData.state = strings?.hunting | ||
| } | ||
| presence.setActivity(presenceData) | ||
| return | ||
| } | ||
|
|
||
| const resolvers: Resolver[] = [ | ||
| merchantResolver, | ||
| dealResolver, | ||
| discussionResolver, | ||
| groupResolver, | ||
| listingResolver, | ||
| ] | ||
|
|
||
| const activeResolver = resolvers.filter(r => r.isActive(pathname))[0] | ||
|
|
||
| if (activeResolver) { | ||
| const state = activeResolver.getState(t, settings) | ||
| const details = activeResolver.getDetails(t, settings) | ||
|
|
||
| if (state) | ||
| presenceData.state = state | ||
| if (details) | ||
| presenceData.details = details | ||
|
|
||
| if (!settings.hideImages && activeResolver.getLargeImage) { | ||
| const img = activeResolver.getLargeImage() | ||
| if (img) | ||
| presenceData.largeImageKey = img | ||
| } | ||
|
|
||
| if (activeResolver.getButtons) { | ||
| presenceData.buttons = activeResolver.getButtons(t) | ||
| } | ||
|
|
||
| presenceData.smallImageKey = activityAssets.logo | ||
| presenceData.smallImageText = 'Dealabs' | ||
| } | ||
|
|
||
| if (!presenceData.state || (typeof presenceData.state === 'string' && presenceData.state.length < 2)) { | ||
| presenceData.state = strings?.hunting | ||
| } | ||
| if (!presenceData.details || (typeof presenceData.details === 'string' && presenceData.details.length < 2)) { | ||
| presenceData.details = strings?.onDealabs | ||
| } | ||
|
|
||
| presence.setActivity(presenceData) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,71 @@ | ||||||
| import type { ButtonArray, DealabsSettings, Resolver } from '../util/interfaces.js' | ||||||
| import { getMainContent } from '../util/index.js' | ||||||
|
|
||||||
| const dealResolver: Resolver = { | ||||||
| isActive: (pathname: string) => pathname.includes('/bons-plans/'), | ||||||
|
|
||||||
| getState: (t: any, settings: DealabsSettings) => { | ||||||
|
||||||
| if (settings.hideDealTitles) | ||||||
| return t.hiddenDeal | ||||||
|
|
||||||
| let title = document.querySelector('h1')?.textContent?.trim() | ||||||
| if (!title) { | ||||||
| const metaTitle = document.querySelector('meta[property="og:title"]')?.getAttribute('content') | ||||||
| title = metaTitle ? ((metaTitle.split(' - Dealabs')[0] ?? metaTitle).trim()) : t.detail | ||||||
| } | ||||||
| return title | ||||||
| }, | ||||||
|
|
||||||
| getDetails: (t: any, settings: DealabsSettings) => { | ||||||
maximedeprince marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| const mainContent = getMainContent() | ||||||
|
|
||||||
| let price: string | undefined | ||||||
| if (!settings.hidePrices) { | ||||||
| const priceEl = mainContent.querySelector('.thread-price') | ||||||
| if (priceEl && priceEl.textContent) { | ||||||
| price = priceEl.textContent.trim() | ||||||
| } | ||||||
| else { | ||||||
| const freeLabel = mainContent.querySelector('.text--color-free') | ||||||
| if (freeLabel || (mainContent.textContent && mainContent.textContent.includes('Gratuit') && !mainContent.textContent.includes('€'))) { | ||||||
|
||||||
| if (freeLabel || (mainContent.textContent && mainContent.textContent.includes('Gratuit') && !mainContent.textContent.includes('€'))) { | |
| if (freeLabel) { |
Fixed
Show fixed
Hide fixed
Outdated
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using 'any' type for the translation parameter defeats the purpose of TypeScript's type safety. The Translation interface is already defined in interfaces.ts and should be used instead.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import type { ButtonArray, DealabsSettings, Resolver } from '../util/interfaces.js' | ||
|
|
||
| const discussionResolver: Resolver = { | ||
| isActive: (pathname: string) => { | ||
| return pathname === '/discussions' || pathname.includes('/discussions/') | ||
| }, | ||
|
|
||
| getState: (t: any, settings: DealabsSettings) => { | ||
maximedeprince marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (document.location.pathname === '/discussions') | ||
| return t.browseDiscuss | ||
|
|
||
| if (settings.hideDiscussionTitles) | ||
| return t.hiddenTopic | ||
|
|
||
| const h1 = document.querySelector('h1')?.textContent?.trim() | ||
| if (h1) | ||
| return h1 | ||
|
|
||
| const metaTitle = document.querySelector('meta[property="og:title"]')?.getAttribute('content') | ||
| return metaTitle ? ((metaTitle.split(' - Dealabs')[0] ?? metaTitle).trim()) : t.detail | ||
| }, | ||
|
|
||
| getDetails: (t: any) => { | ||
|
||
| if (document.location.pathname === '/discussions') | ||
| return t.onForum | ||
| return t.readDiscuss | ||
| }, | ||
|
|
||
| getButtons: (t: any) => { | ||
|
||
| if (document.location.pathname === '/discussions') | ||
| return undefined | ||
| return [{ label: t.viewPage, url: document.location.href }] as ButtonArray | ||
| }, | ||
| } | ||
|
|
||
| export default discussionResolver | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import type { Resolver } from '../util/interfaces.js' | ||
| import { formatSlug } from '../util/index.js' | ||
|
|
||
| const groupResolver: Resolver = { | ||
| isActive: (pathname: string) => pathname.includes('/groupe'), | ||
|
|
||
| getState: (t: any) => { | ||
|
||
| const docTitle = document.title | ||
| const titleMatch = docTitle.match(/Bon plan (.+?) [⇒|:\-]/) | ||
maximedeprince marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (titleMatch && titleMatch[1]) | ||
| return titleMatch[1].trim() | ||
maximedeprince marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const h1 = document.querySelector('h1')?.textContent?.trim() | ||
| if (h1) | ||
| return h1 | ||
|
|
||
| const parts = document.location.pathname.split('/groupe/') | ||
| const slug = (parts[1] ?? '').split('/')[0] || 'Inconnue' | ||
|
||
| return formatSlug(slug) | ||
| }, | ||
|
|
||
| getDetails: (t: any) => t.exploreCat, | ||
| } | ||
|
|
||
| export default groupResolver | ||
Uh oh!
There was an error while loading. Please reload this page.