-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Thunderstore] Add Thunderstore Badges (#9782)
* add base for thunderstore services * badge service and corresponding tester for thunderstore download count * badge service and corresponding tester for thunderstore latest package version * fix HTML * use stable package-metrics endpoint * remove erroneous statement from docs * remove `namedLogo` from default badge data on both services * follow route naming conventions * use `[x].json` for test assertions * use existing version pattern * document service `handle` return-type more narrowly * use consistent test formatting * add base for thunderstore services * badge service and corresponding tester for thunderstore download count * badge service and corresponding tester for thunderstore latest package version * fix HTML * use stable package-metrics endpoint * remove erroneous statement from docs * remove `namedLogo` from default badge data on both services * follow route naming conventions * use `[x].json` for test assertions * use existing version pattern * document service `handle` return-type more narrowly * use consistent test formatting * plural-ise base thunderstore docs * don't require unused attributes * declare BaseThunderstoreService abstract, add docstring * add thunderstoreGreen static variable * add thunderstore likes service --------- Co-authored-by: chris48s <chris48s@users.noreply.github.com>
- Loading branch information
1 parent
2de7e8f
commit d0bdb82
Showing
7 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import Joi from 'joi' | ||
import { BaseJsonService } from '../index.js' | ||
import { nonNegativeInteger } from '../validators.js' | ||
|
||
const packageSchema = Joi.object({ | ||
latest: Joi.object({ | ||
version_number: Joi.string().required(), | ||
}).required(), | ||
}).required() | ||
|
||
const packageMetricsSchema = Joi.object({ | ||
downloads: nonNegativeInteger, | ||
rating_score: nonNegativeInteger, | ||
}) | ||
|
||
const documentation = ` | ||
<p> | ||
The Thunderstore badges require a package's <code>namespace</code> and <code>name</code>. | ||
</p> | ||
<p> | ||
Everything can be discerned from your package's URL. Thunderstore package URLs have a mostly consistent | ||
format: | ||
</p> | ||
<p> | ||
<code>https://thunderstore.io/c/[community]/p/[namespace]/[packageName]</code> | ||
</p> | ||
<p> | ||
For example: <code>https://thunderstore.io/c/lethal-company/p/notnotnotswipez/MoreCompany/</code>. | ||
<ul> | ||
<li><code>namespace = "notnotnotswipez"</code></li> | ||
<li><code>packageName = "MoreCompany"</code></li> | ||
</ul> | ||
</p> | ||
<details> | ||
<summary>Risk Of Rain 2</summary> | ||
<p> | ||
The 'default community', Risk of Rain 2, has an alternative URL: | ||
</p> | ||
<p> | ||
<code>https://thunderstore.io/package/[namespace]/[packageName]</code> | ||
</p> | ||
</details> | ||
<details> | ||
<summary>Subdomain Communities</summary> | ||
<p> | ||
Some communities use a 'subdomain' alternative URL, for example, Valheim: | ||
</p> | ||
<p> | ||
<code>https://valheim.thunderstore.io/package/[namespace]/[packageName]</code> | ||
</p> | ||
</details> | ||
` | ||
|
||
/** | ||
* Services which query Thunderstore endpoints should extend BaseThunderstoreService | ||
* | ||
* @abstract | ||
*/ | ||
class BaseThunderstoreService extends BaseJsonService { | ||
static thunderstoreGreen = '23FFB0' | ||
|
||
/** | ||
* Fetches package metadata from the Thunderstore API. | ||
* | ||
* @param {object} pkg - Package specifier | ||
* @param {string} pkg.namespace - the package namespace | ||
* @param {string} pkg.packageName - the package name | ||
* @returns {Promise<object>} - Promise containing validated package information | ||
*/ | ||
async fetchPackage({ namespace, packageName }) { | ||
return this._requestJson({ | ||
schema: packageSchema, | ||
url: `https://thunderstore.io/api/experimental/package/${namespace}/${packageName}`, | ||
}) | ||
} | ||
|
||
/** | ||
* Fetches package metrics from the Thunderstore API. | ||
* | ||
* @param {object} pkg - Package specifier | ||
* @param {string} pkg.namespace - the package namespace | ||
* @param {string} pkg.packageName - the package name | ||
* @returns {Promise<object>} - Promise containing validated package metrics | ||
*/ | ||
async fetchPackageMetrics({ namespace, packageName }) { | ||
return this._requestJson({ | ||
schema: packageMetricsSchema, | ||
url: `https://thunderstore.io/api/v1/package-metrics/${namespace}/${packageName}`, | ||
}) | ||
} | ||
} | ||
|
||
export { BaseThunderstoreService, documentation } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { renderDownloadsBadge } from '../downloads.js' | ||
import { BaseThunderstoreService, documentation } from './thunderstore-base.js' | ||
|
||
export default class ThunderstoreDownloads extends BaseThunderstoreService { | ||
static category = 'downloads' | ||
|
||
static route = { | ||
base: 'thunderstore/dt', | ||
pattern: ':namespace/:packageName', | ||
} | ||
|
||
static examples = [ | ||
{ | ||
title: 'Thunderstore Downloads', | ||
namedParams: { | ||
namespace: 'notnotnotswipez', | ||
packageName: 'MoreCompany', | ||
}, | ||
staticPreview: renderDownloadsBadge({ downloads: 120000 }), | ||
documentation, | ||
}, | ||
] | ||
|
||
static defaultBadgeData = { | ||
label: 'downloads', | ||
} | ||
|
||
/** | ||
* @param {object} pkg - Package specifier | ||
* @param {string} pkg.namespace - the package namespace | ||
* @param {string} pkg.packageName - the package name | ||
* @returns {Promise<object>} - Promise containing the rendered badge payload | ||
*/ | ||
async handle({ namespace, packageName }) { | ||
const { downloads } = await this.fetchPackageMetrics({ | ||
namespace, | ||
packageName, | ||
}) | ||
return renderDownloadsBadge({ downloads }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createServiceTester } from '../tester.js' | ||
import { isMetric } from '../test-validators.js' | ||
|
||
export const t = await createServiceTester() | ||
|
||
t.create('Downloads') | ||
.get('/ebkr/r2modman.json') | ||
.expectBadge({ label: 'downloads', message: isMetric }) | ||
|
||
t.create('Downloads (not found)') | ||
.get('/not-a-namespace/not-a-package-name.json') | ||
.expectBadge({ label: 'downloads', message: 'not found', color: 'red' }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { metric } from '../text-formatters.js' | ||
import { BaseThunderstoreService, documentation } from './thunderstore-base.js' | ||
|
||
export default class ThunderstoreLikes extends BaseThunderstoreService { | ||
static category = 'social' | ||
|
||
static route = { | ||
base: 'thunderstore/likes', | ||
pattern: ':namespace/:packageName', | ||
} | ||
|
||
static examples = [ | ||
{ | ||
title: 'Thunderstore Likes', | ||
namedParams: { | ||
namespace: 'notnotnotswipez', | ||
packageName: 'MoreCompany', | ||
}, | ||
staticPreview: { | ||
label: 'likes', | ||
message: '150', | ||
style: 'social', | ||
}, | ||
documentation, | ||
}, | ||
] | ||
|
||
static defaultBadgeData = { | ||
label: 'likes', | ||
namedLogo: 'thunderstore', | ||
} | ||
|
||
static render({ likes }) { | ||
return { | ||
message: metric(likes), | ||
color: `#${this.thunderstoreGreen}`, | ||
} | ||
} | ||
|
||
/** | ||
* @param {object} pkg - Package specifier | ||
* @param {string} pkg.namespace - the package namespace | ||
* @param {string} pkg.packageName - the package name | ||
* @returns {Promise<object>} - Promise containing the rendered badge payload | ||
*/ | ||
async handle({ namespace, packageName }) { | ||
const { rating_score: likes } = await this.fetchPackageMetrics({ | ||
namespace, | ||
packageName, | ||
}) | ||
return this.constructor.render({ likes }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createServiceTester } from '../tester.js' | ||
import { isMetric } from '../test-validators.js' | ||
|
||
export const t = await createServiceTester() | ||
|
||
t.create('Likes') | ||
.get('/ebkr/r2modman.json') | ||
.expectBadge({ label: 'likes', message: isMetric }) | ||
|
||
t.create('Likes (not found)') | ||
.get('/not-a-namespace/not-a-package-name.json') | ||
.expectBadge({ label: 'likes', message: 'not found', color: 'red' }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { renderVersionBadge } from '../version.js' | ||
import { BaseThunderstoreService, documentation } from './thunderstore-base.js' | ||
|
||
export default class ThunderstoreVersion extends BaseThunderstoreService { | ||
static category = 'version' | ||
|
||
static route = { | ||
base: 'thunderstore/v', | ||
pattern: ':namespace/:packageName', | ||
} | ||
|
||
static examples = [ | ||
{ | ||
title: 'Thunderstore Version', | ||
namedParams: { | ||
namespace: 'notnotnotswipez', | ||
packageName: 'MoreCompany', | ||
}, | ||
staticPreview: renderVersionBadge({ version: '1.4.5' }), | ||
documentation, | ||
}, | ||
] | ||
|
||
static defaultBadgeData = { | ||
label: 'thunderstore', | ||
} | ||
|
||
/** | ||
* @param {object} pkg - Package specifier | ||
* @param {string} pkg.namespace - the package namespace | ||
* @param {string} pkg.packageName - the package name | ||
* @returns {Promise<object>} - Promise containing the rendered badge payload | ||
*/ | ||
async handle({ namespace, packageName }) { | ||
const { | ||
latest: { version_number: version }, | ||
} = await this.fetchPackage({ namespace, packageName }) | ||
return renderVersionBadge({ version }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createServiceTester } from '../tester.js' | ||
import { isSemver } from '../test-validators.js' | ||
|
||
export const t = await createServiceTester() | ||
|
||
t.create('Version') | ||
.get('/ebkr/r2modman.json') | ||
.expectBadge({ label: 'thunderstore', message: isSemver }) | ||
|
||
t.create('Version (not found)') | ||
.get('/not-a-namespace/not-a-package-name.json') | ||
.expectBadge({ label: 'thunderstore', message: 'not found', color: 'red' }) |