Skip to content

refactor: improve validation script structure and readability 🦖 #495

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 38 additions & 32 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
In order to make the review process faster, please take a few minutes to go through this checklist:

## Acceptance Criteria

- All of the following criteria must be met:
- [ ] This community is directly tied to an open source project
- [ ] The open source project has an OSI-approved open source license: https://opensource.org/licenses
- [ ] This Discord server is an official channel for the open source project approved by the maintainers of the open source project
- [ ] This Discord server adheres to the [Discord Community Guidelines](https://discord.com/guidelines)
- One of the following criteria must be met:
- [ ] The Discord server has over 1,000 members
- [ ] The open source project has a repository with at least 1,000 GitHub Stars or some equivalent

## Code Review

- All of the following criteria must be met:
- [ ] The `"title"` field has a commonly identifiable name of the open source project
- [ ] The `"inviteCode"` field contains a valid invite code (without the full URL), without an expiration date
- [ ] The `"githubUrl"` field is a fully specified and valid URL starting with `https://` and refers to a place where we can easily find your primary open source repositories. GitHub repo or organization URLs (or some equivalent) are acceptable (but not a cloning URL).
- [ ] The `"logo"` field refers to the correct logo file (double-check the extension)
- [ ] The `"quote"` field (if included, it is optional) is about your usage of Discord (and not about the open source project itself)
- [ ] The `"quoteSourceUrl"` field (if included, it is optional) is a fully specified and valid URL starting with `https://`

## Logo Review

- All of the following criteria must be met:
- [ ] The logo makes use of transparent elements and does not include its own background
- [ ] The logo will look correct when the non-transparent elements are made solid white
- [ ] The logo is a commonly identifiable logo of the open source project
- One of the following criteria must be met:
- [ ] The logo is an SVG file with 72x72 dimensions and has been optimized with [`svgo`](https://www.npmjs.com/package/svgo) ([Web UI](https://jakearchibald.github.io/svgomg/))
- [ ] The logo is a PNG file with 144x144 dimensions and has been optimized with [ImageOptim](https://imageoptim.com/) ([Web UI](https://imageoptim.com/online))
# Review Checklist

## 1. Acceptance Criteria

### **All of the following criteria must be met:**
- [ ] This community is directly tied to an open source project.
- [ ] The open source project has an OSI-approved open source license ([List of OSI-approved licenses](https://opensource.org/licenses)).
- [ ] This Discord server is an official channel for the open source project and is approved by the maintainers of the project.
- [ ] This Discord server adheres to the [Discord Community Guidelines](https://discord.com/guidelines).

### **One of the following criteria must be met:**
- [ ] The Discord server has over **1,000 members**.
- [ ] The open source project has a repository with at least **1,000 GitHub Stars** or an equivalent metric.

---

## 2. Code Review

### **All of the following criteria must be met:**
- [ ] The `"title"` field contains a commonly identifiable name of the open source project.
- [ ] The `"inviteCode"` field contains a **valid invite code** (not a full URL) without an expiration date.
- [ ] The `"githubUrl"` field is a fully specified and valid URL starting with `https://` and refers to the primary open source repositories (GitHub repo or organization URLs are acceptable, but cloning URLs are not).
- [ ] The `"logo"` field refers to the correct logo file (double-check the file extension).
- [ ] The `"quote"` field (optional) is about your usage of Discord, not about the open source project itself.
- [ ] The `"quoteSourceUrl"` field (optional) is a fully specified and valid URL starting with `https://`.

---

## 3. Logo Review

### **All of the following criteria must be met:**
- [ ] The logo includes transparent elements and does not include its own background.
- [ ] The logo looks correct when the non-transparent elements are made solid white.
- [ ] The logo is a commonly identifiable logo of the open source project.

### **One of the following criteria must be met:**
- [ ] The logo is an **SVG file** with **72x72 dimensions** and has been optimized with [`svgo`](https://www.npmjs.com/package/svgo) ([Optimize via Web UI](https://jakearchibald.github.io/svgomg/)).
- [ ] The logo is a **PNG file** with **144x144 dimensions** and has been optimized with [ImageOptim](https://imageoptim.com/) ([Optimize via Web UI](https://imageoptim.com/online)).
63 changes: 44 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,65 @@
# Open Source Communities on Discord

This is the source list of OSS communities that use and live on Discord. It is used to populate the list at https://discord.com/open-source.
This repository contains the source list of open source software (OSS) communities that utilize Discord. It is used to populate the list displayed on [Discord's Open Source page](https://discord.com/open-source).

Thanks for being part of Discord!
Thank you for being part of Discord!

## Acceptance criteria
---

At this time, we are accepting communities which meet the following criteria:
## 🎯 Acceptance Criteria

1. Your community is not Discord-focused (for example, Discord bots or modifications are not accepted).
2. Your community has at least 1,000 members, or the GitHub repo has at least 1,000 stars.
3. Your community adheres to the [Discord community guidelines](https://discord.com/guidelines).
To be added to the list, communities must meet the following requirements:

Do you own a large bot? Take a look at [verifying it](https://support.discord.com/hc/en-us/articles/360040720412).
1. **Open Source Focus:** Your community should be tied directly to an open source project and not Discord-focused (e.g., Discord bots or modifications are not accepted).
2. **Community Size:** Your community must have at least **1,000 members**, or the associated GitHub repository must have **1,000 stars** or more.
3. **Guidelines Compliance:** Your community must adhere to the [Discord Community Guidelines](https://discord.com/guidelines).

## Adding your project
> **Note:** If you own a large bot, consider [verifying it](https://support.discord.com/hc/en-us/articles/360040720412) as an alternative.

1. Fork the repo
2. Add your logo into [`/logos`](https://github.com/discord/discord-open-source/tree/master/logos)
---

* Logo dimensions should be either `72x72` for SVG or `144x144` for PNG.
* Logo should be minified.
* Logo should be monochromatic and white (check [the website](https://discord.com/open-source) for examples)
* SVGs should contain only vector shapes — no raster graphics.
## 🛠 Adding Your Project

3. Add your community to [`communities.json`](https://github.com/discord/discord-open-source/blob/master/communities.json), like so:
Follow these steps to submit your project:

### 1. Fork the Repository
Fork this repository to your GitHub account.

### 2. Add Your Logo
Place your project's logo in the [`/logos`](https://github.com/discord/discord-open-source/tree/master/logos) directory.

- **Dimensions:**
- For SVG: `72x72`
- For PNG: `144x144`
- **Optimization:** The logo should be minified.
- **Style:** The logo must be monochromatic and white (see examples on the [Discord Open Source page](https://discord.com/open-source)).
- **Format:** SVGs should contain only vector shapes (no raster graphics).

### 3. Update `communities.json`
Add your community details to the [`communities.json`](https://github.com/discord/discord-open-source/blob/master/communities.json) file using the following format:

```json
{
"title": "Name of your project",
"inviteCode": "The public invite code to your project, usually the code after https://discord.gg/",
"inviteCode": "The public invite code to your project (found after https://discord.gg/).",
"githubUrl": "The URL of your GitHub organization or project repository.",
"logo": "your-logo.svg",
"quote": "Optional: A short quote about how you use Discord for your project.",
"quoteSourceUrl": "Optional: An optional source for the quote."
"quoteSourceUrl": "Optional: A URL providing the source of the quote."
}
```

4. Submit a PR with your change, and if all is well, we'll merge it and display it on Discord's [open source page](https://discord.com/open-source)!
### 4. Submit a Pull Request
- Open a Pull Request (PR) with your changes.
- Ensure all criteria are met and the JSON follows the required structure.
- Once reviewed and approved, your community will be added to Discord's [Open Source page](https://discord.com/open-source).

---

## 📋 Notes

- Communities tied to Discord-focused projects (e.g., bots, modifications) are not eligible for this list.
- Ensure your logo meets all specifications to avoid delays in approval.
- Join our growing list of open source communities and connect with contributors worldwide!

💜 Thanks for contributing to open source and for being part of Discord!
91 changes: 53 additions & 38 deletions validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,71 @@ const { data: communities } = require('./communities.json');
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function validateCommunity(community, error, warn) {
for (const field of ['title', 'inviteCode', 'githubUrl', 'logo']) {
// Required string fields
const requiredFields = ['title', 'inviteCode', 'githubUrl', 'logo'];
for (const field of requiredFields) {
if (!community[field] || typeof community[field] !== 'string') {
error(`${chalk.bold(field)} field must be present and a string`);
}
}

const ghUrl = new URL(community.githubUrl);
if (ghUrl.protocol !== 'https:' || ghUrl.pathname.endsWith('.git')) {
error(`${chalk.bold('githubUrl')} should be a valid URL starting with \`https://\` (and not a cloning URL)`);
// Validate GitHub URL
try {
const ghUrl = new URL(community.githubUrl);
if (ghUrl.protocol !== 'https:' || ghUrl.pathname.endsWith('.git')) {
error(`${chalk.bold('githubUrl')} should be a valid URL starting with \`https://\` (and not a cloning URL)`);
}
} catch {
error(`${chalk.bold('githubUrl')} is not a valid URL`);
}

if (community.quote) {
if (community.quote.length > 350) {
warn(`${chalk.bold('quote')} field must not have more than 350 characters`);
}
// Validate quote length
if (community.quote && community.quote.length > 350) {
warn(`${chalk.bold('quote')} field must not have more than 350 characters`);
}

// Validate quote source URL
if (community.quoteSourceUrl) {
if (!community.quote) {
error(`${chalk.bold('quoteSourceUrl')} field requires the ${chalk.bold('quote')} field`);
}
const url = new URL(community.quoteSourceUrl);
if (url.protocol !== 'https:') {
error(`${chalk.bold('quoteSourceUrl')} should be a valid URL starting with \`https://\``);
try {
const url = new URL(community.quoteSourceUrl);
if (url.protocol !== 'https:') {
error(`${chalk.bold('quoteSourceUrl')} should be a valid URL starting with \`https://\``);
}
} catch {
error(`${chalk.bold('quoteSourceUrl')} is not a valid URL`);
}
}

await fs.stat(`./logos/${community.logo}`);
// Check if logo file exists
try {
await fs.stat(`./logos/${community.logo}`);
} catch {
error(`Logo file ${chalk.bold(community.logo)} not found in /logos folder`);
}

// Validate Discord invite
while (true) {
const req = await fetch(`https://discord.com/api/v9/invites/${community.inviteCode}?with_expiration=1`);
const response = await req.json();
const res = await fetch(`https://discord.com/api/v9/invites/${community.inviteCode}?with_expiration=1`);
const json = await res.json();

if (response.retry_after) {
console.warn(chalk.yellow(`Rate limited for ${response.retry_after}s, waiting`));
await delay(response.retry_after * 1000);
if (json.retry_after) {
console.warn(chalk.yellow(`Rate limited for ${json.retry_after}s, waiting...`));
await delay(json.retry_after * 1000);
continue;
}

if (!response.guild) {
error(`${community.inviteCode} ${util.inspect(response)}`);
if (!json.guild) {
error(`${community.inviteCode} ${util.inspect(json)}`);
}

if (response.expires_at) {
error('Invite must be permanant');
if (json.expires_at) {
error('Invite must be permanent');
}

if (!response.guild.features.includes('COMMUNITY')) {
if (!json.guild.features.includes('COMMUNITY')) {
warn('COMMUNITY feature is not enabled');
}

Expand All @@ -71,49 +88,47 @@ async function validateCommunity(community, error, warn) {

async function validate() {
console.log(chalk.underline.bold.white('Validating communities.json'));

const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
bar.start(communities.length, 0);

for (const community of communities) {
const error = (message) => {
const error = (msg) => {
process.exitCode = 1;
if (process.stderr.clearLine) {
process.stderr.clearLine();
process.stderr.cursorTo(0);
}
console.error(`${chalk.red.bold(community.title)}: ${message}`);
console.error(`${chalk.red.bold(community.title)}: ${msg}`);
};
const warn = (message) => {

const warn = (msg) => {
if (process.stderr.clearLine) {
process.stderr.clearLine();
process.stderr.cursorTo(0);
}
console.error(`${chalk.yellow.bold(community.title)}: ${message}`);
console.error(`${chalk.yellow.bold(community.title)}: ${msg}`);
};

await validateCommunity(community, error, warn);

bar.increment();
}

bar.stop();

const sorted = communities
.slice(0)
.sort((a, b) => a.title.localeCompare(b.title));

for (let i = 0; i < sorted.length; i += 1) {
const a = sorted[i];
const b = communities[i];
if (a.title !== b.title) {
console.log(chalk.red(`${chalk.bold(b.title)} is not in alphabetical order!`));
// Alphabetical order check
const sorted = [...communities].sort((a, b) => a.title.localeCompare(b.title));
for (let i = 0; i < sorted.length; i++) {
if (sorted[i].title !== communities[i].title) {
console.log(chalk.red(`${chalk.bold(communities[i].title)} is not in alphabetical order!`));
process.exitCode = 1;
break;
}
}
}

validate().catch((e) => {
// Entry point
validate().catch((err) => {
process.exitCode = 1;
console.error(e);
console.error(err);
});