-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
889 additions
and
24 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,42 @@ | ||
name: Submit Verification Request | ||
on: | ||
issue_comment: | ||
types: [created, edited] | ||
jobs: | ||
submit: | ||
if: ${{ github.event.issue.pull_request }} | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Install Node | ||
uses: actions/setup-node@v3 | ||
if: contains(github.event.comment.body, '/verify') | ||
with: | ||
node-version: 16 | ||
- uses: actions/github-script@v6 | ||
id: process-comment | ||
if: contains(github.event.comment.body, '/verify') | ||
with: | ||
script: | | ||
const body = context.payload.comment.body | ||
return body | ||
- uses: actions/checkout@v3 | ||
if: contains(github.event.comment.body, '/verify') | ||
- name: Install Packages | ||
if: contains(github.event.comment.body, '/verify') | ||
run: yarn --cwd ./scripts install --frozen-lockfile | ||
- name: Submit Request to Discord | ||
if: contains(github.event.comment.body, '/verify') | ||
run: node --no-warnings ./scripts/verify/index.js | ||
env: | ||
DISCORD_VERIFY_WEBHOOK: ${{ secrets.DISCORD_VERIFY_WEBHOOK }} | ||
EXTENSION_KEY: ${{ steps.process-comment.outputs.result }} | ||
- uses: actions/github-script@v6 | ||
if: contains(github.event.comment.body, '/verify') | ||
with: | ||
script: | | ||
github.rest.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: 'Verification has been submitted' | ||
}) |
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
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
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,79 @@ | ||
import { expect, test } from "vitest"; | ||
import { validateManifest } from "../validation/manifest"; | ||
|
||
test("successfully validates valid manifest", () => { | ||
const validManifest = { | ||
title: "Example Extension Manifest", | ||
description: "An example manifest for testing", | ||
author: "Extension Tests", | ||
image: "https://example.com/", | ||
icon: "https://example.com/", | ||
tags: ["combat", "tool", "automation"], | ||
manifest: "https://example.com/manifest.json", | ||
"learn-more": "[email protected]", | ||
}; | ||
|
||
const expectedResult = []; | ||
|
||
expect(validateManifest(validManifest)).toStrictEqual(expectedResult); | ||
}); | ||
|
||
test("throws error on invalid tag", () => { | ||
const invalidTagInManifest = { | ||
title: "Example Extension Manifest", | ||
description: "An example manifest for testing", | ||
author: "Extension Tests", | ||
image: "https://example.com/", | ||
icon: "https://example.com/", | ||
tags: ["combat", "tool", "automation", "built-by-owlbear"], | ||
manifest: "https://example.com/manifest.json", | ||
"learn-more": "[email protected]", | ||
}; | ||
|
||
const expectedResult = ['"tags[3]" contains an excluded value']; | ||
|
||
expect(validateManifest(invalidTagInManifest)).toStrictEqual(expectedResult); | ||
}); | ||
|
||
test("should return all validation errors in one message", () => { | ||
const mulitpleValidationIssues = { | ||
description: "An example manifest for testing", | ||
author: "Extension Tests", | ||
icon: "example.com/", | ||
tags: [], | ||
manifest: "https://example.com/manifest.json", | ||
}; | ||
|
||
const expectedResult = [ | ||
'"title" is required', | ||
'"image" is required', | ||
'"icon" failed custom validation because Invalid URL', | ||
'"tags" must contain at least 1 items', | ||
'"learn-more" is required', | ||
]; | ||
|
||
expect(validateManifest(mulitpleValidationIssues)).toStrictEqual( | ||
expectedResult | ||
); | ||
}); | ||
|
||
test("should return issue when learn-more is invalid", () => { | ||
const learnMoreInvalidationIssue = { | ||
title: "Example Extension Manifest", | ||
description: "An example manifest for testing", | ||
author: "Extension Tests", | ||
image: "https://example.com/", | ||
icon: "https://example.com/", | ||
tags: ["combat", "tool", "automation"], | ||
manifest: "https://example.com/manifest.json", | ||
"learn-more": "learn-more.com", | ||
}; | ||
|
||
const expectedResult = [ | ||
'"learn-more" does not match any of the allowed types', | ||
]; | ||
|
||
expect(validateManifest(learnMoreInvalidationIssue)).toStrictEqual( | ||
expectedResult | ||
); | ||
}); |
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,45 @@ | ||
import fetch from "node-fetch"; | ||
import data from "../../extensions.json" assert { type: "json" }; | ||
import matter from "gray-matter"; | ||
|
||
export async function getExtensionDetails() { | ||
const keys = Object.keys(data); | ||
|
||
const id = keys[keys.length - 1]; | ||
const item = data[keys[keys.length - 1]]; | ||
|
||
const result = await fetch(item); | ||
|
||
if (result.ok) { | ||
const markdown = await result.text(); | ||
const data = matter(markdown)["data"]; | ||
|
||
return { id, data }; | ||
} | ||
} | ||
|
||
export async function getExtensionDetailsFromKey(key) { | ||
const item = data[key]; | ||
|
||
const result = await fetch(item); | ||
|
||
if (result.ok) { | ||
const markdown = await result.text(); | ||
const data = matter(markdown)["data"]; | ||
|
||
return { key, data }; | ||
} | ||
} | ||
|
||
export async function getExtensionDetailsFromUrl(url) { | ||
const result = await fetch(url); | ||
|
||
if (result.ok) { | ||
const markdown = await result.text(); | ||
const data = matter(markdown)["data"]; | ||
|
||
console.log("data", data); | ||
|
||
return data; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
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
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
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,107 @@ | ||
import Joi from "joi"; | ||
|
||
import { getExtensionDetailsFromUrl } from "../common/extensionDetails.js"; | ||
|
||
const checkUrl = (value, helpers) => { | ||
new URL(value); | ||
|
||
return value; | ||
}; | ||
|
||
const schema = Joi.object({ | ||
title: Joi.string().required(), | ||
description: Joi.string().required(), | ||
author: Joi.string().required(), | ||
image: Joi.string().custom(checkUrl, "check url is valid format").required(), | ||
icon: Joi.string().custom(checkUrl, "check url is valid format").required(), | ||
tags: Joi.array() | ||
.min(1) | ||
.items( | ||
Joi.string().valid("built-by-owlbear").forbidden(), | ||
Joi.string().valid( | ||
"dice", | ||
"fog", | ||
"tool", | ||
"content-pack", | ||
"drawing", | ||
"audio", | ||
"combat", | ||
"automation", | ||
"other" | ||
) | ||
), | ||
manifest: Joi.string() | ||
.custom(checkUrl, "check url is valid format") | ||
.required(), | ||
"learn-more": Joi.alternatives() | ||
.try( | ||
Joi.string().email(), | ||
Joi.string().custom(checkUrl, "check url is valid format") | ||
) | ||
.required(), | ||
}); | ||
|
||
async function checkExtensionManifest(url) { | ||
const data = await getExtensionDetailsFromUrl(url); | ||
|
||
if (!data) { | ||
return false; | ||
} | ||
|
||
const issues = validateManifest(data); | ||
|
||
return issues; | ||
} | ||
|
||
export function validateManifest(data) { | ||
const validationIssues = []; | ||
const { value, error } = schema.validate(data, { abortEarly: false }); | ||
|
||
if (error) { | ||
error.details.forEach((element) => { | ||
validationIssues.push(element.message); | ||
}); | ||
} | ||
|
||
formatConsoleOutput(validationIssues); | ||
|
||
return validationIssues; | ||
} | ||
|
||
/* | ||
* | ||
*/ | ||
function formatConsoleOutput(issues) { | ||
let output = "## Manifest Validation Result\n\n"; | ||
|
||
if (Array.isArray(issues)) { | ||
if (issues.length === 0) { | ||
output += "No issues found in manifest type validation."; | ||
console.log(output); | ||
return; | ||
} | ||
|
||
output += "***Issues found in manifest type validation.***\n\n"; | ||
|
||
for (let issue of issues) { | ||
output += `- ${issue}\n`; | ||
} | ||
|
||
output += `\n\n***You must follow the below guidelines on manifest files***\n\n | ||
| NAME | DESCRIPTION | | ||
| :---------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| title | This is the name of your extension | | ||
| description | This should be a brief description of what your extension is for | | ||
| author | This is your name or alias | | ||
| image | This is an absolute link to a hero image for your extension. The image must be hosted on an external site. | | ||
| icon | This is an absolute link to your extensions icon and must be hosted on an external site | | ||
| tags | To help make your extension more discoverable you can add tags to it. In the extension store, users will be able to find your extension under that tag. You may only use our [supported tags](https://github.com/owlbear-rodeo/extensions/blob/main/tags.json) and only extensions published by Owlbear Rodeo can use the built-by-owlbear tag. | | ||
| manifest | This should have an link to your manifest file. This is what will be copied by other users to install your extension. | | ||
| learn-more | You should link to a site or email that users can go to to find more information about your extension. |`; | ||
|
||
console.log(output); | ||
return; | ||
} | ||
} | ||
|
||
await checkExtensionManifest(""); |
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,42 @@ | ||
import fetch from "node-fetch"; | ||
import { getExtensionDetailsFromKey } from "../common/extensionDetails.js"; | ||
|
||
function createDiscordVerificationPost(id, data) { | ||
const text = `## Extension Verfication Request\n\n${data.title} would like to be verified.\n\nIt can be installed via this [store page](https://extensions.owlbear.rodeo/${id}).\n\nTo be verified the extension must meet these criteria:\n\n- Extension design uses accessible colors :art:\n- Extension design uses accessible font sizes :blue_book:\n- Extension is legible with Owlbear Rodeo’s light and dark theme :ghost:\n- Extension is fully functional on mobile devices (1) :mobile_phone:\n- Extension is fully functional across all major browsers (2) :computer:\n- Extension requires no other extensions to be installed :link: \n- Extension functions in a private browsing window or with cookies disabled :lock:\n- Extension makes proper use of the Owlbear Rodeo APIs (3) :jigsaw: \n- Extension functions in all configurations of an Owlbear Rodeo Room (4) :house:\n- Extension provides user support for queries, issues and requests :question:\n- Extension has no known bugs :bug: \n- Extension manifest is hosted on a custom domain controlled by the extension developer :pencil:\n\nNotes:\n\n1) This includes iPhone’s, Android devices as well as tablets such as an iPad.\n\n2) Major browsers include Chrome, Firefox and Safari\n\n3) For example the Scene API is only used to store data that shares the Scene lifecycle\n\n4) Valid configurations include a Room with a Scene open and no Scene open\n\nAlso note that the term “fully functional” above means all extension functionally works correctly whereas the term “functional” means the extension still functions but some features may not be available.\n\n### To help verify this extension react to this message with the criteria that are met! (i.e. react :bug: if it has no known bugs)`; | ||
|
||
const post = { | ||
thread_name: `${data.title} wants to be verified`, | ||
content: text, | ||
}; | ||
|
||
return post; | ||
} | ||
|
||
export async function sendDiscordWebhook(value) { | ||
const values = value.replaceAll('"', "").split(" "); | ||
if (values.length !== 2) { | ||
console.log(values); | ||
throw Error("invalid submission"); | ||
} | ||
|
||
const key = values[1]; | ||
const { key: id, data } = await getExtensionDetailsFromKey(key); | ||
|
||
const embed = createDiscordVerificationPost(id, data); | ||
|
||
const response = await fetch(process.env.DISCORD_VERIFY_WEBHOOK, { | ||
method: "POST", | ||
body: JSON.stringify(embed), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
|
||
if (response.ok) { | ||
console.log("Success!!"); | ||
} else { | ||
console.log(JSON.stringify(await response.text(), null, 2)); | ||
} | ||
} | ||
|
||
await sendDiscordWebhook(process.env.EXTENSION_KEY); |
Oops, something went wrong.