This commit is contained in:
yoannchb-pro
2023-03-20 21:17:29 -04:00
committed by GitHub
parent 1a3bb2c0ad
commit f1f271c66e
26 changed files with 895 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# CHANGELOG
## v1.0.0
- Initial commit
+70
View File
@@ -0,0 +1,70 @@
<p align="center"><a
href="https://www.flaticon.com/free-icons/mortarboard" target="_blank" rel="noopener noreferrer"
title="Mortarboard icons created by itim2101 - Flaticon" ><img src="./extension/icon.png" alt="Mortarboard icons created by itim2101 - Flaticon" width="150" style="display:block; margin:auto;"></a></p>
# Moodle-gpt
This extension allows you to hide CHAT-GPT in a Moodle quiz. You just need to enter <b>the code configured in the extension</b> on the keyboard and then click on the question you want to solve, and CHAT-GPT will automatically provide the answer. However, one needs to be careful because as we know, CHAT-GPT can make errors especially in calculations.
## Disclaimer !
I hereby declare that I am not responsible for any misuse or illegal activities carried out using my program. The code is provided for educational and research purposes only, and any use of it outside of these purposes is at the user's own risk.
## Update
- See [changelog](./CHANGELOG.md)
## Set up
Go to <b>"Manage my extensions"</b> on your browser, then click on <b>"Load unpacked extension"</b> and select the <b>"extension"</b> folder. Afterwards, click on the extension icons and enter the apiKey obtained from [openai](https://platform.openai.com/). Finally, enter a code that will activate the extension on your moodle page and enter the langage of the quiz.
## Test
To test the code, you can run the index.js file located in the <b>"test"</b> folder.
## Options
![Popup](./assets/popup.png)
- Title indication: Show some informations into the title to know for example if the code have been injected. ![Injected](./assets/title-injected.png)
- Console logs: show logs into the console. ![Logs](./assets/logs.png)
- Cursor indication: show a pointer cursor and a hourglass to know when the request is finished.
- Typing effect: create a typing effect for text. ![Typing](./assets/typing.gif)
- Mouseover effect: you will need to hover (or click for select) the question response to complete it automaticaly. ![Mouseover](./assets/mouseover.gif) ![Mouseover2](./assets/mouseover2.gif)
## Examples
### Select
![Select](./assets/select.gif)
### Put in order question
![Order](./assets/order.gif)
### Resolve equation
![Equations](./assets/equations.gif)
### One response (radio button)
![Radio](./assets/radio.gif)
### Multiples responses (checkbox)
![Checkbox](./assets/checkbox.gif)
### True or false
![True-false](./assets/true-false.gif)
### Text
![Text](./assets/text.gif)
## If it can't complete the question, the answer will be copied to your clipboard
To know if the answer has been copied to the clipboard, you can look at the title of the page which will become <b>"Copied to clipboard"</b> for 5 seconds.
![Clipboard](./assets/clipboard.gif)
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+26
View File
@@ -0,0 +1,26 @@
{
"manifest_version": 3,
"name": "moddle-gpt",
"version": "1.0",
"description": "Hidden chat-gpt for moodle cheat",
"permissions": ["activeTab", "tabs", "storage"],
"action": {
"default_icon": "icon.png",
"default_popup": "./popup/index.html"
},
"icons": {
"16": "icon.png",
"32": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"content_scripts": [
{
"matches": ["https://*/*", "http://*/*"],
"js": ["moodle-gpt.js"],
"run_at": "document_end"
}
]
}
+254
View File
@@ -0,0 +1,254 @@
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
const config = storage.moodleGPT;
if (!config) throw new Error("Please configure MoodleGPT into the extension");
//listening to the keys to inject moodleGPT
const pressedKeys = [];
const listeners = [];
document.body.addEventListener("keypress", function (e) {
pressedKeys.push(e.key);
if (pressedKeys.length > config.code) pressedKeys.shift();
if (pressedKeys.join("") === config.code) {
pressedKeys.length = 0;
setUpMoodleGpt();
}
});
/**
* Show some informations into the document title and remove it after 3000ms
* @param {*} text
*/
function titleIndications(text) {
const backTitle = document.title;
document.title = text;
setTimeout(() => (document.title = backTitle), 3000);
}
/**
* Setup moodleGPT into the page (remove/injection)
*/
function setUpMoodleGpt() {
//removing events
if (listeners.length > 0) {
for (const listener of listeners) {
if (config.cursor) listener.element.style.cursor = "initial";
listener.element.removeEventListener("click", listener.fn, {
once: true,
});
}
if (config.title) titleIndications("Removed");
listeners.length = 0;
return;
}
//injection
const inputQuery = ["checkbox", "radio", "text"]
.map((e) => `input[type="${e}"]`)
.join(",");
const query = inputQuery + ", textarea, select";
const forms = Array.from(document.querySelectorAll(".formulation"));
for (const form of forms) {
const hiddenButton = form.querySelector(".qtext");
if (config.cursor) hiddenButton.style.cursor = "pointer";
const fn = reply.bind(null, hiddenButton, form, query);
listeners.push({ element: hiddenButton, fn });
hiddenButton.addEventListener("click", fn, { once: true });
}
if (config.title) titleIndications("Injected");
}
/**
* Normlize text
* @param {*} text
*/
function normalizeText(text) {
return text.replace(/\n+/gi, "\n").toLowerCase().trim();
}
/**
* Get the response from chatGPT api
* @param {*} question
* @returns
*/
async function getChatGPTResponse(question) {
const req = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${config.apiKey}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: question }],
temperature: 0.8,
top_p: 1.0,
presence_penalty: 1.0,
stop: null,
}),
});
const rep = await req.json();
const response = rep.choices[0].message.content;
return normalizeText(response);
}
/**
* Handling logs into the console
*/
class Logs {
static question(text) {
const css = "color: blue";
console.log("%c[QUESTION]: %s", css, text);
}
static responseTry(text, valide) {
const css = "color: " + (valide ? "green" : "red");
console.log("%c[CHECKING]: %s", css, text);
}
static array(arr) {
console.log("[CORRECTS] ", arr);
}
static response(text) {
console.log(text);
}
}
/**
* Reply to the question
* @param {*} form
* @param {*} query
* @returns
*/
async function reply(hiddenButton, form, query) {
if (config.cursor) hiddenButton.style.cursor = "wait";
const question = normalizeText(
form.textContent.replace("Texte de la question", "")
);
const inputList = form.querySelectorAll(query);
const response = await getChatGPTResponse(
`Give a short response as possible for this question, reply in this langage "${config.langage}", only show the result:
${question}
(if you have to choose between multiple results only show the corrects one)`
);
if (config.logs) {
Logs.question(question);
Logs.response(response);
}
if (config.cursor) hiddenButton.style.cursor = "initial";
//if we dont find the input we copy into the clipboard
if (inputList.length === 0) {
if (config.title) titleIndications("Copied to clipboard");
navigator.clipboard.writeText(response);
return;
}
//if it's a text
if (
(inputList.length === 1 && inputList[0].type === "text") ||
inputList[0].tagName === "TEXTAREA"
) {
if (config.typing) {
for (let i = 0; i < response.length; ++i) {
setTimeout(
() => (inputList[0].value = response.slice(0, i + 1)),
i * 50
);
}
} else {
inputList[0].value = response;
}
return;
}
//if it's a select
if (inputList[0].tagName === "SELECT") {
let correct = response.split("\n");
if (correct.length === 1 && correct.length !== inputList)
correct = response.split(",");
if (config.logs) Logs.array(correct);
for (let j = 0; j < inputList.length; ++j) {
const options = inputList[j].querySelectorAll("option");
for (const option of options) {
const content = option.textContent.toLocaleLowerCase().trim();
const valide = correct[j].includes(content);
//if it's a put in order
if (!isNaN(parseInt(content))) {
const content = normalizeText(
option.parentNode.closest("tr").querySelector(".text").textContent
);
const index = correct.findIndex((c) => {
const valide = c.includes(content);
if (config.logs) Logs.responseTry(content, valide);
return valide;
});
if (index !== -1) {
if (config.mouseover) {
options[index + 1].closest("select").addEventListener(
"click",
function () {
options[index + 1].selected = "selected";
},
{ once: true }
);
} else {
options[index + 1].selected = "selected";
}
break;
}
}
if (config.logs) Logs.responseTry(content, valide);
if (valide) {
if (config.mouseover) {
option.closest("select").addEventListener(
"click",
function () {
option.selected = "selected";
},
{ once: true }
);
} else {
option.selected = "selected";
}
break;
}
}
}
return;
}
//if it's a radio button or checkbox
for (const input of inputList) {
const content = normalizeText(input.parentNode.textContent);
const valide = response
.replace(/^[a-z\d]\.\s/gi, "")
.includes(content.replace(/^[a-z\d]\.\s/gi, ""));
if (config.logs) Logs.responseTry(content, valide);
if (valide) {
if (config.mouseover) {
input.addEventListener(
"mouseover",
function (event) {
event.target.checked = true;
},
{ once: true }
);
} else {
input.checked = true;
}
}
}
}
});
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MoodleGPT</title>
<link rel="stylesheet" href="style.css" />
<script src="index.js" defer></script>
<link rel="icon" type="image/png" href="../icon.png" />
</head>
<body>
<main>
<a
target="_blank"
rel="noopener noreferrer"
href="https://www.flaticon.com/free-icons/mortarboard"
title="Mortarboard icons created by itim2101 - Flaticon"
><img src="../icon.png" alt="icon"
/></a>
<h1>MoodleGPT</h1>
<div class="line">
<label for="apiKey" class="textLabel">Api Key</label>
<input id="apiKey" type="password" />
</div>
<div class="line">
<label for="code" class="textLabel">Code</label>
<input id="code" type="text" />
</div>
<div class="line">
<label for="langage" class="textLabel">Langage</label>
<input id="langage" type="text" />
</div>
<div class="line" style="margin-top: 1rem">
<input id="typing" type="checkbox" checked />
<label for="typing">Typing effect</label>
</div>
<div class="line" style="margin-bottom: 1rem">
<input id="mouseover" type="checkbox" />
<label for="mouseover">Mouseover effect</label>
</div>
<div class="line">
<input id="logs" type="checkbox" />
<label for="logs">Console logs</label>
</div>
<div class="line">
<input id="title" type="checkbox" checked />
<label for="title">Title indication</label>
</div>
<div class="line">
<input id="cursor" type="checkbox" checked />
<label for="cursor">Cursor indication</label>
</div>
<p id="message"></p>
<div class="line">
<button class="save">Save</button>
</div>
</main>
</body>
</html>
+63
View File
@@ -0,0 +1,63 @@
const saveBtn = document.querySelector(".save");
const message = document.querySelector("#message");
const inputsText = ["apiKey", "code", "langage"];
const inputsCheckbox = ["logs", "title", "cursor", "typing", "mouseover"];
function showMessage(messageTxt, valide) {
message.style.color = valide ? "limegreen" : "red";
message.textContent = messageTxt;
message.style.display = "block";
setTimeout(() => (message.style.display = "none"), 5000);
}
//save the configuration
saveBtn.addEventListener("click", function () {
const [apiKey, code, langage] = inputsText.map((selector) =>
document.querySelector("#" + selector).value.trim()
);
const [logs, title, cursor, typing, mouseover] = inputsCheckbox.map(
(selector) => document.querySelector("#" + selector).checked
);
if (!apiKey || !code || !langage) {
showMessage("Please comple all the form");
return;
}
if (code.length < 3) {
showMessage("The code should at least contain 3 characters");
return;
}
chrome.storage.sync.set({
moodleGPT: {
apiKey,
code,
langage,
logs,
title,
cursor,
typing,
mouseover,
},
});
showMessage("Configuration saved", true);
});
//we load back the configuration
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
if (storage.moodleGPT) {
const config = storage.moodleGPT;
inputsText.forEach(
(key) => (document.querySelector("#" + key).value = config[key] || "")
);
inputsCheckbox.forEach(
(key) => (document.querySelector("#" + key).checked = config[key] || "")
);
} else {
//set default config
document.querySelector("#langage").value = navigator.language;
}
});
+89
View File
@@ -0,0 +1,89 @@
@font-face {
font-family: Segeo UI;
src: url(../../fonts/Segoe\ UI.ttf);
}
:root {
--bg-color: #121212;
--color: #fff;
--btn-color: #7f39fb;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI", sans-serif;
color: var(--color);
}
body {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.75rem;
gap: 0.4rem;
text-align: center;
width: 20rem;
}
img {
width: 5rem;
margin-top: 1rem;
}
h1 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.line {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.line .textLabel {
width: 5rem;
text-transform: uppercase;
}
.line input[type="text"],
.line input[type="password"] {
flex: 1 1;
border: thin solid var(--color);
padding: 0.3rem 0.5rem;
border-radius: 0.2rem;
outline-color: transparent;
background-color: transparent;
}
.line input[type="checkbox"] {
accent-color: var(--btn-color);
margin-right: 0.3rem;
}
.save {
border: none;
background-color: var(--btn-color);
margin-top: 1.5rem;
padding: 0.5rem 2rem;
cursor: pointer;
border-radius: 0.2rem;
font-size: 1.5rem;
margin-bottom: 1rem;
}
#message {
display: none;
margin-top: 1rem;
}
+15
View File
@@ -0,0 +1,15 @@
//to try in real moodle env
for (const option of document.querySelectorAll("option")) {
option.selected = false;
}
for (const input of document.querySelectorAll(
'input[type="radio"], input[type="checkbox"]'
)) {
input.checked = false;
}
for (const icon of document.querySelectorAll(".text-danger, .text-success")) {
icon.remove();
}
+67
View File
@@ -0,0 +1,67 @@
@font-face {
font-family: Segeo UI;
src: url(../../extension/fonts/Segoe\ UI.ttf);
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI";
}
body {
display: flex;
align-items: center;
flex-direction: column;
padding: 1rem;
gap: 1rem;
}
h1 {
text-align: center;
}
.formulation {
width: 60%;
background-color: #e7f3f5;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.inp {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
align-items: center;
position: relative;
}
select {
position: absolute;
right: 0;
}
.editable {
background-color: #fff;
height: 10rem;
width: 100%;
resize: vertical;
overflow-y: auto;
white-space: pre-wrap;
padding: 0.5rem;
outline: none;
border: thin solid #000;
}
textarea {
outline: none;
padding: 0.5rem;
height: 10rem;
width: 100%;
resize: vertical;
white-space: pre-wrap;
overflow-y: auto;
}
+246
View File
@@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Moodle test</title>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<!-- checkbox -->
<section class="formulation">
<div class="qtext">
<p>Which words are animals ?</p>
</div>
<div>
<div class="inp">
<input type="checkbox" />
<label>a. Cat</label>
</div>
<div class="inp">
<input type="checkbox" />
<label>b. Dog</label>
</div>
<div class="inp">
<input type="checkbox" />
<label>c. Computer</label>
</div>
</div>
</section>
<!-- Radio -->
<section class="formulation">
<div class="qtext">
<p>What is the french president name ?</p>
</div>
<div>
<div class="inp">
<input type="radio" />
<label>Emmanuel Macron</label>
</div>
<div class="inp">
<input type="radio" />
<label>Jean Macron</label>
</div>
<div class="inp">
<input type="radio" />
<label>Yves Macron</label>
</div>
</div>
</section>
<!-- True or false -->
<section class="formulation">
<div class="qtext">
<p>The cat sometimes drink milk?</p>
</div>
<div>
<div class="inp">
<input type="radio" />
<label>True</label>
</div>
<div class="inp">
<input type="radio" />
<label>False</label>
</div>
</div>
</section>
<!-- Select -->
<section class="formulation">
<div class="qtext">
<p>Choose the correct answer</p>
</div>
<div>
<div class="inp">
<table>
<tr>
<td class="text">
<p>I am a feline</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>Cat</option>
<option>Dog</option>
<option>Cow</option>
</select>
</td>
</tr>
<tr>
<td class="text">
<p>I am a descendant of the wolf</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>Cat</option>
<option>Dog</option>
<option>Cow</option>
</select>
</td>
</tr>
<tr>
<td class="text"><p>I produce milk</p></td>
<td>
<select>
<option>Choose...</option>
<option>Cat</option>
<option>Dog</option>
<option>Cow</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</section>
<!-- Select Number -->
<section class="formulation">
<div class="qtext">
<p>Put the following tags in the order of creation of a html file:</p>
</div>
<div>
<div class="inp">
<table>
<tr>
<td class="text">
<p>html</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</td>
</tr>
<tr>
<td class="text">
<p>body</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</td>
</tr>
<tr>
<td class="text"><p>head</p></td>
<td>
<select>
<option>Choose...</option>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</section>
<!-- Select Calc -->
<section class="formulation">
<div class="qtext">
<p>Solve those equations:</p>
</div>
<div>
<div class="inp">
<table>
<tr>
<td class="text">
<p>5*5</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>10</option>
<option>20</option>
<option>25</option>
</select>
</td>
</tr>
<tr>
<td class="text">
<p>20 - 10</p>
</td>
<td>
<select>
<option>Choose...</option>
<option>10</option>
<option>20</option>
<option>25</option>
</select>
</td>
</tr>
<tr>
<td class="text"><p>10+10</p></td>
<td>
<select>
<option>Choose...</option>
<option>10</option>
<option>20</option>
<option>25</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</section>
<!-- Text -->
<section class="formulation">
<div class="qtext">
<p>Give me five diferences between a dog and a cat</p>
</div>
<div>
<textarea></textarea>
</div>
</section>
<!-- Clipboard -->
<section class="formulation">
<div class="qtext">
<p>
Gives a "reverseWorld" function in javascript which takes as a
parameter a word and flips it in the opposite direction
</p>
</div>
<div>
<div contenteditable="true" class="editable"></div>
</div>
</section>
</body>
</html>