Omni Tools

;)

  1. // ==UserScript==
  2. // @name Omni Tools
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4.1
  5. // @description ;)
  6. // @author You
  7. // @match https://omni.top-academy.ru/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=top-academy.ru
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. var Version = "1.4.1";
  13. var CurrentHomeworks=null;
  14. var CanOpenImage=true;
  15. var UpdateFounded=false;
  16. var TeacherLogin = null;
  17. let FetchesCount = 0;
  18. let URLWaitingList = [];
  19.  
  20. function FeedbackAi(){
  21. //Список моделей на сайте
  22. let AINameList = [
  23. 'OpenChat 3.5 (Recommended)',
  24. 'SnowFlake (Recommended)',
  25. 'Mistral 7B',
  26. 'Llama 3 8B',
  27. 'RWKV v5 3B AI Town',
  28. 'Capybara 7B',
  29. 'Gemma 7B',
  30. 'MythoMist 7B']
  31. //Список используемых нейросетей
  32. let AIProvidesList = [
  33. 'openchat/openchat-7b:free',
  34. 'snowflake/snowflake-arctic-instruct',
  35. 'mistralai/mistral-7b-instruct:free',
  36. 'meta-llama/llama-3-8b-instruct:free',
  37. 'recursal/rwkv-5-3b-ai-town',
  38. 'nousresearch/nous-capybara-7b:free',
  39. 'google/gemma-7b-it:free',
  40. 'gryphe/mythomist-7b:free']
  41.  
  42. //Функция отправки запроса с ожиданием ответа
  43. function sendRequest(method, url, senddata) {
  44. try{
  45. return new Promise((resolve, reject) => {
  46. fetch("https://openrouter.ai/api/v1/chat/completions", {
  47. method: "POST",
  48. headers: {
  49. "Authorization": `Bearer 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`,
  50. "HTTP-Referer": `https://docs.google.com/`,
  51. "Content-Type": "application/json"
  52. },
  53. body: JSON.stringify({
  54. "model": senddata.ai_model,
  55. "messages": [
  56. {"role": "user", "content": senddata.promt},
  57. ],
  58. })
  59. })
  60. .then(response => {
  61. if (!response.ok) {
  62. throw new Error(`HTTP error! status: ${response.status}`);
  63. }
  64. return response.json();
  65. })
  66. .then(data => resolve(data["choices"][0]["message"]["content"]))
  67. .catch(error => reject(error));
  68. });
  69. } catch(e){alert(e)}
  70. }
  71.  
  72.  
  73.  
  74. let feedbackareas = document.querySelectorAll('textarea.textarea-rev')//Поиск всех textarea для подстановки текста из ИИ
  75. let stundentinfodiv = document.querySelectorAll('.col-md-12 .col-md-6:nth-child(1)')//Поиск div с описанием студента
  76.  
  77. while (document.getElementById('AIButton') !== null){document.getElementById('AIButton').remove()}//Это очищает все созданные кнопки
  78. while (document.getElementById('AIPromt') !== null){document.getElementById('AIPromt').remove()}//Это очищает все созданные поля промтов
  79.  
  80. function PromtPrepare(buttonPosition){
  81. let informationdiv = stundentinfodiv[buttonPosition]//Получение div под номером buttonPosition из списка
  82. let information = informationdiv.querySelectorAll('p')//Получение данных об ученике
  83.  
  84. let Grade = Math.round(Number(information[4].textContent.replace('Успеваемость: ',''))/120*1000)//Конвертация успреваемости в проценты (я знаю что этот расчёт просто имба)
  85. if (Grade !== NaN && Grade !== null){
  86. let PromtText = 'Привет! Я преподаватель в колледже и мне нужно оставить простой и краткий отзыв для ученика, желательно, не более 2 предложений. Прошу тебя помочь с написанием отзыва. Ниже приведена небольшая информация об ученике. Полное имя ученика: '+information[5].textContent.replace('ФИО: ','')+', средняя посещаемость ученика: '+information[3].textContent.replace('Посещаемость: ','')+', средняя успеваемость:'+Grade+'%. (Предмет:'+(information[6].textContent.replace('Предмет:',''))+'). Будет замечательно если ты допустишь как можно меньше грамматических ошибок';
  87. return PromtText
  88. } else {
  89. return false
  90. }
  91. }
  92.  
  93.  
  94.  
  95.  
  96. function AskAI(i){
  97. let pressedButton = document.querySelector('.AIButtonAsk'+i)//Поиск нажатой кнопки
  98. let informationdiv = stundentinfodiv[i]//Поиск требуемого div из списка
  99. let promt = informationdiv.querySelector('textarea').value//Получение textarea из полученного div и его значения
  100. let choiced_model_number = AINameList.indexOf(document.getElementById('AISelection').value)//Получение выбранной модели
  101. pressedButton.textContent = 'Генерация отзыва...'
  102. pressedButton.disabled='true'
  103. sendRequest('POST', 'https://omnixtended.loca.lt/askAi', {promt: promt, ai_model:AIProvidesList[choiced_model_number]}).then(res => {
  104. feedbackareas[i].value = res;
  105. pressedButton.textContent = 'Сгенерировать заново'
  106. pressedButton.style.border='none'
  107. pressedButton.disabled = false;
  108. }).catch(err=> {
  109. console.error(err)
  110. pressedButton.textContent = 'Произошла ошибка :(';
  111. pressedButton.style.border='solid 2px red'
  112. pressedButton.disabled = false;
  113. })
  114. }
  115.  
  116. //Для каждого ученика создаётся своя кнопка и поле с промптом
  117. for (let i = 0; i<feedbackareas.length; i++){
  118. //Создание кнопки с вызовом генерации
  119. let AIButton = document.createElement('button')
  120. AIButton.id='AIButton'
  121. AIButton.textContent='Генерация отзыва с помозью AI'
  122. AIButton.style='margin: 10px 0px 0px 0px; width: 100%'
  123. AIButton.className= 'waves-effect waves-light btn md-button md-ink-ripple AIButtonAsk'+i
  124. AIButton.addEventListener('click', function() {
  125. AskAI(i)
  126. })
  127.  
  128.  
  129. //Создание поля с промптом
  130. let AIPromt = document.createElement('textarea')
  131. AIPromt.id="AIPromt"
  132. AIPromt.style="width: 100%; height: 60px; border-radius: 10px; font-size: smaller"
  133. AIPromt.placeholder="Описание студента для нейросети";
  134.  
  135. //Заспавнить кнопку после поля ввода
  136. feedbackareas[i].parentElement.appendChild(AIButton)//Можно использовать parentElement.appendChild вместо after
  137. //Заспавнить промпт-поле после информации об ученике
  138. stundentinfodiv[i].appendChild(AIPromt)
  139. let finalPromt = PromtPrepare(i)//Создание и вставка заготовленного промта для этого ученика
  140. AIPromt.value = finalPromt
  141. }
  142.  
  143. if(document.getElementById('AISelection') !== null) {document.getElementById('AISelection').remove()}//Сброс выбора поля модельки нейросети при перезапуске
  144. //Создание выпадающего меню
  145. let AISelection = document.createElement('select')
  146. AISelection.id='AISelection'
  147. AISelection.style='margin: 10px; padding: 4px; border-radius: 5px;'
  148. let selectionInner='';
  149. for (let h = 0; h<AINameList.length; h++){
  150. //Добавлениее списка доступных нейронок в выпадающее меню
  151. selectionInner+='<option>'+AINameList[h]+'</option>'
  152. }
  153. //Применение
  154. AISelection.innerHTML=selectionInner;
  155. document.querySelector('span.reviews-container').before(AISelection)
  156.  
  157. //Создание кнопки перезапуска (нужна была когда я начинал делать этот код, но сейчас просто фишка)
  158. if (document.getElementById('AIReload') !== null){document.getElementById('AIReload').remove()}
  159. let AIReload = document.createElement('button')
  160. AIReload.id="AIReload";
  161. AIReload.className = 'waves-effect waves-light btn md-button md-ink-ripple'
  162. AIReload.style='position: absolute; top: 0px; width: fit-content; right: 10px'
  163. AIReload.textContent = 'Перезапустить AITools';
  164. AIReload.addEventListener('click', function() {
  165. FeedbackAi()//Вызвать эту функцию заново
  166. })
  167. document.querySelector('span.reviews-container').appendChild(AIReload)
  168. }
  169. function checkFeedbackOpened(){
  170. if(document.querySelector('md-dialog.reviews-modal.reviews-modal-comments.layout-padding') !== null && document.getElementById('AIButton') === null){
  171. setTimeout(FeedbackAi, 1000);
  172. }
  173. }
  174.  
  175.  
  176.  
  177.  
  178. function SendPacket(URL, Type, JSONVals){
  179. return new Promise((resolve, reject) => {
  180.  
  181. const xhr = new XMLHttpRequest();
  182. xhr.open(Type, URL);
  183. xhr.setRequestHeader('authority', 'msapi.top-academy.ru');
  184. xhr.setRequestHeader('method', 'POST');
  185. xhr.setRequestHeader('path', '/api/v2/auth/login');
  186. xhr.setRequestHeader('scheme', 'https');
  187. xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
  188. xhr.setRequestHeader('Accept-Language', 'ru_RU, ru');
  189. xhr.setRequestHeader('UIRequestData', TeacherLogin);
  190.  
  191. xhr.onreadystatechange = () => {
  192. if (xhr.readyState === XMLHttpRequest.DONE) {
  193. if (xhr.status >= 200 && xhr.status < 300) {
  194. resolve(xhr.responseText);
  195. } else {
  196. reject(xhr.statusText);
  197. }
  198. }
  199. };
  200. xhr.onerror = () => reject(xhr.statusText);
  201.  
  202. if (URL.indexOf("teacherTools") >= 0) {FetchesCount += 1;}
  203. if (JSONVals!==null) {
  204. xhr.setRequestHeader('Content-Type', 'application/json');
  205.  
  206. let requestBody = JSONVals;
  207. if (typeof JSONVals === 'string') {
  208. requestBody = JSON.parse(JSONVals);
  209. }
  210. xhr.send(JSON.stringify(requestBody));
  211. } else {
  212. xhr.send();
  213. }
  214.  
  215. });
  216.  
  217. }
  218. window.SendPacket = SendPacket;
  219.  
  220. function CreateFullscreenViewAPI(){
  221. var FullscreenView = document.createElement('div');
  222. FullscreenView.id="FillScreenViewer"
  223. FullscreenView.innerHTML=`
  224. <style>
  225. img#FullscreenImg { max-width: 100%; max-height: 100%; height: 80% !important; object-fit: cover; transition: all 1s;transform: translate(-50%, -50%);left: 50%;top: 50%;position: relative;height: auto;border-radius: 20px;z-index: 9000;display: block;-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}
  226. img#FullscreenImg:hover { height: 97% !important; }
  227. .imgActiveImage{ transition: all 1s; border-radius: 20px; width: 100%; max-height: 100px; object-fit: cover; cursor: pointer; }
  228. .imgActiveImage:hover{ max-height: 150px; }
  229. div#FullscreenView {width: 100%; height: 0%; background: #252525de; position: absolute; transition: all .6s; top: 0px; z-index: 102; display: none; }
  230. </style>
  231. <div id="FullscreenView" onClick="window.CloseImageOnFullscreen()">
  232. <img id="FullscreenImg">
  233.  
  234. </div>`;
  235. document.querySelector("body").after(FullscreenView)
  236. }
  237.  
  238. function IsHomeWorksOpened(){
  239. return document.querySelectorAll("#myDialog.home_work_modal md-dialog").length > 0 // Применить скрипт если окно открылось
  240. }
  241.  
  242. function CreateStyleIfNotExists(name, content) {
  243. if (document.getElementById(name) === null) {
  244. let style = document.createElement('style')
  245. style.textContent = content;
  246. style.id = name;
  247. document.body.appendChild(style)
  248. }
  249. }
  250.  
  251.  
  252. function DisplayRender(res, urlToHomework, placement) {
  253. CreateStyleIfNotExists('hwPreview', `
  254. #myDialog.home_work_modal .hw-md_item {width: 50%; position: relative}
  255. .hwPreview {width: 50%; position: absolute; left: 100%; top: 0%; height: 100%}
  256. .hwPreview {border-radius: 4px; width:100%; overflow: auto; border: solid 1px #383838; height: 100%; padding: 10px }
  257. .hwPreview img {max-width:100%; object-fit: contain; padding: 10px; border-radius: 6px}
  258. .hwPreview .pythonReader {white-space: pre;}
  259. .hwPreview *[style="min-height:56.7pt"] {display: none;}
  260. .md-dialog-container.ng-scope {height: 100% !important; position: fixed}
  261. .hw-md_single__select-mark {flex-wrap: wrap;}
  262. #myDialog.home_work_modal md-dialog {width: 1160px; left: 50%; transform: translateX(-50%)}
  263. `);
  264.  
  265. if (document.querySelector(`.hwPreview[previewurl="${urlToHomework}"]`) !== null) {return}
  266.  
  267. let content = '<span class="NoSucsessLoad"> Не удалось открыть файл (неизвестный тип файла) </span>'
  268.  
  269. if (res.displayAs ==='html'){
  270. content = res.content.replace('href="', 'href="https:\\\\journalui.ru\\HwPreview\\fileReaderCache\\')
  271. } else if (res.displayAs ==='image'){
  272. content = res.content
  273. } else if (res.displayAs ==='pdf'){
  274. content = '<iframe class="pdfViewer" src="https:\\journalui.ru/homework/pdfPreview/'+res.AdditionalInfo+'"></iframe>'
  275. }
  276.  
  277. let DisplayingDiv = document.createElement('div')
  278. DisplayingDiv.setAttribute('previewurl', urlToHomework)
  279. DisplayingDiv.className = 'hwPreview'
  280. DisplayingDiv.innerHTML = content;
  281. placement.after(DisplayingDiv)
  282. FetchesCount = FetchesCount - 1;
  283. URLWaitingList.splice(URLWaitingList.indexOf(urlToHomework), 1)
  284. if (res.displayAs ==='python') {
  285. DisplayingDiv.innerHTML = '';
  286. let span = document.createElement('span');
  287. span.className = 'pythonReader';
  288. span.textContent = res.content;
  289. DisplayingDiv.appendChild(span);
  290. }
  291.  
  292.  
  293. }
  294.  
  295. function CreateRemoteViewAPI (urlToHomework, placement) {
  296. // Скоро загружу обнову на сервер, и запрос будет доступен по ссылке (пишу на момент теста на моём любимом 127.0.0.1:4890)
  297. // https://journalui.ru/teacherTools/hwPreviewTool
  298.  
  299. // P.S. файлы по типу .py начну поддерживать скоро, обновление клиента (этого скрипта) не понадобиться (я надеюсь). А ещё добавлю пару проверок чтобы запрос был разрешён только с омни (код же открытый)
  300. // Дизайн наверное сделаю приятнее но чуть позже, сейчас времени нету
  301.  
  302.  
  303. if (document.querySelector(`.hwPreview[previewurl="${urlToHomework}"]`) === null && urlToHomework !== null && FetchesCount < 10 && URLWaitingList.indexOf(urlToHomework) == -1) {
  304. URLWaitingList.push(urlToHomework);
  305. if (localStorage.getItem(`hwPreviewTool:${urlToHomework}`) !== null) {
  306. let res = localStorage.getItem(`hwPreviewTool:${urlToHomework}`)
  307. console.log(`hwPreviewTool:${urlToHomework}`)
  308. res= JSON.parse(res);
  309. console.log(`Pulling ${urlToHomework} to storage...`);
  310. DisplayRender(res, urlToHomework, placement);
  311. } else {
  312.  
  313.  
  314.  
  315.  
  316. SendPacket("https://journalui.ru/teacherTools/hwPreviewTool", "POST", {url: urlToHomework}).then(res => {
  317. res = JSON.parse(res);
  318. console.log(`Saving ${urlToHomework} to storage...`);
  319. localStorage.setItem(`hwPreviewTool:${urlToHomework}`, JSON.stringify(res));
  320. DisplayRender(res, urlToHomework, placement);
  321. }).catch(err => {
  322. console.error(err);
  323. let DisplayingDiv = document.createElement('div')
  324. DisplayingDiv.setAttribute('previewurl', urlToHomework);
  325. DisplayingDiv.className = 'hwPreview'
  326. DisplayingDiv.textContent = 'Нам не удалось открыть этот файл'
  327. placement.after(DisplayingDiv) // Предотвращает повторный пинг сервера, убирая нагрузку
  328. FetchesCount = FetchesCount - 1;
  329. URLWaitingList.splice(URLWaitingList.indexOf(urlToHomework), 1)
  330. })
  331. }
  332. }
  333. }
  334.  
  335. function ShowImageIfAvaiable(){
  336. if (IsHomeWorksOpened()){
  337. SendPacket("https://omni.top-academy.ru/homework/get-new-homeworks", "POST", null).then(data => {
  338. data = JSON.parse(data);
  339.  
  340. CurrentHomeworks=data.homework.reverse();
  341. const downloadUrls = CurrentHomeworks.map(obj => obj.download_url_stud);
  342. const PreviewPlaces = document.querySelectorAll(".hw-md_single_stud-work__outer")
  343.  
  344. if (document.getElementById("FillScreenViewer") === null){
  345. CreateFullscreenViewAPI();
  346. }
  347.  
  348.  
  349. for (var i=0; i < PreviewPlaces.length; i++){
  350. try{
  351. CreateRemoteViewAPI(downloadUrls[i], PreviewPlaces[i]);
  352. if (document.getElementById("ActiveImage"+i) !== null && document.getElementById("ActiveImage"+i).src !== downloadUrls[i]) {document.getElementById("ActiveImage"+i).src = downloadUrls[i]}
  353. if (document.getElementById("ActiveImage"+i) === null){
  354. var ImgPreviewDiv = document.createElement('div');
  355. ImgPreviewDiv.innerHTML=(`
  356. <img class='imgActiveImage' src=`+downloadUrls[i]+` id="ActiveImage`+i+`" onError='NotImage("ActiveImage`+i+`")' style="border-radius:20px; width:100%; cursor:pointer;" onClick="OpenImageOnFullscreen('`+downloadUrls[i]+`')">
  357. `);
  358. PreviewPlaces[i].after(ImgPreviewDiv)
  359. }
  360. }catch(e){console.error(e)}
  361. }
  362.  
  363.  
  364.  
  365.  
  366.  
  367. })
  368. }
  369. setTimeout(ShowImageIfAvaiable, 1000)
  370. }
  371.  
  372.  
  373.  
  374. function CheckUpdates(){
  375. fetch('https://gf.qytechs.cn/ru/scripts/487845-omni-image-preview', {method: 'GET'})
  376. .then(response => response.text())
  377. .then(data => {
  378. var versionRegex = /<dt class="script-show-version"><span>Версия<\/span><\/dt>\s+<dd class="script-show-version"><span>(.*?)<\/span><\/dd>/;
  379.  
  380. var match = data.match(versionRegex);
  381.  
  382. if (match) {
  383. var version = match[1];
  384. if (version!==Version && version!=="" && UpdateFounded==false){
  385. UpdateFounded=true;
  386. window.open('https://gf.qytechs.cn/ru/scripts/487845-omni-image-preview')
  387.  
  388. } else {
  389. setTimeout(CheckUpdates, 1800000);
  390. }
  391. }
  392. })
  393. .catch(error => {});
  394. }
  395.  
  396.  
  397. function ProcessLoad(){
  398. if(IsHomeWorksOpened()){
  399. setTimeout(ShowImageIfAvaiable, 200)
  400. } else {
  401. setTimeout(ProcessLoad, 200)
  402. }
  403. }
  404.  
  405. function AccountLog (){
  406. SendPacket('https://omni.top-academy.ru/profile/get-profile', 'POST', {}).then(res=>{
  407. res = JSON.parse(res);
  408. TeacherLogin = encodeURI(res.teach_info.fio_teach.toLowerCase().replace(" ","_"))
  409. console.log(TeacherLogin)
  410. })
  411. }
  412.  
  413. (function() {
  414. setTimeout(CheckUpdates, 60000);
  415. setInterval(checkFeedbackOpened, 1000);//
  416. window.CloseImageOnFullscreen = function () {
  417. if (CanOpenImage){
  418. CanOpenImage=false;
  419. document.getElementById('FullscreenView').style.height='0%'
  420. setTimeout(function() {document.getElementById('FullscreenView').style.display='none'}, 510);
  421. setTimeout(CanOpenImage=true, 500);
  422. }
  423.  
  424. };
  425. window.OpenImageOnFullscreen = function (URL) {
  426. if (CanOpenImage){
  427. CanOpenImage=false;
  428. document.getElementById('FullscreenView').style.display='block';
  429. setTimeout(function() {document.getElementById('FullscreenView').style.height='100%'}, 10);
  430. document.getElementById('FullscreenImg').src=URL;
  431. setTimeout(CanOpenImage=true, 500);
  432. }
  433. };
  434. window.NotImage = function (ID) {
  435. if (document.getElementById(ID) !== null){
  436. document.getElementById(ID).style.display="none";
  437. }
  438. };
  439. ProcessLoad();
  440. setTimeout(AccountLog, 1000)
  441. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址