Skip to content

Commit 983257a

Browse files
committed
Miscellaneous improvements and fixes, especially for "/osslicenses" command
- Function getLicense() is async now - node will be in the list of dependencies/tools whether its license can be fetched or not - npm will be in the list of dependencies/tools whether its package can be found locally or not. Reading from npm process output and fetching license from repository "npm/cli" is used as fallback - Added async function getLicenseByRepository(), usable with repositories managed on GitHub by supplying a valid name (userOrOrganization/repositoryName) - Fixed a bug related to package license file detection, where previously the filename doesn't need to match exactly but can just contain the matching pattern - Added additional logging when a valid package is found with getModuleFromPath() function - Overridden toString() function of NodeModule class, it now returns a string in the format of "packageName@version" - Added handler to handle timeout event from request returned by http.get(). On timeout, the request will be aborted and the generic error message will be sent - Dependencies and tools with "undefined" license will now have the "License" field hidden when info is sent - Dependencies and tools might now have "undefined" version again - Accept both "license" and "licence" for command execution - Removed progress "animation" from console when the process is restarted. This fixes the issue where the animation is separated into multiple groups if additional logging occurs when the animation hasn't finished yet. RIP animation... - Re-added configuration "cliProcessTimeout" - Fixed unexpected additional comma in configuration.json - Bumped version to v2.3.0-beta3
1 parent 766abb4 commit 983257a

File tree

3 files changed

+118
-56
lines changed

3 files changed

+118
-56
lines changed

configurations/configuration.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
"511620513643364353"
1414
],
1515
"developmentEnvironment": false,
16+
"cliProcessTimeout": 5000
1617
}

index.js

Lines changed: 116 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const developerIDs = getConfiguration("developerIDs");
7575
const developmentGuildIDs = getConfiguration("developmentGuildIDs");
7676
const token = getConfiguration("token");
7777
const developmentEnvironment = getConfiguration("developmentEnvironment");
78+
const cliProcessTimeout = getConfiguration("cliProcessTimeout");
7879
const remindmeMaxTimeStringLength = 100;
7980
// 100 is the limit for ms library, see https://github.com/zeit/ms/blob/2.1.1/index.js#L50
8081
const licenseInfoCooldown = 1000 * 60 * 30;
@@ -97,7 +98,6 @@ const inviteString = "Invite ChillBot to other servers!\nhttps://discordapp.com/
9798
const botUnavailableString = "Sorry, ChillBot is currently unavailable, most likely due to a new and not yet deployed update. Please check back later.";
9899
const githubAPIBaseURL = "https://api.github.com/";
99100
const changelogBaseURL = `${githubAPIBaseURL}repos/LightWayUp/chill/releases/`;
100-
const nodeLicenseURL = `${githubAPIBaseURL}repos/nodejs/node/license`
101101
const libraryList = getLibraries();
102102

103103
const response = {
@@ -141,20 +141,7 @@ const response = {
141141
};
142142

143143
if (process.env.RESTARTED !== undefined) {
144-
console.log("This process was started by the previous process.");
145-
if (restartTimeout >= 1000) {
146-
const timeoutInSeconds = Math.floor(restartTimeout / 1000);
147-
let waitString = ".".repeat(timeoutInSeconds);
148-
console.log(waitString);
149-
let i = 1;
150-
const timerID = setInterval(() => {
151-
waitString = "|" + waitString.substring(0, timeoutInSeconds - 1);
152-
console.log(waitString);
153-
if (i++ === timeoutInSeconds) {
154-
clearInterval(timerID);
155-
}
156-
}, 1000);
157-
}
144+
console.log(`This process was started by the previous process. Logging in after ${restartTimeout}ms...`);
158145
setTimeout(() => login(), restartTimeout);
159146
} else {
160147
login();
@@ -409,7 +396,9 @@ client.on("ready", () => {
409396
}
410397

411398
case "osslicenses":
412-
case "opensourcelicenses": {
399+
case "osslicences":
400+
case "opensourcelicenses":
401+
case "opensourcelicences": {
413402
if (!canSendMessages || shouldReject) {
414403
return;
415404
}
@@ -425,16 +414,23 @@ client.on("ready", () => {
425414
licenseInfoSentGuildList.delete(firstItemKey);
426415
}
427416
licenseInfoSentGuildList.set(guildID, [message.url, setTimeout(() => licenseInfoSentGuildList.delete(guildID), licenseInfoCooldown)]);
428-
for (const library of libraryList) {
417+
for (const library of await libraryList) {
429418
const libraryName = library.name
430419
const libraryVersion = library.version;
431-
let messageToSend = `${bold(libraryName)}\nVersion: ${libraryVersion}\nLicense:`;
420+
const libraryLicense = library.license;
421+
let messageToSend = bold(libraryName);
422+
if (libraryVersion !== undefined) {
423+
messageToSend += `\nVersion: ${libraryVersion}`;
424+
}
425+
if (libraryLicense !== undefined) {
426+
messageToSend += "\nLicense:";
427+
}
432428
await channel.send(messageToSend, sendOptionsForLongMessage)
433429
.then(async message => {
434-
messageToSend = library.license;
435-
if (messageToSend === undefined) {
436-
messageToSend = "Unavailable";
430+
if (libraryLicense === undefined) {
431+
return;
437432
}
433+
messageToSend = libraryLicense;
438434
while (messageToSend.length > maxSafeMessageLength) {
439435
let partialMessage = messageToSend.substring(0, maxSafeMessageLength);
440436
const splitableIndex = partialMessage.lastIndexOf("\n");
@@ -477,7 +473,7 @@ client.on("ready", () => {
477473
messageToSend = "Fetching changelog...";
478474
await channel.send(messageToSend)
479475
.catch(error => console.error(`An error occured while sending message "${messageToSend}"!\n\nFull details:\n${error}`));
480-
https.get(`${changelogBaseURL}${tagName !== undefined ? `tags/${tagName}`: "latest"}`, {
476+
const request = https.get(`${changelogBaseURL}${tagName !== undefined ? `tags/${tagName}`: "latest"}`, {
481477
headers: {
482478
"User-Agent": chillPackageJson.name
483479
},
@@ -540,6 +536,11 @@ client.on("ready", () => {
540536
.catch(error => console.error(`An error occured while sending message "${errorFetchingChangelogString}"!\n\nFull details:\n${error}`));
541537
}
542538
});
539+
}).on("timeout", () => {
540+
request.abort();
541+
console.error("Unable to get changelog, request timed out!");
542+
channel.send(errorFetchingChangelogString)
543+
.catch(error => console.error(`An error occured while sending message "${errorFetchingChangelogString}"!\n\nFull details:\n${error}`));
543544
});
544545
break;
545546
}
@@ -774,6 +775,19 @@ function getConfiguration(configurationType) {
774775
break;
775776
}
776777

778+
case "cliProcessTimeout": {
779+
if (result === useProcessEnvIdentifier) {
780+
result = parseInt(process.env.BOT_CLI_PROCESS_TIMEOUT, 10);
781+
}
782+
if (!(typeof result === "number" && isInteger(result.toString()))) {
783+
throw new TypeError("Invalid cliProcessTimeout value!");
784+
}
785+
if (result <= 0) {
786+
throw new Error("Invalid cliProcessTimeout value, cliProcessTimeout must not be less than or equal to 0!");
787+
}
788+
break;
789+
}
790+
777791
default: {
778792
throw new Error("Invalid configuration to fetch!");
779793
}
@@ -790,6 +804,10 @@ function NodeModule(name, version, license) {
790804
this.license = license;
791805
}
792806

807+
NodeModule.prototype.toString = function() {
808+
return `${this.name}@${this.version}`;
809+
}
810+
793811
function getModuleFromPath(directoryPath) {
794812
if (!(typeof directoryPath === "string" || directoryPath instanceof String || directoryPath instanceof url.URL)) {
795813
throw new TypeError("Incorrect type for getModuleFromPath argument!");
@@ -806,10 +824,11 @@ function getModuleFromPath(directoryPath) {
806824
}
807825
const packageJson = require(packageJsonPath);
808826
const nodeModule = new NodeModule(packageJson.name, packageJson.version);
827+
console.log(`Found package "${nodeModule}"`);
809828
const foundLicensesPath = [];
810829
const foundLicenses = fs.readdirSync(directoryPath).find(file => {
811830
const filePath = path.resolve(directoryPath, `./${file}`);
812-
if (fs.statSync(filePath).isFile() && /((licen(s|c)e(s)?)|(copying))(\.((md)|(txt))?)?/gi.test(file)) {
831+
if (fs.statSync(filePath).isFile() && /^((licen(s|c)e(s)?)|(copying))(\.((md)|(txt))?)?$/gi.test(file)) {
813832
foundLicensesPath.push(filePath);
814833
return true;
815834
}
@@ -821,41 +840,83 @@ function getModuleFromPath(directoryPath) {
821840
return nodeModule;
822841
}
823842

824-
function getLibraries() {
825-
const libraries = [];
826-
https.get(nodeLicenseURL, {
827-
headers: {
828-
"User-Agent": chillPackageJson.name,
829-
"Accept": "application/vnd.github.v3.raw"
830-
},
831-
timeout: apiFetchTimeout
832-
}, response => {
833-
const statusCode = response.statusCode;
834-
const contentType = "content-type";
835-
if (statusCode !== 200) {
836-
response.resume();
837-
return console.error(`Unable to get LICENSE for nodejs/node, server responded with status code ${statusCode}!`);
838-
}
839-
const receivedType = response.headers[contentType];
840-
if (!(/^application\/.*raw/gi.test(receivedType))) {
841-
response.resume();
842-
return console.error(`Unable to get LICENSE for nodejs/node, response content type "${receivedType}" does not match "application/vnd.github.v3.raw"!`);
843-
}
844-
let raw = "";
845-
response.on("data", chunk => raw += chunk)
846-
.on("error", error => {
847-
console.error(`An error occured while attempting to fetch LICENSE for nodejs/node!\n\nFull details:\n${error}`);
848-
}).on("end", () => {
849-
if (!response.complete) {
850-
return console.error("Unable to get LICENSE for nodejs/node, connection was terminated while response was still not fully received!");
851-
}
852-
libraries.unshift(new NodeModule("node", process.version, raw.replace(/\r/gi, "")));
843+
function getLicenseByRepository(repository) {
844+
if (!(typeof repository === "string" || repository instanceof String)) {
845+
throw new TypeError("Incorrect type for getLicenseByRepository argument!");
846+
}
847+
if (!/^[^(/|\s)]+\/[^(/|\s)]+$/gi.test(repository)) {
848+
throw new Error("Invalid repository name!");
849+
}
850+
return new Promise((resolve, reject) => {
851+
const request = https.get(`${githubAPIBaseURL}repos/${repository}/license`, {
852+
headers: {
853+
"User-Agent": chillPackageJson.name,
854+
"Accept": "application/vnd.github.v3.raw"
855+
},
856+
timeout: apiFetchTimeout
857+
}, response => {
858+
const statusCode = response.statusCode;
859+
const contentType = "content-type";
860+
if (statusCode !== 200) {
861+
response.resume();
862+
return reject(new Error(`Unable to get license for ${repository}, server responded with status code ${statusCode}!`));
863+
}
864+
const receivedType = response.headers[contentType];
865+
if (!(/^application\/.*raw/gi.test(receivedType))) {
866+
response.resume();
867+
return reject(new Error(`Unable to get license for ${repository}, response content type "${receivedType}" does not match "application/vnd.github.v3.raw"!`));
868+
}
869+
let raw = "";
870+
response.on("data", chunk => raw += chunk)
871+
.on("error", error => {
872+
console.error(`An error occured while attempting to fetch license for ${repository}!\n\nFull details:\n${error}`);
873+
}).on("end", () => {
874+
if (!response.complete) {
875+
return reject(new Error(`Unable to get license for ${repository}, connection was terminated while response was still not fully received!`));
876+
}
877+
resolve(raw.replace(/\r/gi, ""));
878+
});
879+
}).on("timeout", () => {
880+
request.abort();
881+
reject(new Error(`Unable to get license for ${repository}, request timed out!`));
853882
});
854883
});
855-
const npm = getModuleFromPath(path.resolve(path.dirname(process.argv[0]), "./node_modules/npm"));
856-
if (npm !== undefined) {
857-
libraries.push(npm);
884+
}
885+
886+
async function getLibraries() {
887+
const libraries = [];
888+
let nodeLicense;
889+
await getLicenseByRepository("nodejs/node")
890+
.then(license => nodeLicense = license,
891+
error => console.error(error));
892+
libraries.push(new NodeModule("node", process.version, nodeLicense));
893+
let npm = getModuleFromPath(path.resolve(path.dirname(process.argv[0]), "./node_modules/npm"));
894+
if (npm === undefined) {
895+
let npmPath = path.resolve(path.dirname(process.argv[0]), (process.platform === "win32" ? "./npm.cmd" : "./npm"));
896+
if (npmPath.includes(" ")) {
897+
npmPath = `"${npmPath}"`;
898+
}
899+
const npmProcess = childProcess.spawnSync(npmPath, ["-v"], {
900+
timeout: cliProcessTimeout,
901+
shell: true,
902+
windowsHide: true
903+
});
904+
let npmVersion;
905+
if (npmProcess.error !== undefined && npmProcess.error !== null) {
906+
console.error(`An error occured while attempting to read stdout of NPM process!\n\nFull details:\n${npmProcess.error}`);
907+
} else {
908+
const bufferString = npmProcess.stdout.toString();
909+
if (/^\s*(\d+\.){2}\d+\s*$/gi.test(bufferString)) {
910+
npmVersion = bufferString.trim();
911+
}
912+
}
913+
let npmLicense;
914+
await getLicenseByRepository("npm/cli")
915+
.then(license => npmLicense = license,
916+
error => console.error(error));
917+
npm = new NodeModule("npm", npmVersion, npmLicense);
858918
}
919+
libraries.push(npm);
859920
const mainModule = require.main;
860921
if (mainModule === undefined) {
861922
return libraries;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chillbot",
3-
"version": "2.3.0-beta2",
3+
"version": "2.3.0-beta3",
44
"description": "Chill!",
55
"keywords": [
66
"discord",

0 commit comments

Comments
 (0)