Skip to content
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

[openuserjs] Add OpenUserJS service badges #8081

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ff1ef18
feat: openuserjs installs and version badges
DenverCoder1 Jun 13, 2022
b363010
feat: openuserjs issues and license
DenverCoder1 Jun 13, 2022
249d295
ci: fix tests
DenverCoder1 Jun 13, 2022
e671bc6
ci: revert changes to fix tests
DenverCoder1 Jun 13, 2022
fc0fc6b
ci: update tests
DenverCoder1 Jun 13, 2022
b3b72dd
fix: change duplicate service name
DenverCoder1 Jun 13, 2022
053103c
refactored class exports and tests
DenverCoder1 Jun 14, 2022
b1503d5
fix license to include multiple values
DenverCoder1 Jun 14, 2022
5d24cba
test with multiple licenses
DenverCoder1 Jun 14, 2022
731e412
removed required from UserScript schema
DenverCoder1 Jun 14, 2022
020fe21
fix: error if version missing
DenverCoder1 Jun 14, 2022
c07f320
error in case no license is found
DenverCoder1 Jun 14, 2022
5ec468b
fix: use last version specified
DenverCoder1 Jun 14, 2022
f360788
remove _cacheLength
DenverCoder1 Jun 14, 2022
7cb7aa2
refactor: remove 1-line render method
DenverCoder1 Jun 14, 2022
4c508f9
update error messages
DenverCoder1 Jun 14, 2022
e26ad9a
change fake script name to reduce 429s
DenverCoder1 Jun 14, 2022
8e43b44
change fake username to existing username
DenverCoder1 Jun 14, 2022
4a8a1f2
refactor: change parameter names to match documentation
DenverCoder1 Jun 14, 2022
2947508
update examples used in tests
DenverCoder1 Jun 14, 2022
4e3440f
refactor: Always get elements from end of list
DenverCoder1 Jun 14, 2022
64cb1e8
ci: update tests with oujs author account
DenverCoder1 Jun 15, 2022
92c79e5
Use MIT by default if no license appears
DenverCoder1 Jun 15, 2022
31d61c2
Merge branch 'master' into openuserjs
DenverCoder1 Jun 16, 2022
584f4fc
Merge branch 'master' into openuserjs
DenverCoder1 Jun 16, 2022
a341d5a
change message to installs for consistency with greasy fork
DenverCoder1 Jun 16, 2022
6313d49
Merge branch 'master' into openuserjs
DenverCoder1 Jun 16, 2022
c8055a5
Merge branch 'master' into openuserjs
DenverCoder1 Jun 19, 2022
9336020
Merge branch 'master' into openuserjs
DenverCoder1 Jun 23, 2022
8f045ba
Merge branch 'badges:master' into openuserjs
DenverCoder1 Jul 6, 2022
c55845a
Arrays must be non-empty if appearing in response
DenverCoder1 Jul 6, 2022
422ed96
Add documentation for MIT license default
DenverCoder1 Jul 6, 2022
00d402c
Add transform license tests
DenverCoder1 Jul 6, 2022
91e1ea0
fix transform tests
DenverCoder1 Jul 6, 2022
f9c6ba6
Fix calling transform
DenverCoder1 Jul 6, 2022
bcb042c
Update issues color to informational
DenverCoder1 Jul 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions services/openuserjs/openuserjs-base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js'

const schema = Joi.object({
UserScript: Joi.object({
version: Joi.array().items(
Joi.object({
value: Joi.string().required(),
})
),
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
license: Joi.array().items(
Joi.object({
value: Joi.string().required(),
})
),
}).required(),
OpenUserJS: Joi.object({
installs: Joi.array()
.items(
Joi.object({
value: nonNegativeInteger,
})
)
.required(),
issues: Joi.array()
.items(
Joi.object({
value: nonNegativeInteger,
})
)
.required(),
}).required(),
}).required()

export default class BaseOpenUserJSService extends BaseJsonService {
static defaultBadgeData = { label: 'openuserjs' }

async fetch({ username, scriptname }) {
return this._requestJson({
schema,
url: `https://openuserjs.org/meta/${username}/${scriptname}.meta.json`,
errorMessages: {
404: 'user or project not found',
429: 'rate limited by remote server',
},
})
}
}
27 changes: 27 additions & 0 deletions services/openuserjs/openuserjs-downloads.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { renderDownloadsBadge } from '../downloads.js'
import BaseOpenUserJSService from './openuserjs-base.js'

export default class OpenUserJSDownloads extends BaseOpenUserJSService {
static category = 'downloads'
static route = { base: 'openuserjs', pattern: 'dt/:username/:scriptname' }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which noun do you think will feel more natural to OpenUserJS users, installs or downloads? I gather based on the label value and what was done/discussed for GreasyFork that it's installs, but that has me rethinking the route a bit because for other badges that opt for installs over downloads that's typically reflected in the route, e.g. Jenkins Plugin Installs - https://img.shields.io/jenkins/plugin/i/view-job-filters

Our docs on badge url structure do mention using the d* pattern for downloads or installs, but given the presence of i elsewhere I'll double check with the other maintainers to confirm

And somewhat of an aside, but if we do decide to shift to i* for installs then we can consider updating the corresponding GreasyFork route too (we've got mechanisms to update routes while maintaining backwards compatibility, will share if and when we need to cross that bridge)

Copy link
Contributor Author

@DenverCoder1 DenverCoder1 Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installs is the terminology used by OpenUserJS. This includes usage on the website, the meta JSON, and confirmed by the OUJS admin Martii in comments elsewhere.

I suppose I'll leave it as d* for now until that's decided?

Copy link
Member

@calebcartwright calebcartwright Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, leave the route as-is for now and I'll follow up with a resolution one way or another. I believe using the download nomenclature in the route is intentional but it's been a while since we last discussed and my memory is a bit fuzzy on where we landed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll likely continue discussing the specifics of this offline, but for now I think we've reached a sufficient consensus that it's fine to go with this route as is. We can always add redirectors (our mechanism for updating routes without impacting existing users) for these badges down the road if we end up deciding to pivot on the route conventions.

I'll try to make time over the weekend to do a final pass over the subsequent updates

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I see after reading through the previously linked issues that the maintainers of the upstream service still have some concerns, so we'll put this on hold for now until we figure out whether there's a viable path forward or whether we should just abandon this.


static examples = [
{
title: 'OpenUserJS',
namedParams: {
username: 'NatoBoram',
scriptname: 'YouTube_Comment_Blacklist',
},
staticPreview: renderDownloadsBadge({ downloads: 47 }),
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
},
]

static defaultBadgeData = { label: 'downloads' }

async handle({ username, scriptname }) {
const data = await this.fetch({ username, scriptname })
return renderDownloadsBadge({
downloads: data.OpenUserJS.installs[0].value,
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
11 changes: 11 additions & 0 deletions services/openuserjs/openuserjs-downloads.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()

t.create('Downloads')
.get('/dt/NatoBoram/YouTube_Comment_Blacklist.json')
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
.expectBadge({ label: 'downloads', message: isMetric })

t.create('Downloads (not found)')
.get('/dt/DenverCoder1/NotAScript1.json')
.expectBadge({ label: 'downloads', message: 'user or project not found' })
34 changes: 34 additions & 0 deletions services/openuserjs/openuserjs-issues.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { metric } from '../text-formatters.js'
import BaseOpenUserJSService from './openuserjs-base.js'

export default class OpenUserJSIssues extends BaseOpenUserJSService {
static category = 'issue-tracking'
static route = { base: 'openuserjs', pattern: 'issues/:username/:scriptname' }

static examples = [
{
title: 'OpenUserJS',
namedParams: {
username: 'NatoBoram',
scriptname: 'YouTube_Comment_Blacklist',
},
staticPreview: this.render({ issues: 0 }),
},
]

static defaultBadgeData = { label: 'issues' }

static render({ issues }) {
return {
message: metric(issues),
color: issues ? 'yellow' : 'brightgreen',
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
}
}

async handle({ username, scriptname }) {
const data = await this.fetch({ username, scriptname })
return this.constructor.render({
issues: data.OpenUserJS.issues[0].value,
})
}
}
11 changes: 11 additions & 0 deletions services/openuserjs/openuserjs-issues.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()

t.create('Issues')
.get('/issues/MAX30/TopAndDownButtonsEverywhere.json')
.expectBadge({ label: 'issues', message: isMetric })
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved

t.create('Issues (not found)')
.get('/issues/DenverCoder1/NotAScript2.json')
.expectBadge({ label: 'issues', message: 'user or project not found' })
39 changes: 39 additions & 0 deletions services/openuserjs/openuserjs-license.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { InvalidResponse } from '../index.js'
import { renderLicenseBadge } from '../licenses.js'
import BaseOpenUserJSService from './openuserjs-base.js'

export default class OpenUserJSLicense extends BaseOpenUserJSService {
static category = 'license'
static route = { base: 'openuserjs', pattern: 'l/:username/:scriptname' }

static examples = [
{
title: 'OpenUserJS',
namedParams: {
username: 'NatoBoram',
scriptname: 'YouTube_Comment_Blacklist',
},
staticPreview: renderLicenseBadge({ licenses: ['GPL-3.0-or-later'] }),
},
]

static defaultBadgeData = { label: 'license' }

transform(data) {
if (!('license' in data.UserScript)) {
throw new InvalidResponse({
prettyMessage: 'license not found',
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
})
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
}
const licenses = data.UserScript.license.map(
license => license.value.split('; ')[0]
)
return { licenses: licenses.reverse() }
}

async handle({ username, scriptname }) {
const data = await this.fetch({ username, scriptname })
const { licenses } = this.transform(data)
return renderLicenseBadge({ licenses })
}
}
20 changes: 20 additions & 0 deletions services/openuserjs/openuserjs-license.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()

t.create('License (valid)')
.get('/l/DenverCoder1/Unedit_and_Undelete_for_Reddit.json')
.expectBadge({
label: 'license',
message: 'MIT',
})

t.create('Licenses (multiple)')
.get('/l/Marti/oujs_-_Meta_View.json')
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
.expectBadge({
label: 'license',
message: 'GPL-3.0-or-later, CC-BY-NC-SA-4.0',
})

t.create('License (not found)')
.get('/l/DenverCoder1/NotAScript3.json')
.expectBadge({ label: 'license', message: 'user or project not found' })
31 changes: 31 additions & 0 deletions services/openuserjs/openuserjs-version.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { InvalidResponse } from '../index.js'
import { renderVersionBadge } from '../version.js'
import BaseOpenUserJSService from './openuserjs-base.js'

export default class OpenUserJSVersion extends BaseOpenUserJSService {
static category = 'version'
static route = { base: 'openuserjs', pattern: 'v/:username/:scriptname' }

static examples = [
{
title: 'OpenUserJS',
namedParams: {
username: 'NatoBoram',
scriptname: 'YouTube_Comment_Blacklist',
},
staticPreview: renderVersionBadge({ version: '0.0.7' }),
},
]

async handle({ username, scriptname }) {
const data = await this.fetch({ username, scriptname })
if (!('version' in data.UserScript)) {
throw new InvalidResponse({
prettyMessage: 'version not found',
})
}
return renderVersionBadge({
version: data.UserScript.version.at(-1).value,
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
DenverCoder1 marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
18 changes: 18 additions & 0 deletions services/openuserjs/openuserjs-version.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()

t.create('Version')
.get('/v/DenverCoder1/Unedit_and_Undelete_for_Reddit.json')
.expectBadge({
label: 'openuserjs',
message: isVPlusDottedVersionAtLeastOne,
})

t.create('Version (invalid parameters)')
.get('/v/DenverCoder1/NotAScript4.json')
.expectBadge({ label: 'openuserjs', message: 'user or project not found' })

t.create('Version (no version found)')
.get('/v/DenverCoder1/Example_no_version.json')
.expectBadge({ label: 'openuserjs', message: 'version not found' })