334 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| // AUTOGENERATED COPYRIGHT HEADER START
 | |
| // Copyright (C) 2021-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
 | |
| // AUTOGENERATED COPYRIGHT HEADER END
 | |
| 
 | |
| const HTTPS = require("https");
 | |
| const PATH = require("path");
 | |
| const FS = require("fs/promises");
 | |
| const CHILD_PROCESS = require("child_process");
 | |
| 
 | |
| function query_git() {
 | |
| 	// git shortlog -sn --all | sed -E "s/^\W+[0-9]+\W+//"
 | |
| 	return (async () => {
 | |
| 		let users = new Map();
 | |
| 
 | |
| 		let git = new Promise((resolve, reject) => {
 | |
| 			try {
 | |
| 				const child = CHILD_PROCESS.spawn('git', ['shortlog', '-sn', '--all']);
 | |
| 
 | |
| 				let sout = "";
 | |
| 				child.stdout.setEncoding('utf8');
 | |
| 				child.stdout.on('data', (data) => {
 | |
| 					sout += data;
 | |
| 				});
 | |
| 
 | |
| 				let serr = "";
 | |
| 				child.stderr.setEncoding('utf8');
 | |
| 				child.stderr.on('data', (data) => {
 | |
| 					serr += data;
 | |
| 				});
 | |
| 
 | |
| 				child.on('close', (code) => {
 | |
| 					if (code != 0) {
 | |
| 						reject([code, serr]);
 | |
| 					} else {
 | |
| 						resolve(sout);
 | |
| 					}
 | |
| 				});
 | |
| 			} catch (e) {
 | |
| 				reject(e);
 | |
| 			}
 | |
| 		});
 | |
| 
 | |
| 		let result = await git;
 | |
| 		let lines = result.matchAll(/^[\s]+([0-9]+)[\s]+(.+)$/gim);
 | |
| 		for (let line of lines) {
 | |
| 			let name = line[2];
 | |
| 			if (!users.has(name)) {
 | |
| 				users.set(name, `https://github.com/${process.env.GITHUB_REPOSITORY}/graphs/contributors`)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return users;
 | |
| 	})();
 | |
| }
 | |
| 
 | |
| function query_crowdin(project_id, project_auth_key) {
 | |
| 	const limit = 100;
 | |
| 	function _query_page(page) {
 | |
| 		return new Promise((resolve, reject) => {
 | |
| 			try {
 | |
| 				let query = {
 | |
| 					protocol: "https:",
 | |
| 					host: "crowdin.com",
 | |
| 					path: `/api/v2/projects/${project_id}/members?limit=${limit}&offset=${page * limit}`,
 | |
| 					method: "GET",
 | |
| 					headers: {
 | |
| 						"accept": "application/json",
 | |
| 						"authorization": `Bearer ${project_auth_key}`
 | |
| 					}
 | |
| 				};
 | |
| 				let req = HTTPS.request(query, (res) => {
 | |
| 					let data = "";
 | |
| 					res.setEncoding('utf8');
 | |
| 					res.on('data', (chunk) => {
 | |
| 						data += chunk;
 | |
| 					});
 | |
| 					res.on('end', () => {
 | |
| 						res.data = data;
 | |
| 						resolve(res);
 | |
| 					});
 | |
| 				});
 | |
| 				req.end();
 | |
| 			} catch (e) {
 | |
| 				reject(e);
 | |
| 			}
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	return (async () => {
 | |
| 		let page_is_last = false;
 | |
| 		let page = 0;
 | |
| 
 | |
| 		let users = new Map();
 | |
| 
 | |
| 		while (!page_is_last) {
 | |
| 			let res = await _query_page(page++);
 | |
| 			if (res.statusCode == 200) {
 | |
| 				let json = JSON.parse(res.data);
 | |
| 				let count = json.data.length;
 | |
| 				for (let user of json.data) {
 | |
| 					// Don't credit blocked users for work not done.
 | |
| 					if (user.data.role == 'blocked') {
 | |
| 						continue;
 | |
| 					}
 | |
| 
 | |
| 					let key = (user.data.fullName !== null) && (user.data.fullName !== undefined) && (user.data.fullName.length > 0) ? user.data.fullName : user.data.username;
 | |
| 
 | |
| 					users.set(key, `https://crowdin.com/profile/${user.data.username}`);
 | |
| 				}
 | |
| 
 | |
| 				page_is_last = (count < limit);
 | |
| 			} else {
 | |
| 				throw new Error(JSON.parse(res.data));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return users;
 | |
| 	})();
 | |
| }
 | |
| 
 | |
| function query_github_sponsors(auth_token) {
 | |
| 	const HTTPS = require("https");
 | |
| 
 | |
| 	let build_query = function (cursor) {
 | |
| 		if (cursor === undefined) {
 | |
| 			return `query {
 | |
| 	viewer {
 | |
| 		sponsors(after: null, first: 0) {
 | |
| 			totalCount
 | |
| 		}
 | |
| 	}
 | |
| }`;
 | |
| 		} else {
 | |
| 			return `query {
 | |
| 	viewer {
 | |
| 		sponsors(after: ${typeof (cursor) == "string" ? `"${cursor}"` : "null"}, first: 100) {
 | |
| 			nodes {
 | |
| 				__typename
 | |
| 				... on User {
 | |
| 					resourcePath
 | |
| 					login
 | |
| 					name
 | |
| 				}
 | |
| 				... on Organization {
 | |
| 					resourcePath
 | |
| 					login
 | |
| 					name
 | |
| 				}
 | |
| 			}
 | |
| 			pageInfo {
 | |
| 			  endCursor
 | |
| 			  startCursor
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }`;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	let do_query = function (schema) {
 | |
| 		return new Promise((resolve, reject) => {
 | |
| 			let text_query = JSON.stringify({ query: schema });
 | |
| 
 | |
| 			let request = HTTPS.request(
 | |
| 				"https://api.github.com/graphql",
 | |
| 				{
 | |
| 					method: 'POST',
 | |
| 					headers: {
 | |
| 						'Content-Type': 'application/json',
 | |
| 						'Accept': 'application/json',
 | |
| 						'Authorization': `bearer ${auth_token}`,
 | |
| 						'User-Agent': 'Node.JS'
 | |
| 					},
 | |
| 				}, (res) => {
 | |
| 					let data = "";
 | |
| 					res.setEncoding('utf8');
 | |
| 					res.on('data', (chunk) => {
 | |
| 						data += chunk;
 | |
| 					});
 | |
| 					res.on('error', () => {
 | |
| 						reject();
 | |
| 					});
 | |
| 					res.on('end', () => {
 | |
| 						res.data = JSON.parse(data);
 | |
| 						resolve(res);
 | |
| 					});
 | |
| 				});
 | |
| 			request.write(text_query);
 | |
| 			request.end();
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return (async () => {
 | |
| 		let users = new Map();
 | |
| 		let total = 0;
 | |
| 
 | |
| 		// Find the total amount.
 | |
| 		{
 | |
| 			let query = build_query();
 | |
| 			let response = await do_query(query);
 | |
| 			let data = response.data.data;
 | |
| 			total = data.viewer.sponsors.totalCount;
 | |
| 		}
 | |
| 
 | |
| 		// Index by page, in 100 user increments.
 | |
| 		let cursor = null;
 | |
| 		for (let idx = 0, edx = total; idx < edx; idx += 100) {
 | |
| 			let query = build_query(cursor);
 | |
| 			let response = await do_query(query);
 | |
| 			let data = response.data.data;
 | |
| 
 | |
| 			// Insert data info Map.
 | |
| 			for (let entry of data.viewer.sponsors.nodes) {
 | |
| 				let name = entry.login;
 | |
| 				if (typeof (entry.name) === "string") {
 | |
| 					name = entry.name;
 | |
| 				}
 | |
| 
 | |
| 				users.set(name, `https://github.com${entry.resourcePath}`);
 | |
| 			}
 | |
| 
 | |
| 			// Update cursor for further queries.
 | |
| 			cursor = data.viewer.sponsors.pageInfo.endCursor;
 | |
| 		}
 | |
| 
 | |
| 		return users;
 | |
| 	})();
 | |
| }
 | |
| 
 | |
| (async () => {
 | |
| 	let json = {};
 | |
| 	let markdown = `# StreamFX Contributors & Supporters
 | |
| 
 | |
| ## Contributors
 | |
| 
 | |
| `;
 | |
| 
 | |
| 	// Contributors
 | |
| 	{// - Git
 | |
| 		markdown += `### Code, Media
 | |
| Thanks go to the following people, who have either wrangled with code or wrangled with image editors while saving often in the hopes of not losing any changes:
 | |
| 
 | |
| `;
 | |
| 		json.contributor = {};
 | |
| 		let info = await query_git();
 | |
| 		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-contributors-git.json")));
 | |
| 		for (let key in extra) {
 | |
| 			info.set(key, extra[key]);
 | |
| 		}
 | |
| 		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
 | |
| 			let value = info.get(key);
 | |
| 			json.contributor[key] = value;
 | |
| 			markdown += `* [${key}](${value})\n`;
 | |
| 		}
 | |
| 		markdown += "\n";
 | |
| 	}
 | |
| 
 | |
| 	{// - Crowdin
 | |
| 		markdown += `### Translators
 | |
| [StreamFX relies on crowd-sourced translations to be available in your language.](https://github.com/Xaymar/obs-StreamFX/issues/36) Much thanks go out to all volunteer translators who have taken some time to submit translations on Crowdin.
 | |
| 
 | |
| `;
 | |
| 		json.translator = {};
 | |
| 		let info = await query_crowdin(process.env.CROWDIN_PROJECTID, process.env.CROWDIN_TOKEN);
 | |
| 		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-contributors-git.json")));
 | |
| 		for (let key in extra) {
 | |
| 			info.set(key, extra[key]);
 | |
| 		}
 | |
| 		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
 | |
| 			let value = info.get(key);
 | |
| 			json.translator[key] = value;
 | |
| 			markdown += `* [${key}](${value})\n`;
 | |
| 		}
 | |
| 		markdown += "\n";
 | |
| 	}
 | |
| 
 | |
| 	// Supporters
 | |
| 	markdown += `## Supporters
 | |
| The StreamFX project relies on generous donations from you through [Patreon](https://patreon.com/xaymar), [GitHub](https://github.com/sponsors/xaymar) or [PayPal](https://paypal.me/xaymar). Huge thanks go out to the following people for supporting StreamFX:
 | |
| 
 | |
| `;
 | |
| 	json.supporter = {}
 | |
| 	{// - GitHub
 | |
| 		markdown += `### GitHub Sponsors
 | |
| `;
 | |
| 		json.supporter.github = {};
 | |
| 		let info = await query_github_sponsors(process.env.GITHUB_TOKEN);
 | |
| 		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-supporters-github.json")));
 | |
| 		for (let key in extra) {
 | |
| 			info.set(key, extra[key]);
 | |
| 		}
 | |
| 		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
 | |
| 			let value = info.get(key);
 | |
| 			json.supporter.github[key] = value;
 | |
| 			markdown += `* [${key}](${value})\n`;
 | |
| 		}
 | |
| 		markdown += "\n";
 | |
| 	}
 | |
| 
 | |
| 	{// - Patreon
 | |
| 		// TODO: How do we OAuth without OAuth?!
 | |
| 		markdown += `### Patreon Patrons
 | |
| `;
 | |
| 		json.supporter.patreon = {};
 | |
| 		let info = new Map();
 | |
| 		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-supporters-patreon.json")));
 | |
| 		for (let key in extra) {
 | |
| 			info.set(key, extra[key]);
 | |
| 		}
 | |
| 		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
 | |
| 			let value = info.get(key);
 | |
| 			json.supporter.patreon[key] = value;
 | |
| 			markdown += `* [${key}](${value})\n`;
 | |
| 		}
 | |
| 		markdown += "\n";
 | |
| 	}
 | |
| 
 | |
| 	// Write Markdown file
 | |
| 	FS.writeFile(process.argv[2], 
 | |
| 		markdown, 
 | |
| 		{
 | |
| 			encoding: 'utf8'
 | |
| 		}
 | |
| 	)
 | |
| 
 | |
| 	// Write JSON file
 | |
| 	FS.writeFile(process.argv[3], 
 | |
| 		JSON.stringify(json, undefined, '\t'),
 | |
| 		{
 | |
| 			encoding: 'utf8'
 | |
| 		}
 | |
| 	)
 | |
| })();
 |