-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a script for generating credits for scratch-gui (#1216)
Will be used for updating https://turbowarp.org/credits.html
- Loading branch information
1 parent
28f8f4e
commit 1bf1d05
Showing
3 changed files
with
172 additions
and
46 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
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,53 @@ | ||
const fs = require("fs"); | ||
const pathUtil = require("path"); | ||
|
||
/** | ||
* Recursively read a directory. | ||
* @param {string} directory | ||
* @returns {Array<[string, string]>} List of tuples [name, absolutePath]. | ||
* The return result includes files in subdirectories, but not the subdirectories themselves. | ||
*/ | ||
const recursiveReadDirectory = (directory) => { | ||
const result = []; | ||
for (const name of fs.readdirSync(directory)) { | ||
if (name.startsWith(".")) { | ||
// Ignore .eslintrc.js, .DS_Store, etc. | ||
continue; | ||
} | ||
const absolutePath = pathUtil.join(directory, name); | ||
const stat = fs.statSync(absolutePath); | ||
if (stat.isDirectory()) { | ||
for (const [ | ||
relativeToChildName, | ||
childAbsolutePath, | ||
] of recursiveReadDirectory(absolutePath)) { | ||
// This always needs to use / on all systems | ||
result.push([`${name}/${relativeToChildName}`, childAbsolutePath]); | ||
} | ||
} else { | ||
result.push([name, absolutePath]); | ||
} | ||
} | ||
return result; | ||
}; | ||
|
||
/** | ||
* Synchronous create a directory and any parents. Does nothing if the folder already exists. | ||
* @param {string} directory | ||
*/ | ||
const mkdirp = (directory) => { | ||
try { | ||
fs.mkdirSync(directory, { | ||
recursive: true, | ||
}); | ||
} catch (e) { | ||
if (e.code !== "ENOENT") { | ||
throw e; | ||
} | ||
} | ||
}; | ||
|
||
module.exports = { | ||
recursiveReadDirectory, | ||
mkdirp, | ||
}; |
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,118 @@ | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const https = require("https"); | ||
const fsUtils = require("./fs-utils"); | ||
const parseMetadata = require("./parse-extension-metadata"); | ||
|
||
class AggregatePersonInfo { | ||
/** @param {Person} person */ | ||
constructor(person) { | ||
this.name = person.name; | ||
|
||
/** @type {Set<string>} */ | ||
this.links = new Set(); | ||
} | ||
|
||
/** @param {string} link */ | ||
addLink(link) { | ||
this.links.add(link); | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} username | ||
* @returns {Promise<string>} | ||
*/ | ||
const getUserID = (username) => | ||
new Promise((resolve, reject) => { | ||
process.stdout.write(`Getting user ID for ${username}... `); | ||
const request = https.get(`https://api.scratch.mit.edu/users/${username}`); | ||
|
||
request.on("response", (response) => { | ||
const data = []; | ||
response.on("data", (newData) => { | ||
data.push(newData); | ||
}); | ||
|
||
response.on("end", () => { | ||
const allData = Buffer.concat(data); | ||
const json = JSON.parse(allData.toString("utf-8")); | ||
const userID = String(json.id); | ||
process.stdout.write(`${userID}\n`); | ||
resolve(userID); | ||
}); | ||
|
||
response.on("error", (error) => { | ||
process.stdout.write("error\n"); | ||
reject(error); | ||
}); | ||
}); | ||
|
||
request.on("error", (error) => { | ||
process.stdout.write("error\n"); | ||
reject(error); | ||
}); | ||
|
||
request.end(); | ||
}); | ||
|
||
const run = async () => { | ||
/** | ||
* @type {Map<string, AggregatePersonInfo>} | ||
*/ | ||
const aggregate = new Map(); | ||
|
||
const extensionRoot = path.resolve(__dirname, "../extensions/"); | ||
for (const [name, absolutePath] of fsUtils.recursiveReadDirectory( | ||
extensionRoot | ||
)) { | ||
if (!name.endsWith(".js")) { | ||
continue; | ||
} | ||
|
||
const code = fs.readFileSync(absolutePath, "utf-8"); | ||
const metadata = parseMetadata(code); | ||
|
||
for (const person of [...metadata.by, ...metadata.original]) { | ||
const personID = person.name.toLowerCase(); | ||
if (!aggregate.has(personID)) { | ||
aggregate.set(personID, new AggregatePersonInfo(person)); | ||
} | ||
|
||
if (person.link) { | ||
aggregate.get(personID).addLink(person.link); | ||
} | ||
} | ||
} | ||
|
||
const result = []; | ||
|
||
for (const id of [...aggregate.keys()].sort()) { | ||
const info = aggregate.get(id); | ||
|
||
if (info.links.size > 0) { | ||
const link = [...info.links.values()].sort()[0]; | ||
const username = link.match(/users\/(.+?)\/?$/)[1]; | ||
const userID = await getUserID(username); | ||
result.push({ | ||
userID, | ||
username, | ||
}); | ||
} else { | ||
result.push({ | ||
username: info.name, | ||
}); | ||
} | ||
} | ||
|
||
return result; | ||
}; | ||
|
||
run() | ||
.then((result) => { | ||
console.log(JSON.stringify(result, null, 4)); | ||
}) | ||
.catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
}); |