Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchemmc authored Sep 25, 2023
2 parents 01071ee + 95041ca commit febe6df
Show file tree
Hide file tree
Showing 12 changed files with 889 additions and 24 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/submit-verification-request.yml
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'
})
4 changes: 1 addition & 3 deletions .github/workflows/update-extension-store.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
name: Update Extention Store
on:
on:
push:
paths:
- "extensions.json"
branches:
- main
workflow_dispatch:
branches:
- main

jobs:
regenerate:
Expand Down
10 changes: 10 additions & 0 deletions extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,15 @@
"sit": "https://sit.manuelpoell.at/assets/store.md",
"marked": "https://www.battle-system.com/owlbear/marked-docs/store.md",
"hp-tracker": "https://raw.githubusercontent.com/kamejosh/owlbear-hp-tracker/master/docs/store.md",
"last-fable": "https://fabula-ultima-extension.onrender.com/store.md",
"ultimate-story": "https://fabula-ultima-character-extension.onrender.com/store.md",
"dummysheet": "https://www.dummysheet.com/store.md",
"condition-markers": "https://raw.githubusercontent.com/kgbergman/conditionmarkers/main/docs/store.md",
"flip": "https://www.battle-system.com/owlbear/flip-docs/store.md",
"token-anonymizer": "https://owlbear-token-anonymizer.davidsev.co.uk/store.md",
"bubble-tracker": "https://raw.githubusercontent.com/SeamusFinlayson/Bubbles-for-Owlbear-Rodeo/master/docs/store.md",
"movement-tracker": "https://movement-tracker.abarbre.com/store.md",
"bendy-ruler": "https://owlbear-bendy-ruler.davidsev.co.uk/store.md",
"djinni-music-player": "https://raw.githubusercontent.com/kgbergman/djinni-music-player/main/docs/store.md",
"path-measure": "https://raw.githubusercontent.com/Kaisky1911/obr-extension-path-measure/main/store.md"
}
79 changes: 79 additions & 0 deletions scripts/__tests__/validation.test.js
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
);
});
45 changes: 45 additions & 0 deletions scripts/common/extensionDetails.js
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;
}
}
19 changes: 0 additions & 19 deletions scripts/discord-webhook/extensionDetails.js

This file was deleted.

2 changes: 1 addition & 1 deletion scripts/discord-webhook/webhook.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from "node-fetch";

import { getExtensionDetails } from "./extensionDetails.js";
import { getExtensionDetails } from "../common/extensionDetails.js";

function createDiscordEmbed(id, title, description, url, image, author, tags) {
const embed = {
Expand Down
8 changes: 7 additions & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
"type": "module",
"license": "MIT",
"scripts": {
"test": "vitest",
"trigger": "node ./discord-webhook/index.js",
"deploy": "node ./extension-store-deploy/index.js"
"deploy": "node ./extension-store-deploy/index.js",
"verification": "node --no-warnings ./verify/index.js"
},
"dependencies": {
"fetch": "^1.1.0",
"gray-matter": "^4.0.3",
"joi": "^17.10.2",
"node-fetch": "^3.3.1"
},
"devDependencies": {
"vitest": "^0.34.4"
}
}
107 changes: 107 additions & 0 deletions scripts/validation/manifest.js
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("");
42 changes: 42 additions & 0 deletions scripts/verify/index.js
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);
Loading

0 comments on commit febe6df

Please sign in to comment.