Educake ChatGPT Auto-Integration

Automatically sends Educake questions to ChatGPT API and displays responses inline (make sure to add in apikey)

  1. // ==UserScript==
  2. // @name Educake ChatGPT Auto-Integration
  3. // @version 2.1
  4. // @description Automatically sends Educake questions to ChatGPT API and displays responses inline (make sure to add in apikey)
  5. // @author frozled @ guns.lol/frozled
  6. // @match *://*.educake.co.uk/*
  7. // @grant GM_xmlhttpRequest
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @connect api.openai.com
  11. // @license MIT
  12. // @namespace https://greasyfork.org/users/1390797
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Configuration object
  19. const config = {
  20. apiKey: '', // Store your API key here or use GM_getValue to get it from storage
  21. model: 'gpt-3.5-turbo',
  22. apiEndpoint: 'https://api.openai.com/v1/chat/completions'
  23. };
  24.  
  25. // Styles for the UI elements
  26. const styles = `
  27. .gpt-response {
  28. margin-top: 10px;
  29. padding: 10px;
  30. border: 1px solid #e0e0e0;
  31. border-radius: 4px;
  32. background-color: #f8f9fa;
  33. }
  34. .gpt-loading {
  35. color: #666;
  36. font-style: italic;
  37. }
  38. .api-key-modal {
  39. position: fixed;
  40. top: 50%;
  41. left: 50%;
  42. transform: translate(-50%, -50%);
  43. background: white;
  44. padding: 20px;
  45. border-radius: 8px;
  46. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  47. z-index: 1000;
  48. width: 400px;
  49. }
  50. .modal-backdrop {
  51. position: fixed;
  52. top: 0;
  53. left: 0;
  54. width: 100%;
  55. height: 100%;
  56. background: rgba(0,0,0,0.5);
  57. z-index: 999;
  58. }
  59. .error-message {
  60. color: #dc3545;
  61. margin-top: 10px;
  62. padding: 10px;
  63. border: 1px solid #dc3545;
  64. border-radius: 4px;
  65. background-color: #fff;
  66. }
  67. .debug-info {
  68. margin-top: 5px;
  69. font-size: 12px;
  70. color: #666;
  71. font-family: monospace;
  72. white-space: pre-wrap;
  73. }
  74. `;
  75.  
  76. // Add styles to document
  77. function addStyles() {
  78. const styleSheet = document.createElement('style');
  79. styleSheet.textContent = styles;
  80. document.head.appendChild(styleSheet);
  81. }
  82.  
  83. // Create and show API key modal
  84. function showApiKeyModal() {
  85. const modalHtml = `
  86. <div class="modal-backdrop">
  87. <div class="api-key-modal">
  88. <h3>Enter your OpenAI API Key</h3>
  89. <p>You need to enter your OpenAI API key to use this feature. You can get one from <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI's website</a>.</p>
  90. <input type="password" id="api-key-input" placeholder="sk-..." style="width: 100%; margin: 10px 0; padding: 5px;">
  91. <button id="save-api-key" style="padding: 5px 10px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">Save Key</button>
  92. <div id="api-key-error" style="color: red; margin-top: 10px;"></div>
  93. </div>
  94. </div>
  95. `;
  96.  
  97. const modalContainer = document.createElement('div');
  98. modalContainer.innerHTML = modalHtml;
  99. document.body.appendChild(modalContainer);
  100.  
  101. document.getElementById('save-api-key').addEventListener('click', () => {
  102. const apiKey = document.getElementById('api-key-input').value.trim();
  103. if (apiKey.startsWith('sk-') && apiKey.length > 20) {
  104. GM_setValue('openai_api_key', apiKey);
  105. config.apiKey = apiKey;
  106. modalContainer.remove();
  107. } else {
  108. document.getElementById('api-key-error').textContent = 'Please enter a valid OpenAI API key (should start with sk-)';
  109. }
  110. });
  111. }
  112.  
  113. // Function to get response from ChatGPT
  114. async function getGPTResponse(question) {
  115. if (!config.apiKey) {
  116. config.apiKey = GM_getValue('openai_api_key', '');
  117. if (!config.apiKey) {
  118. showApiKeyModal();
  119. return null;
  120. }
  121. }
  122.  
  123. return new Promise((resolve, reject) => {
  124. const requestData = {
  125. model: config.model,
  126. messages: [{
  127. role: 'user',
  128. content: question
  129. }],
  130. temperature: 0.7
  131. };
  132.  
  133. GM_xmlhttpRequest({
  134. method: 'POST',
  135. url: config.apiEndpoint,
  136. headers: {
  137. 'Content-Type': 'application/json',
  138. 'Authorization': `Bearer ${config.apiKey}`
  139. },
  140. data: JSON.stringify(requestData),
  141. onload: function(response) {
  142. try {
  143. const responseData = JSON.parse(response.responseText);
  144. console.log('API Response:', responseData); // Debug log
  145.  
  146. if (response.status === 200 && responseData.choices && responseData.choices[0]) {
  147. resolve(responseData.choices[0].message.content);
  148. } else {
  149. const errorMessage = responseData.error ? responseData.error.message : 'Unknown error';
  150. reject(new Error(`API Error (${response.status}): ${errorMessage}`));
  151. }
  152. } catch (error) {
  153. console.error('Response parsing error:', error);
  154. reject(new Error(`Failed to parse API response: ${error.message}`));
  155. }
  156. },
  157. onerror: function(error) {
  158. console.error('Request error:', error);
  159. reject(new Error(`Network error: ${error.statusText || 'Failed to connect to API'}`));
  160. }
  161. });
  162. });
  163. }
  164.  
  165. // Function to create and inject the GPT button
  166. function injectGPTButton() {
  167. if (!document.getElementById('gpt-button')) {
  168. const button = document.createElement('div');
  169. button.id = 'gpt-button';
  170. button.className = 'btn bg-green-80 bg-green-hover r-bg-light r-bg-light-hover r-text-dark ml-2 lh-close mb-2 mb-sm-0 align-self-start';
  171. button.textContent = 'Ask ChatGPT';
  172.  
  173. button.addEventListener('click', async function() {
  174. const questionElements = document.querySelectorAll('.question-text');
  175. const question = Array.from(questionElements).map(el => el.textContent).join('\n');
  176.  
  177. // Create response container
  178. let responseContainer = document.querySelector('.gpt-response');
  179. if (!responseContainer) {
  180. responseContainer = document.createElement('div');
  181. responseContainer.className = 'gpt-response';
  182. button.parentElement.appendChild(responseContainer);
  183. }
  184.  
  185. responseContainer.innerHTML = '<div class="gpt-loading">Getting response from ChatGPT...</div>';
  186.  
  187. try {
  188. const response = await getGPTResponse(question);
  189. if (response) {
  190. responseContainer.innerHTML = `
  191. <strong>ChatGPT Response:</strong>
  192. <div style="margin-top: 8px;">${response.replace(/\n/g, '<br>')}</div>
  193. `;
  194. }
  195. } catch (error) {
  196. responseContainer.innerHTML = `
  197. <div class="error-message">
  198. Error: ${error.message}
  199. <div class="debug-info">
  200. Status: ${error.status || 'N/A'}
  201. Time: ${new Date().toISOString()}
  202. </div>
  203. </div>
  204. `;
  205.  
  206. // If the error is related to authentication, show the API key modal
  207. if (error.message.includes('401') || error.message.includes('authentication')) {
  208. config.apiKey = ''; // Clear the invalid API key
  209. GM_setValue('openai_api_key', ''); // Clear stored key
  210. showApiKeyModal();
  211. }
  212. }
  213. });
  214.  
  215. const existingDiv = document.querySelector('.column');
  216. existingDiv.insertBefore(button, existingDiv.lastElementChild);
  217. }
  218. }
  219.  
  220. // Remove paste restrictions
  221. function removePasteRestrictions() {
  222. const elements = document.querySelectorAll('.answer-text');
  223. elements.forEach(element => {
  224. element.removeAttribute('onpaste');
  225. });
  226. }
  227.  
  228. // Initialize
  229. function init() {
  230. addStyles();
  231. setInterval(injectGPTButton, 500);
  232. setInterval(removePasteRestrictions, 500);
  233. }
  234.  
  235. // Start the script
  236. init();
  237. })();