Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98728b52e7 | |||
| 78942c20dd | |||
| 5fe25d0b34 | |||
| e7a6e07856 | |||
| 0458137f0c | |||
| 7af54c40ad | |||
| 8292ae42a4 |
@@ -1,5 +1,9 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.2
|
||||
|
||||
- Added `mode`
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- Removed langage
|
||||
|
||||
@@ -2,14 +2,48 @@
|
||||
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>
|
||||
|
||||
# MoodleGPT v1.0.1
|
||||
# MoodleGPT v1.0.2
|
||||
|
||||
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.
|
||||
|
||||
## Webstore
|
||||
|
||||
Find it on the chrome webstore "MoodleGPT"
|
||||
|
||||
## Summary
|
||||
|
||||
- [MoodleGPT v1.0.2](#moodlegpt-v102)
|
||||
- [Webstore](#webstore)
|
||||
- [Summary](#summary)
|
||||
- [Disclaimer !](#disclaimer-)
|
||||
- [Support](#support)
|
||||
- [Update](#update)
|
||||
- [MoodleGPT don't complete my quiz ?](#moodlegpt-dont-complete-my-quiz-)
|
||||
- [Set up](#set-up)
|
||||
- [Inject the code into the moodle](#inject-the-code-into-the-moodle)
|
||||
- [Remove injection](#remove-injection)
|
||||
- [Mode](#mode)
|
||||
- [Settings](#settings)
|
||||
- [Supported questions type](#supported-questions-type)
|
||||
- [Select](#select)
|
||||
- [Put in order question](#put-in-order-question)
|
||||
- [Resolve equation](#resolve-equation)
|
||||
- [One response (radio button)](#one-response-radio-button)
|
||||
- [Multiples responses (checkbox)](#multiples-responses-checkbox)
|
||||
- [True or false](#true-or-false)
|
||||
- [Number](#number)
|
||||
- [Text](#text)
|
||||
- [What about if the question can't be completed ?](#what-about-if-the-question-cant-be-completed-)
|
||||
- [Test](#test)
|
||||
|
||||
## 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.
|
||||
|
||||
## Support
|
||||
|
||||
Will be a pleasure if you want to supprot this project :) -> Just right [here](https://www.buymeacoffee.com/yoannchbpro)
|
||||
|
||||
## Update
|
||||
|
||||
See [changelog](./CHANGELOG.md)
|
||||
@@ -22,6 +56,10 @@ If MoodleGPT cannot complete one of your moodle quiz please provide the html cod
|
||||
|
||||
> NOTE: This extension only works on Chromium-based browsers like Edge, Chrome, etc. Unfortunately, Firefox requires a click on the extension, which is not very discreet.
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/setup.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
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 icon and enter the apiKey obtained from [openai](https://platform.openai.com/) and enter a <b>code</b> that will activate the extension on your moodle page. Finally, click on the <b>reload button</b> next to model (it should give you the last ChatGPT version, otherwise enter it by your self) and click on the save button (The extension need to be configured before entering the moodle quiz).
|
||||
|
||||
## Inject the code into the moodle
|
||||
@@ -32,10 +70,21 @@ You just need to enter on the keyboard the <b>code</b> you have set into the ext
|
||||
|
||||
Type back the <b>code</b> on the keyboard and the code will be removed from the current page.
|
||||
|
||||
## Options
|
||||
## Mode
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/popup.png" alt="Popup" height="300">
|
||||
<img src="./assets/mode.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
- <b>Autocomplete:</b> The extension will complete the question for you.
|
||||
- <b>Clipboard:</b> The response is copied into the clipboard.
|
||||
- <b>Question to answer:</b> The question is converted to the answer and you can click on it to show back the question (or show back the answer).
|
||||
<br/><img src="./assets/question-to-answer.gif" alt="Question to Answer">
|
||||
|
||||
## Settings
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/settings.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
- <b>Api key</b>: the openai api key.
|
||||
@@ -97,7 +146,7 @@ Type back the <b>code</b> on the keyboard and the code will be removed from the
|
||||
|
||||

|
||||
|
||||
## If it can't complete the question, the answer will be copied to your clipboard
|
||||
## What about if the question can't be completed ?
|
||||
|
||||
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 3 seconds.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -61,8 +61,8 @@
|
||||
*/
|
||||
function normalizeText(text) {
|
||||
return text
|
||||
.replace(/\n+/g, "\n")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.replace(/(\n\s*)+/gi, "\n")
|
||||
.replace(/[ \t]+/gi, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
|
||||
@@ -382,6 +382,21 @@
|
||||
Logs.question(question);
|
||||
Logs.response(response);
|
||||
}
|
||||
if (config.mode === "clipboard") {
|
||||
return handleClipboard(config, response);
|
||||
}
|
||||
if (config.mode === "question-to-answer") {
|
||||
const questionBackup = form.textContent;
|
||||
const questionContainer = form.querySelector(".qtext");
|
||||
questionContainer.textContent = response;
|
||||
questionContainer.addEventListener("click", function () {
|
||||
questionContainer.textContent =
|
||||
questionContainer.textContent === questionBackup
|
||||
? response
|
||||
: questionBackup;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handlers = [
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
@@ -393,6 +408,7 @@
|
||||
if (handler(config, inputList, response))
|
||||
return;
|
||||
}
|
||||
/** In the case we can't auto complete the question */
|
||||
handleClipboard(config, response);
|
||||
});
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 166 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "MoodleGPT",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "Hidden chat-gpt for your moodle quiz",
|
||||
"permissions": ["activeTab", "tabs", "storage"],
|
||||
"action": {
|
||||
@@ -18,13 +18,7 @@
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"*://*/**/mod/quiz/*",
|
||||
"*://*/mod/quiz/*",
|
||||
"http://localhost:*/*",
|
||||
"http://127.0.0.1:*/*",
|
||||
"file:///*"
|
||||
],
|
||||
"matches": ["*://*/**/mod/quiz/*", "*://*/mod/quiz/*", "file:///*"],
|
||||
"js": ["MoodleGPT.js"],
|
||||
"run_at": "document_end"
|
||||
}
|
||||
|
||||
@@ -48,7 +48,28 @@
|
||||
title="Provide an api key first"
|
||||
></i>
|
||||
</div>
|
||||
<div class="line center" style="margin-top: 1rem">
|
||||
<div class="line mt">
|
||||
<i class="fa-solid fa-robot"></i>
|
||||
<p>Mode:</p>
|
||||
</div>
|
||||
<div class="line">
|
||||
<ul id="mode" class="line center">
|
||||
<li><button value="autocomplete">autocomplete</button></li>
|
||||
<li>
|
||||
<button value="clipboard" class="not-selected">clipboard</button>
|
||||
</li>
|
||||
<li>
|
||||
<button value="question-to-answer" class="not-selected">
|
||||
question to answer
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="line mt">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<p>Settings:</p>
|
||||
</div>
|
||||
<div class="line center">
|
||||
<div class="col">
|
||||
<div class="line">
|
||||
<input id="typing" type="checkbox" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const saveBtn = document.querySelector(".save");
|
||||
const message = document.querySelector("#message");
|
||||
|
||||
/* inputs id */
|
||||
const inputsText = ["apiKey", "code", "model"];
|
||||
const inputsCheckbox = [
|
||||
"logs",
|
||||
@@ -13,6 +14,21 @@ const inputsCheckbox = [
|
||||
"timeout",
|
||||
];
|
||||
|
||||
const mode = document.querySelector("#mode");
|
||||
const modes = mode.querySelectorAll("button");
|
||||
let actualMode = "autocomplete";
|
||||
/* inputs id that need to be disabled for a specific mode */
|
||||
const disabledForThisMode = {
|
||||
autocomplete: [],
|
||||
clipboard: ["typing", "mouseover"],
|
||||
"question-to-answer": ["typing", "infinite", "mouseover"],
|
||||
};
|
||||
|
||||
/**
|
||||
* Show message into the popup
|
||||
* @param {string} messageTxt
|
||||
* @param {boolean} valide
|
||||
*/
|
||||
function showMessage(messageTxt, valide) {
|
||||
message.style.color = valide ? "limegreen" : "red";
|
||||
message.textContent = messageTxt;
|
||||
@@ -20,15 +36,48 @@ function showMessage(messageTxt, valide) {
|
||||
setTimeout(() => (message.style.display = "none"), 5000);
|
||||
}
|
||||
|
||||
//save the configuration
|
||||
/**
|
||||
* Handle when a mode change to show specific input
|
||||
*/
|
||||
function handleModeChange() {
|
||||
const needDisable = disabledForThisMode[actualMode];
|
||||
const dontNeedDisable = inputsCheckbox.filter(
|
||||
(input) => !needDisable.includes(input)
|
||||
);
|
||||
for (const id of needDisable) {
|
||||
document.querySelector("#" + id).parentElement.style.display = "none";
|
||||
}
|
||||
for (const id of dontNeedDisable) {
|
||||
document.querySelector("#" + id).parentElement.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mode handler */
|
||||
modes.forEach((button) => {
|
||||
button.addEventListener("click", function () {
|
||||
const value = button.value;
|
||||
actualMode = value;
|
||||
for (const mode of modes) {
|
||||
if (mode.value !== value) {
|
||||
mode.classList.add("not-selected");
|
||||
} else {
|
||||
mode.classList.remove("not-selected");
|
||||
}
|
||||
}
|
||||
handleModeChange();
|
||||
});
|
||||
});
|
||||
|
||||
/* Save the configuration */
|
||||
saveBtn.addEventListener("click", function () {
|
||||
const [apiKey, code, model] = inputsText.map((selector) =>
|
||||
document.querySelector("#" + selector).value.trim()
|
||||
);
|
||||
const [logs, title, cursor, typing, mouseover, infinite, table, timeout] =
|
||||
inputsCheckbox.map(
|
||||
(selector) => document.querySelector("#" + selector).checked
|
||||
);
|
||||
inputsCheckbox.map((selector) => {
|
||||
const element = document.querySelector("#" + selector);
|
||||
return element.checked && element.parentElement.style.display !== "none";
|
||||
});
|
||||
|
||||
if (!apiKey || !code || !model) {
|
||||
showMessage("Please complete all the form");
|
||||
@@ -53,31 +102,16 @@ saveBtn.addEventListener("click", function () {
|
||||
infinite,
|
||||
table,
|
||||
timeout,
|
||||
mode: actualMode,
|
||||
},
|
||||
});
|
||||
|
||||
showMessage("Configuration saved", true);
|
||||
});
|
||||
|
||||
//we load back the configuration
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
const config = storage.moodleGPT;
|
||||
|
||||
if (config) {
|
||||
inputsText.forEach((key) =>
|
||||
config[key]
|
||||
? (document.querySelector("#" + key).value = config[key])
|
||||
: null
|
||||
);
|
||||
inputsCheckbox.forEach(
|
||||
(key) => (document.querySelector("#" + key).checked = config[key] || "")
|
||||
);
|
||||
}
|
||||
|
||||
getLastChatGPTVersion();
|
||||
});
|
||||
|
||||
//getting the last chatgpt version
|
||||
/**
|
||||
* Get the last ChatGPT version
|
||||
*/
|
||||
function getLastChatGPTVersion() {
|
||||
const apiKeySelector = document.querySelector("#apiKey");
|
||||
const reloadModel = document.querySelector("#reloadModel");
|
||||
@@ -119,3 +153,33 @@ function getLastChatGPTVersion() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* we load back the configuration */
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
const config = storage.moodleGPT;
|
||||
|
||||
if (config) {
|
||||
if (config.mode) {
|
||||
actualMode = config.mode;
|
||||
for (const mode of modes) {
|
||||
if (mode.value === config.mode) {
|
||||
mode.classList.remove("not-selected");
|
||||
} else {
|
||||
mode.classList.add("not-selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputsText.forEach((key) =>
|
||||
config[key]
|
||||
? (document.querySelector("#" + key).value = config[key])
|
||||
: null
|
||||
);
|
||||
inputsCheckbox.forEach(
|
||||
(key) => (document.querySelector("#" + key).checked = config[key] || "")
|
||||
);
|
||||
}
|
||||
|
||||
handleModeChange();
|
||||
getLastChatGPTVersion();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const currentVersion = "1.0.1";
|
||||
const currentVersion = "1.0.2";
|
||||
const versionDisplay = document.querySelector("#version");
|
||||
|
||||
/**
|
||||
* Get the last version from the github
|
||||
* @returns
|
||||
*/
|
||||
async function getLastVersion() {
|
||||
const req = await fetch(
|
||||
"https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json"
|
||||
@@ -9,6 +13,12 @@ async function getLastVersion() {
|
||||
return rep.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the version or an update message
|
||||
* @param {string} version
|
||||
* @param {boolean} isCurrent
|
||||
* @returns
|
||||
*/
|
||||
function setVersion(version, isCurrent = true) {
|
||||
if (isCurrent) {
|
||||
versionDisplay.textContent = "v" + version;
|
||||
@@ -24,6 +34,9 @@ function setVersion(version, isCurrent = true) {
|
||||
versionDisplay.appendChild(document.createTextNode(" is now available !"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the extension neeed an update or no
|
||||
*/
|
||||
async function notifyUpdate() {
|
||||
const lastVersion = await getLastVersion().catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
@@ -52,6 +52,7 @@ a {
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -98,6 +99,34 @@ a {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mt {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.not-selected {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
#mode li {
|
||||
list-style: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#mode {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#mode button {
|
||||
background-color: var(--btn-color);
|
||||
border: none;
|
||||
text-align: center;
|
||||
padding: 0.3rem 0.75rem;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#version {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moodlegpt",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
|
||||
@@ -16,3 +16,7 @@ for (const input of document.querySelectorAll(
|
||||
for (const icon of document.querySelectorAll(".text-danger, .text-success")) {
|
||||
icon.remove();
|
||||
}
|
||||
|
||||
for (const feedback of document.querySelectorAll(".specificfeedback")) {
|
||||
feedback.remove();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,23 @@ async function reply(
|
||||
Logs.response(response);
|
||||
}
|
||||
|
||||
if (config.mode === "clipboard") {
|
||||
return handleClipboard(config, response);
|
||||
}
|
||||
|
||||
if (config.mode === "question-to-answer") {
|
||||
const questionBackup = form.textContent;
|
||||
const questionContainer = form.querySelector(".qtext");
|
||||
questionContainer.textContent = response;
|
||||
questionContainer.addEventListener("click", function () {
|
||||
questionContainer.textContent =
|
||||
questionContainer.textContent === questionBackup
|
||||
? response
|
||||
: questionBackup;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const handlers = [
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
@@ -61,6 +78,7 @@ async function reply(
|
||||
if (handler(config, inputList, response)) return;
|
||||
}
|
||||
|
||||
/** In the case we can't auto complete the question */
|
||||
handleClipboard(config, response);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ type Config = {
|
||||
title?: boolean;
|
||||
table?: boolean;
|
||||
timeout?: boolean;
|
||||
mode?: "autocomplete" | "question-to-answer" | "clipboard";
|
||||
};
|
||||
|
||||
export default Config;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
function normalizeText(text: string) {
|
||||
return text
|
||||
.replace(/\n+/g, "\n")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.replace(/(\n\s*)+/gi, "\n")
|
||||
.replace(/[ \t]+/gi, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
|
||||
|
||||