Skip to content

Instantly share code, notes, and snippets.

@iamandrewluca
Last active November 3, 2022 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamandrewluca/f723f2409443b30ddcc585c381736566 to your computer and use it in GitHub Desktop.
Save iamandrewluca/f723f2409443b30ddcc585c381736566 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
/**
* Makes quiz average from a bunch of Thinkific Quiz CSV files
*
* Prerequirements node and npm installed
* Drop this file in a folder only with Quiz CSVs exported from Thinkific
* Make this file executable by running `chmod +x quiz-totals`
* Install `csv` dependency by running `npm install csv`
* Execute it by giving output filename as argument
* ./quiz-totals fe1.csv
*/
const process = require("process");
const fs = require("fs");
const csv = require("csv/sync");
const Columns = {
email: "Student Email",
name: "Student Name",
score: "% Score",
quiz: "Quiz Name",
};
const debugStudentEmail = "email of student";
const [_node, _exec, fileName] = process.argv;
const files = fs
.readdirSync(process.cwd())
.filter((f) => f.endsWith(".csv") && f !== fileName);
const filesContents = files.map((f) => fs.readFileSync(f, "utf8"));
const quizzesLength = filesContents.length;
const fullQuizzes = filesContents.flatMap((fc) =>
csv.parse(fc, { columns: true })
);
const shrinkedQuizzes = fullQuizzes.map(dropQuestionsColumns);
const studentQuizzes = removeQuizDuplicatesByTakingMax(shrinkedQuizzes).map(
(quiz) => dropOtherColumns(quiz)
);
// console.log(
// studentQuizzes
// .filter((q) => q[Columns.email] === debugStudentEmail)
// .sort((a, b) => a[Columns.quiz] > b[Columns.quiz])
// );
const quizzesAverageByStudent = makeQuizzesAverageByStudent(
studentQuizzes,
quizzesLength
).sort((a, b) => a[Columns.email].localeCompare(b[Columns.email]));
const output = csv.stringify(quizzesAverageByStudent, { header: true });
console.table(quizzesAverageByStudent);
fs.writeFileSync(fileName, output);
function makeQuizzesAverageByStudent(quizzes, quizzesLength) {
let byStudentAverage = {};
quizzes.forEach((quiz) => {
const studentEmail = quiz[Columns.email];
if (!byStudentAverage[studentEmail]) {
byStudentAverage[studentEmail] = {
...quiz,
[Columns.score]: [quiz[Columns.score]],
};
} else {
byStudentAverage[studentEmail][Columns.score].push(quiz[Columns.score]);
}
});
// console.log(byStudentAverage[debugStudentEmail]);
return Object.values(byStudentAverage).map((quiz) => {
const scores = quiz[Columns.score];
const average = sum(scores) / quizzesLength;
const score = Math.round(average);
// if (quiz["Student Email"] === debugStudentEmail) {
// console.log({ scores, average, score });
// }
const data = {
...quiz,
["Score Percent"]: `${score}%`,
["Score Number"]: score,
["Score Normal"]: score / 100,
};
delete data[Columns.score];
return data;
});
}
function removeQuizDuplicatesByTakingMax(quizzes) {
const groupedQuizzes = {};
quizzes.forEach((quiz) => {
const studentEmail = quiz[Columns.email];
const quizName = quiz["Quiz Name"];
const quizScore = Number(quiz["% Score"]);
if (!groupedQuizzes[studentEmail]) groupedQuizzes[studentEmail] = {};
if (!groupedQuizzes[studentEmail][quizName])
groupedQuizzes[studentEmail][quizName] = quiz;
const existingQuizScore =
groupedQuizzes[studentEmail][quizName][Columns.score];
if (quizScore > existingQuizScore)
groupedQuizzes[studentEmail][quizName] = quiz;
});
return Object.values(groupedQuizzes).flatMap((quizzes) =>
Object.values(quizzes)
);
}
function dropQuestionsColumns(quiz) {
return Object.fromEntries(
Object.entries(quiz).filter(
([column]) => !column.match(/(Correct\?)|Response|(Question #\d+)/)
)
);
}
function dropOtherColumns(quiz) {
return {
[Columns.name]: quiz[Columns.name].trim(),
[Columns.email]: quiz[Columns.email].trim(),
[Columns.score]: Number(quiz[Columns.score]),
};
}
function groupBy(arr, key) {
return arr.reduce(
(acc, item) => ((acc[item[key]] = [...(acc[item[key]] || []), item]), acc),
{}
);
}
function sum(arr) {
return arr.reduce((a, b) => a + b, 0);
}
javascript: void ((() => {
/* This bookmarklet should be run in browser, on the zoom recording page */
let keyword = window.prompt("What is the searched keyword?").toUpperCase();
if (!keyword) return;
/** @type {HTMLElement[]} */
let allChatMessages = [...document.querySelectorAll(".chat-list-item")];
let allWithKeyWord = allChatMessages
.filter((chatListItem) => {
let contentWrapper = chatListItem.querySelector(".content-wrapper");
let text = contentWrapper.textContent.toUpperCase();
if (!text.includes(keyword)) return false;
let onlyLettersText = text.replace(/[^A-Z]+/g, "");
return onlyLettersText === keyword;
})
.map(chatListItem => chatListItem.querySelector(".user-name").textContent);
allWithKeyWord = [...new Set(allWithKeyWord)];
console.log(allWithKeyWord);
let textarea = document.createElement("textarea");
textarea.cols = Math.max(...allWithKeyWord.map(n => n.length)) + 10;
textarea.rows = allWithKeyWord.length + 10;
textarea.readOnly = true;
textarea.textContent = allWithKeyWord.join("\n");
textarea.style.display = "block";
let close = document.createElement("button");
close.textContent = "Close";
close.addEventListener("click", () => dialog.remove());
let copy = document.createElement("button");
/** @type {Clipboard} */
let clipboard = navigator.clipboard;
copy.textContent = "Copy";
copy.addEventListener("click", () => clipboard.writeText(textarea.textContent));
let dialog = document.createElement("dialog");
dialog.append(textarea, close, copy);
document.body.append(dialog);
dialog.showModal();
})());
#!/usr/bin/env node
/**
* Extracts a specific keyword from a Zoom recording conversation file
*
* Prerequirements node and npm installed
* Make this file executable by running `chmod +x zoomkey`
* Execute it by giving keyword and filename as arguments
* ./zoomkey php GMT20221017-134954_Recording.txt
*/
const { readFileSync } = require('fs')
const process = require('process')
const [node, exec, k, file] = process.argv
const keyword = k.toUpperCase()
const lines = readFileSync(file, 'utf-8')
.split('\n')
.filter(Boolean)
.map(l => l.split('\t'))
.filter(l => l.length === 3)
.map(([date, user, text]) => [date, user, text.trim()])
.filter(([,, text]) => text.toUpperCase().includes(keyword))
.filter(([,, text]) => text.toUpperCase().replace(/[^A-Z]+/g, "") === keyword)
console.log(lines);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment