From f52f5e340cceefd081e1b848c17134cce59b6339 Mon Sep 17 00:00:00 2001 From: ankey05 Date: Tue, 9 Jun 2026 11:22:41 +0530 Subject: [PATCH 1/7] Add Thunderbird add-on badge service --- services/thunderbird/thunderbird-base.js | 28 ++++++++++ .../thunderbird-downloads.service.js | 53 +++++++++++++++++++ .../thunderbird/thunderbird-rating.service.js | 44 +++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 services/thunderbird/thunderbird-base.js create mode 100644 services/thunderbird/thunderbird-downloads.service.js create mode 100644 services/thunderbird/thunderbird-rating.service.js diff --git a/services/thunderbird/thunderbird-base.js b/services/thunderbird/thunderbird-base.js new file mode 100644 index 0000000000000..6e5343d70a41c --- /dev/null +++ b/services/thunderbird/thunderbird-base.js @@ -0,0 +1,28 @@ +import Joi from 'joi' +import { nonNegativeInteger } from '../validators.js' +import { BaseJsonService } from '../index.js' + +const description = +'[addons.thunderbird.net](https://addons.thunderbird.net)' +const schema = Joi.object({ + average_daily_users: nonNegativeInteger, + current_version: Joi.object({ + version: Joi.string().required(), + }).required(), + ratings: Joi.object({ + average: Joi.number().required(), + }).required(), + weekly_downloads: nonNegativeInteger, +}).required() + +class BaseThunderbirdService extends BaseJsonService { + static defaultBadgeData = { label: 'thunderbird add-on' } + async fetch({ addonId }) { + return this._requestJson({ + schema, + url: `https://addons.thunderbird.net/api/v4/addons/addon/${addonId}/`, + }) + } +} + +export { BaseThunderbirdService, description } diff --git a/services/thunderbird/thunderbird-downloads.service.js b/services/thunderbird/thunderbird-downloads.service.js new file mode 100644 index 0000000000000..bb37282d86909 --- /dev/null +++ b/services/thunderbird/thunderbird-downloads.service.js @@ -0,0 +1,53 @@ +import { renderDownloadsBadge } from '../downloads.js' +import { retiredService, pathParams } from '../index.js' +import { BaseThunderbirdService, description as baseDescription } from './thunderbird-base.js' + +const description = `${baseDescription} + +Previously \`thunderbird/d\` provided a “total downloads” badge. However, +[updates to the v3 API](https://github.com/badges/shields/issues/3079) +only give us weekly downloads. The route \`thunderbird/d\` redirects to \`thunderbird/dw\`. +` + +class ThunderbirdWeeklyDownloads extends BaseThunderbirdService { + static category = 'downloads' + static route = { base: 'thunderbird/dw', pattern: ':addonId' } + + static openApi = { + '/thunderbird/dw/{addonId}': { + get: { + summary: 'Thunderbird Add-on Downloads', + description, + parameters: pathParams({ name: 'addonId', example: 'dustman' }), + }, + }, + } + + static _cacheLength = 21600 + + static defaultBadgeData = { label: 'downloads' } + + static render({ downloads }) { + return renderDownloadsBadge({ downloads, interval: 'week' }) + } + + async handle({ addonId }) { + const data = await this.fetch({ addonId }) + return this.constructor.render({ + downloads: data.weekly_downloads, + }) + } +} + +const ThunderbirdLegacyRedirect = retiredService({ + category: 'downloads', + label: 'Thunderbird-add-on', + route: { + base: 'thunderbird/d', + pattern: ':addonId', + }, + dateAdded: new Date('2025-12-20'), + issueUrl: 'https://github.com/badges/shields/pull/11583', +}) + +export { ThunderbirdWeeklyDownloads, ThunderbirdLegacyRedirect } diff --git a/services/thunderbird/thunderbird-rating.service.js b/services/thunderbird/thunderbird-rating.service.js new file mode 100644 index 0000000000000..2f79f57e54214 --- /dev/null +++ b/services/thunderbird/thunderbird-rating.service.js @@ -0,0 +1,44 @@ +import { starRating } from '../text-formatters.js' +import { floorCount as floorCountColor } from '../color-formatters.js' +import { pathParams } from '../index.js' +import { BaseThunderbirdService, description } from './thunderbird-base.js' + +export default class ThunderbirdRating extends BaseThunderbirdService { + static category = 'rating' + static route = { base: 'thunderbird', pattern: ':format(stars|rating)/:addonId' } + + static openApi = { + '/thunderbird/rating/{addonId}': { + get: { + summary: 'Thunderbird Add-on Rating', + description, + parameters: pathParams({ name: 'addonId', example: 'dustman' }), + }, + }, + '/thunderbird/stars/{addonId}': { + get: { + summary: 'Thunderbird Add-on Stars', + description, + parameters: pathParams({ name: 'addonId', example: 'dustman' }), + }, + }, + } + + static _cacheLength = 7200 + + static render({ format, rating }) { + return { + label: format, + message: + format === 'stars' + ? starRating(rating) + : `${Math.round(rating * 10) / 10}/5`, + color: floorCountColor(rating, 2, 3, 4), + } + } + + async handle({ format, addonId }) { + const data = await this.fetch({ addonId }) + return this.constructor.render({ format, rating: data.ratings.average }) + } +} From da3ac24bbacb16d960d5f5b56be9d6f8e04d7d6a Mon Sep 17 00:00:00 2001 From: ankey05 Date: Wed, 10 Jun 2026 09:22:02 +0530 Subject: [PATCH 2/7] Add Thunderbird add-on badge services with test files - Implement base service for Thunderbird add-ons API - Add rating/stars badge service with tests - Add downloads badge service with tests - Include proper test coverage for all services --- .../thunderbird-downloads.service.tester.js | 22 +++++++++++++++++++ .../thunderbird/thunderbird-rating.service.js | 2 +- .../thunderbird-rating.service.tester.js | 19 ++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 services/thunderbird/thunderbird-downloads.service.tester.js create mode 100644 services/thunderbird/thunderbird-rating.service.tester.js diff --git a/services/thunderbird/thunderbird-downloads.service.tester.js b/services/thunderbird/thunderbird-downloads.service.tester.js new file mode 100644 index 0000000000000..38490da536e57 --- /dev/null +++ b/services/thunderbird/thunderbird-downloads.service.tester.js @@ -0,0 +1,22 @@ +import { ServiceTester } from '../tester.js' +import { isMetricOverTimePeriod } from '../test-validators.js' +export const t = new ServiceTester({ + id: 'ThunderbirdDownloads', + title: 'ThunderbirdDownloads', + pathPrefix: '/thunderbird', +}) + +t.create('Weekly Downloads') + .get('/dw/dustman.json') + .expectBadge({ label: 'downloads', message: isMetricOverTimePeriod }) + +t.create('Weekly Downloads (not found)') + .get('/dw/not-a-real-plugin.json') + .expectBadge({ label: 'downloads', message: 'not found' }) + +t.create('/d URL should return deprecated badge') + .get('/d/dustman.json') + .expectBadge({ + label: 'thunderbird-add-on', + message: 'https://github.com/badges/shields/pull/11583', + }) diff --git a/services/thunderbird/thunderbird-rating.service.js b/services/thunderbird/thunderbird-rating.service.js index 2f79f57e54214..d59e23e3a5fe8 100644 --- a/services/thunderbird/thunderbird-rating.service.js +++ b/services/thunderbird/thunderbird-rating.service.js @@ -8,7 +8,7 @@ export default class ThunderbirdRating extends BaseThunderbirdService { static route = { base: 'thunderbird', pattern: ':format(stars|rating)/:addonId' } static openApi = { - '/thunderbird/rating/{addonId}': { + '/thunderbird/rating/{addonId}': { get: { summary: 'Thunderbird Add-on Rating', description, diff --git a/services/thunderbird/thunderbird-rating.service.tester.js b/services/thunderbird/thunderbird-rating.service.tester.js new file mode 100644 index 0000000000000..5003d462102ff --- /dev/null +++ b/services/thunderbird/thunderbird-rating.service.tester.js @@ -0,0 +1,19 @@ +import Joi from 'joi' +import { isStarRating } from '../test-validators.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('Rating') + .get('/rating/dustman.json') + .expectBadge({ + label: 'rating', + message: Joi.string().regex(/^\d(\.\d)?\/\d$/), + }) + +t.create('Stars') + .get('/stars/dustman.json') + .expectBadge({ label: 'stars', message: isStarRating }) + +t.create('Rating (not found)') + .get('/rating/not-a-real-plugin.json') + .expectBadge({ label: 'thunderbird add-on', message: 'not found' }) From 2ffbab8740343a071ac5eafaee5c38ee98ae27ba Mon Sep 17 00:00:00 2001 From: ankey05 Date: Wed, 10 Jun 2026 10:14:27 +0530 Subject: [PATCH 3/7] Fix code formatting with Prettier --- services/thunderbird/thunderbird-base.js | 3 +-- services/thunderbird/thunderbird-downloads.service.js | 5 ++++- services/thunderbird/thunderbird-rating.service.js | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/services/thunderbird/thunderbird-base.js b/services/thunderbird/thunderbird-base.js index 6e5343d70a41c..441d6306daa27 100644 --- a/services/thunderbird/thunderbird-base.js +++ b/services/thunderbird/thunderbird-base.js @@ -2,8 +2,7 @@ import Joi from 'joi' import { nonNegativeInteger } from '../validators.js' import { BaseJsonService } from '../index.js' -const description = -'[addons.thunderbird.net](https://addons.thunderbird.net)' +const description = '[addons.thunderbird.net](https://addons.thunderbird.net)' const schema = Joi.object({ average_daily_users: nonNegativeInteger, current_version: Joi.object({ diff --git a/services/thunderbird/thunderbird-downloads.service.js b/services/thunderbird/thunderbird-downloads.service.js index bb37282d86909..258bbadc81ca5 100644 --- a/services/thunderbird/thunderbird-downloads.service.js +++ b/services/thunderbird/thunderbird-downloads.service.js @@ -1,6 +1,9 @@ import { renderDownloadsBadge } from '../downloads.js' import { retiredService, pathParams } from '../index.js' -import { BaseThunderbirdService, description as baseDescription } from './thunderbird-base.js' +import { + BaseThunderbirdService, + description as baseDescription, +} from './thunderbird-base.js' const description = `${baseDescription} diff --git a/services/thunderbird/thunderbird-rating.service.js b/services/thunderbird/thunderbird-rating.service.js index d59e23e3a5fe8..9e16bb1305b42 100644 --- a/services/thunderbird/thunderbird-rating.service.js +++ b/services/thunderbird/thunderbird-rating.service.js @@ -5,7 +5,10 @@ import { BaseThunderbirdService, description } from './thunderbird-base.js' export default class ThunderbirdRating extends BaseThunderbirdService { static category = 'rating' - static route = { base: 'thunderbird', pattern: ':format(stars|rating)/:addonId' } + static route = { + base: 'thunderbird', + pattern: ':format(stars|rating)/:addonId', + } static openApi = { '/thunderbird/rating/{addonId}': { From e649afe6c8e453f0380a1e1b41382e25cba1dbe4 Mon Sep 17 00:00:00 2001 From: ankey05 Date: Wed, 10 Jun 2026 10:35:07 +0530 Subject: [PATCH 4/7] Rename tester files with correct naming convention --- ...ownloads.service.tester.js => thunderbird-downloads.tester.js} | 0 ...bird-rating.service.tester.js => thunderbird-rating.tester.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename services/thunderbird/{thunderbird-downloads.service.tester.js => thunderbird-downloads.tester.js} (100%) rename services/thunderbird/{thunderbird-rating.service.tester.js => thunderbird-rating.tester.js} (100%) diff --git a/services/thunderbird/thunderbird-downloads.service.tester.js b/services/thunderbird/thunderbird-downloads.tester.js similarity index 100% rename from services/thunderbird/thunderbird-downloads.service.tester.js rename to services/thunderbird/thunderbird-downloads.tester.js diff --git a/services/thunderbird/thunderbird-rating.service.tester.js b/services/thunderbird/thunderbird-rating.tester.js similarity index 100% rename from services/thunderbird/thunderbird-rating.service.tester.js rename to services/thunderbird/thunderbird-rating.tester.js From 3a0db459bab7f067b9f51f368e28a63d21ef350c Mon Sep 17 00:00:00 2001 From: ankey05 Date: Sun, 14 Jun 2026 14:10:07 +0530 Subject: [PATCH 5/7] Remove legacy redirect code from Thunderbird services - Removed ThunderbirdLegacyRedirect constant from downloads service - Removed deprecated badge test from downloads tester - Kept only clean Thunderbird implementation - Addresses maintainer feedback on PR #11918 --- .../thunderbird/thunderbird-downloads.service.js | 13 +------------ .../thunderbird/thunderbird-downloads.tester.js | 6 ------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/services/thunderbird/thunderbird-downloads.service.js b/services/thunderbird/thunderbird-downloads.service.js index 258bbadc81ca5..ac5c92cbbc90e 100644 --- a/services/thunderbird/thunderbird-downloads.service.js +++ b/services/thunderbird/thunderbird-downloads.service.js @@ -42,15 +42,4 @@ class ThunderbirdWeeklyDownloads extends BaseThunderbirdService { } } -const ThunderbirdLegacyRedirect = retiredService({ - category: 'downloads', - label: 'Thunderbird-add-on', - route: { - base: 'thunderbird/d', - pattern: ':addonId', - }, - dateAdded: new Date('2025-12-20'), - issueUrl: 'https://github.com/badges/shields/pull/11583', -}) - -export { ThunderbirdWeeklyDownloads, ThunderbirdLegacyRedirect } +export { ThunderbirdWeeklyDownloads } \ No newline at end of file diff --git a/services/thunderbird/thunderbird-downloads.tester.js b/services/thunderbird/thunderbird-downloads.tester.js index 38490da536e57..34e37bd4fc9f2 100644 --- a/services/thunderbird/thunderbird-downloads.tester.js +++ b/services/thunderbird/thunderbird-downloads.tester.js @@ -14,9 +14,3 @@ t.create('Weekly Downloads (not found)') .get('/dw/not-a-real-plugin.json') .expectBadge({ label: 'downloads', message: 'not found' }) -t.create('/d URL should return deprecated badge') - .get('/d/dustman.json') - .expectBadge({ - label: 'thunderbird-add-on', - message: 'https://github.com/badges/shields/pull/11583', - }) From 029700cdcdeb9599fe00581008b38d2de6ca1e38 Mon Sep 17 00:00:00 2001 From: ankey05 Date: Sun, 14 Jun 2026 14:16:29 +0530 Subject: [PATCH 6/7] Format code with Prettier - Fixed formatting issues in Thunderbird services - Prettier auto-fix applied --- services/thunderbird/thunderbird-downloads.service.js | 2 +- services/thunderbird/thunderbird-downloads.tester.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/services/thunderbird/thunderbird-downloads.service.js b/services/thunderbird/thunderbird-downloads.service.js index ac5c92cbbc90e..87a029ac0cac6 100644 --- a/services/thunderbird/thunderbird-downloads.service.js +++ b/services/thunderbird/thunderbird-downloads.service.js @@ -42,4 +42,4 @@ class ThunderbirdWeeklyDownloads extends BaseThunderbirdService { } } -export { ThunderbirdWeeklyDownloads } \ No newline at end of file +export { ThunderbirdWeeklyDownloads } diff --git a/services/thunderbird/thunderbird-downloads.tester.js b/services/thunderbird/thunderbird-downloads.tester.js index 34e37bd4fc9f2..57df6f3a1a817 100644 --- a/services/thunderbird/thunderbird-downloads.tester.js +++ b/services/thunderbird/thunderbird-downloads.tester.js @@ -13,4 +13,3 @@ t.create('Weekly Downloads') t.create('Weekly Downloads (not found)') .get('/dw/not-a-real-plugin.json') .expectBadge({ label: 'downloads', message: 'not found' }) - From a79864193d4dcb4698933a21ce29c4e326292ba1 Mon Sep 17 00:00:00 2001 From: ankey05 Date: Sun, 14 Jun 2026 14:30:13 +0530 Subject: [PATCH 7/7] Add missing pathParams import - Added pathParams import from index.js - Fixes ESLint error for undefined variable --- services/thunderbird/thunderbird-downloads.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/thunderbird/thunderbird-downloads.service.js b/services/thunderbird/thunderbird-downloads.service.js index 87a029ac0cac6..da0eb26145dd7 100644 --- a/services/thunderbird/thunderbird-downloads.service.js +++ b/services/thunderbird/thunderbird-downloads.service.js @@ -1,5 +1,5 @@ import { renderDownloadsBadge } from '../downloads.js' -import { retiredService, pathParams } from '../index.js' +import { pathParams } from '../index.js' import { BaseThunderbirdService, description as baseDescription,