// ==UserScript==
// @name Omni Tools
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description ;)
// @author You
// @match https://omni.top-academy.ru/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=top-academy.ru
// @grant none
// @license MIT
// ==/UserScript==
var Version = "1.4.1";
var CurrentHomeworks=null;
var CanOpenImage=true;
var UpdateFounded=false;
var TeacherLogin = null;
let FetchesCount = 0;
let URLWaitingList = [];
function FeedbackAi(){
//Список моделей на сайте
let AINameList = [
'OpenChat 3.5 (Recommended)',
'SnowFlake (Recommended)',
'Mistral 7B',
'Llama 3 8B',
'RWKV v5 3B AI Town',
'Capybara 7B',
'Gemma 7B',
'MythoMist 7B']
//Список используемых нейросетей
let AIProvidesList = [
'openchat/openchat-7b:free',
'snowflake/snowflake-arctic-instruct',
'mistralai/mistral-7b-instruct:free',
'meta-llama/llama-3-8b-instruct:free',
'recursal/rwkv-5-3b-ai-town',
'nousresearch/nous-capybara-7b:free',
'google/gemma-7b-it:free',
'gryphe/mythomist-7b:free']
//Функция отправки запроса с ожиданием ответа
function sendRequest(method, url, senddata) {
try{
return new Promise((resolve, reject) => {
fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer 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`,
"HTTP-Referer": `https://docs.google.com/`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"model": senddata.ai_model,
"messages": [
{"role": "user", "content": senddata.promt},
],
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => resolve(data["choices"][0]["message"]["content"]))
.catch(error => reject(error));
});
} catch(e){alert(e)}
}
let feedbackareas = document.querySelectorAll('textarea.textarea-rev')//Поиск всех textarea для подстановки текста из ИИ
let stundentinfodiv = document.querySelectorAll('.col-md-12 .col-md-6:nth-child(1)')//Поиск div с описанием студента
while (document.getElementById('AIButton') !== null){document.getElementById('AIButton').remove()}//Это очищает все созданные кнопки
while (document.getElementById('AIPromt') !== null){document.getElementById('AIPromt').remove()}//Это очищает все созданные поля промтов
function PromtPrepare(buttonPosition){
let informationdiv = stundentinfodiv[buttonPosition]//Получение div под номером buttonPosition из списка
let information = informationdiv.querySelectorAll('p')//Получение данных об ученике
let Grade = Math.round(Number(information[4].textContent.replace('Успеваемость: ',''))/120*1000)//Конвертация успреваемости в проценты (я знаю что этот расчёт просто имба)
if (Grade !== NaN && Grade !== null){
let PromtText = 'Привет! Я преподаватель в колледже и мне нужно оставить простой и краткий отзыв для ученика, желательно, не более 2 предложений. Прошу тебя помочь с написанием отзыва. Ниже приведена небольшая информация об ученике. Полное имя ученика: '+information[5].textContent.replace('ФИО: ','')+', средняя посещаемость ученика: '+information[3].textContent.replace('Посещаемость: ','')+', средняя успеваемость:'+Grade+'%. (Предмет:'+(information[6].textContent.replace('Предмет:',''))+'). Будет замечательно если ты допустишь как можно меньше грамматических ошибок';
return PromtText
} else {
return false
}
}
function AskAI(i){
let pressedButton = document.querySelector('.AIButtonAsk'+i)//Поиск нажатой кнопки
let informationdiv = stundentinfodiv[i]//Поиск требуемого div из списка
let promt = informationdiv.querySelector('textarea').value//Получение textarea из полученного div и его значения
let choiced_model_number = AINameList.indexOf(document.getElementById('AISelection').value)//Получение выбранной модели
pressedButton.textContent = 'Генерация отзыва...'
pressedButton.disabled='true'
sendRequest('POST', 'https://omnixtended.loca.lt/askAi', {promt: promt, ai_model:AIProvidesList[choiced_model_number]}).then(res => {
feedbackareas[i].value = res;
pressedButton.textContent = 'Сгенерировать заново'
pressedButton.style.border='none'
pressedButton.disabled = false;
}).catch(err=> {
console.error(err)
pressedButton.textContent = 'Произошла ошибка :(';
pressedButton.style.border='solid 2px red'
pressedButton.disabled = false;
})
}
//Для каждого ученика создаётся своя кнопка и поле с промптом
for (let i = 0; i<feedbackareas.length; i++){
//Создание кнопки с вызовом генерации
let AIButton = document.createElement('button')
AIButton.id='AIButton'
AIButton.textContent='Генерация отзыва с помозью AI'
AIButton.style='margin: 10px 0px 0px 0px; width: 100%'
AIButton.className= 'waves-effect waves-light btn md-button md-ink-ripple AIButtonAsk'+i
AIButton.addEventListener('click', function() {
AskAI(i)
})
//Создание поля с промптом
let AIPromt = document.createElement('textarea')
AIPromt.id="AIPromt"
AIPromt.style="width: 100%; height: 60px; border-radius: 10px; font-size: smaller"
AIPromt.placeholder="Описание студента для нейросети";
//Заспавнить кнопку после поля ввода
feedbackareas[i].parentElement.appendChild(AIButton)//Можно использовать parentElement.appendChild вместо after
//Заспавнить промпт-поле после информации об ученике
stundentinfodiv[i].appendChild(AIPromt)
let finalPromt = PromtPrepare(i)//Создание и вставка заготовленного промта для этого ученика
AIPromt.value = finalPromt
}
if(document.getElementById('AISelection') !== null) {document.getElementById('AISelection').remove()}//Сброс выбора поля модельки нейросети при перезапуске
//Создание выпадающего меню
let AISelection = document.createElement('select')
AISelection.id='AISelection'
AISelection.style='margin: 10px; padding: 4px; border-radius: 5px;'
let selectionInner='';
for (let h = 0; h<AINameList.length; h++){
//Добавлениее списка доступных нейронок в выпадающее меню
selectionInner+='<option>'+AINameList[h]+'</option>'
}
//Применение
AISelection.innerHTML=selectionInner;
document.querySelector('span.reviews-container').before(AISelection)
//Создание кнопки перезапуска (нужна была когда я начинал делать этот код, но сейчас просто фишка)
if (document.getElementById('AIReload') !== null){document.getElementById('AIReload').remove()}
let AIReload = document.createElement('button')
AIReload.id="AIReload";
AIReload.className = 'waves-effect waves-light btn md-button md-ink-ripple'
AIReload.style='position: absolute; top: 0px; width: fit-content; right: 10px'
AIReload.textContent = 'Перезапустить AITools';
AIReload.addEventListener('click', function() {
FeedbackAi()//Вызвать эту функцию заново
})
document.querySelector('span.reviews-container').appendChild(AIReload)
}
function checkFeedbackOpened(){
if(document.querySelector('md-dialog.reviews-modal.reviews-modal-comments.layout-padding') !== null && document.getElementById('AIButton') === null){
setTimeout(FeedbackAi, 1000);
}
}
function SendPacket(URL, Type, JSONVals){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(Type, URL);
xhr.setRequestHeader('authority', 'msapi.top-academy.ru');
xhr.setRequestHeader('method', 'POST');
xhr.setRequestHeader('path', '/api/v2/auth/login');
xhr.setRequestHeader('scheme', 'https');
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
xhr.setRequestHeader('Accept-Language', 'ru_RU, ru');
xhr.setRequestHeader('UIRequestData', TeacherLogin);
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(xhr.statusText);
}
}
};
xhr.onerror = () => reject(xhr.statusText);
if (URL.indexOf("teacherTools") >= 0) {FetchesCount += 1;}
if (JSONVals!==null) {
xhr.setRequestHeader('Content-Type', 'application/json');
let requestBody = JSONVals;
if (typeof JSONVals === 'string') {
requestBody = JSON.parse(JSONVals);
}
xhr.send(JSON.stringify(requestBody));
} else {
xhr.send();
}
});
}
window.SendPacket = SendPacket;
function CreateFullscreenViewAPI(){
var FullscreenView = document.createElement('div');
FullscreenView.id="FillScreenViewer"
FullscreenView.innerHTML=`
<style>
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;}
img#FullscreenImg:hover { height: 97% !important; }
.imgActiveImage{ transition: all 1s; border-radius: 20px; width: 100%; max-height: 100px; object-fit: cover; cursor: pointer; }
.imgActiveImage:hover{ max-height: 150px; }
div#FullscreenView {width: 100%; height: 0%; background: #252525de; position: absolute; transition: all .6s; top: 0px; z-index: 102; display: none; }
</style>
<div id="FullscreenView" onClick="window.CloseImageOnFullscreen()">
<img id="FullscreenImg">
</div>`;
document.querySelector("body").after(FullscreenView)
}
function IsHomeWorksOpened(){
return document.querySelectorAll("#myDialog.home_work_modal md-dialog").length > 0 // Применить скрипт если окно открылось
}
function CreateStyleIfNotExists(name, content) {
if (document.getElementById(name) === null) {
let style = document.createElement('style')
style.textContent = content;
style.id = name;
document.body.appendChild(style)
}
}
function DisplayRender(res, urlToHomework, placement) {
CreateStyleIfNotExists('hwPreview', `
#myDialog.home_work_modal .hw-md_item {width: 50%; position: relative}
.hwPreview {width: 50%; position: absolute; left: 100%; top: 0%; height: 100%}
.hwPreview {border-radius: 4px; width:100%; overflow: auto; border: solid 1px #383838; height: 100%; padding: 10px }
.hwPreview img {max-width:100%; object-fit: contain; padding: 10px; border-radius: 6px}
.hwPreview .pythonReader {white-space: pre;}
.hwPreview *[style="min-height:56.7pt"] {display: none;}
.md-dialog-container.ng-scope {height: 100% !important; position: fixed}
.hw-md_single__select-mark {flex-wrap: wrap;}
#myDialog.home_work_modal md-dialog {width: 1160px; left: 50%; transform: translateX(-50%)}
`);
if (document.querySelector(`.hwPreview[previewurl="${urlToHomework}"]`) !== null) {return}
let content = '<span class="NoSucsessLoad"> Не удалось открыть файл (неизвестный тип файла) </span>'
if (res.displayAs ==='html'){
content = res.content.replace('href="', 'href="https:\\\\journalui.ru\\HwPreview\\fileReaderCache\\')
} else if (res.displayAs ==='image'){
content = res.content
} else if (res.displayAs ==='pdf'){
content = '<iframe class="pdfViewer" src="https:\\journalui.ru/homework/pdfPreview/'+res.AdditionalInfo+'"></iframe>'
}
let DisplayingDiv = document.createElement('div')
DisplayingDiv.setAttribute('previewurl', urlToHomework)
DisplayingDiv.className = 'hwPreview'
DisplayingDiv.innerHTML = content;
placement.after(DisplayingDiv)
FetchesCount = FetchesCount - 1;
URLWaitingList.splice(URLWaitingList.indexOf(urlToHomework), 1)
if (res.displayAs ==='python') {
DisplayingDiv.innerHTML = '';
let span = document.createElement('span');
span.className = 'pythonReader';
span.textContent = res.content;
DisplayingDiv.appendChild(span);
}
}
function CreateRemoteViewAPI (urlToHomework, placement) {
// Скоро загружу обнову на сервер, и запрос будет доступен по ссылке (пишу на момент теста на моём любимом 127.0.0.1:4890)
// https://journalui.ru/teacherTools/hwPreviewTool
// P.S. файлы по типу .py начну поддерживать скоро, обновление клиента (этого скрипта) не понадобиться (я надеюсь). А ещё добавлю пару проверок чтобы запрос был разрешён только с омни (код же открытый)
// Дизайн наверное сделаю приятнее но чуть позже, сейчас времени нету
if (document.querySelector(`.hwPreview[previewurl="${urlToHomework}"]`) === null && urlToHomework !== null && FetchesCount < 10 && URLWaitingList.indexOf(urlToHomework) == -1) {
URLWaitingList.push(urlToHomework);
if (localStorage.getItem(`hwPreviewTool:${urlToHomework}`) !== null) {
let res = localStorage.getItem(`hwPreviewTool:${urlToHomework}`)
console.log(`hwPreviewTool:${urlToHomework}`)
res= JSON.parse(res);
console.log(`Pulling ${urlToHomework} to storage...`);
DisplayRender(res, urlToHomework, placement);
} else {
SendPacket("https://journalui.ru/teacherTools/hwPreviewTool", "POST", {url: urlToHomework}).then(res => {
res = JSON.parse(res);
console.log(`Saving ${urlToHomework} to storage...`);
localStorage.setItem(`hwPreviewTool:${urlToHomework}`, JSON.stringify(res));
DisplayRender(res, urlToHomework, placement);
}).catch(err => {
console.error(err);
let DisplayingDiv = document.createElement('div')
DisplayingDiv.setAttribute('previewurl', urlToHomework);
DisplayingDiv.className = 'hwPreview'
DisplayingDiv.textContent = 'Нам не удалось открыть этот файл'
placement.after(DisplayingDiv) // Предотвращает повторный пинг сервера, убирая нагрузку
FetchesCount = FetchesCount - 1;
URLWaitingList.splice(URLWaitingList.indexOf(urlToHomework), 1)
})
}
}
}
function ShowImageIfAvaiable(){
if (IsHomeWorksOpened()){
SendPacket("https://omni.top-academy.ru/homework/get-new-homeworks", "POST", null).then(data => {
data = JSON.parse(data);
CurrentHomeworks=data.homework.reverse();
const downloadUrls = CurrentHomeworks.map(obj => obj.download_url_stud);
const PreviewPlaces = document.querySelectorAll(".hw-md_single_stud-work__outer")
if (document.getElementById("FillScreenViewer") === null){
CreateFullscreenViewAPI();
}
for (var i=0; i < PreviewPlaces.length; i++){
try{
CreateRemoteViewAPI(downloadUrls[i], PreviewPlaces[i]);
if (document.getElementById("ActiveImage"+i) !== null && document.getElementById("ActiveImage"+i).src !== downloadUrls[i]) {document.getElementById("ActiveImage"+i).src = downloadUrls[i]}
if (document.getElementById("ActiveImage"+i) === null){
var ImgPreviewDiv = document.createElement('div');
ImgPreviewDiv.innerHTML=(`
<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]+`')">
`);
PreviewPlaces[i].after(ImgPreviewDiv)
}
}catch(e){console.error(e)}
}
})
}
setTimeout(ShowImageIfAvaiable, 1000)
}
function CheckUpdates(){
fetch('https://greasyfork.org/ru/scripts/487845-omni-image-preview', {method: 'GET'})
.then(response => response.text())
.then(data => {
var versionRegex = /<dt class="script-show-version"><span>Версия<\/span><\/dt>\s+<dd class="script-show-version"><span>(.*?)<\/span><\/dd>/;
var match = data.match(versionRegex);
if (match) {
var version = match[1];
if (version!==Version && version!=="" && UpdateFounded==false){
UpdateFounded=true;
window.open('https://greasyfork.org/ru/scripts/487845-omni-image-preview')
} else {
setTimeout(CheckUpdates, 1800000);
}
}
})
.catch(error => {});
}
function ProcessLoad(){
if(IsHomeWorksOpened()){
setTimeout(ShowImageIfAvaiable, 200)
} else {
setTimeout(ProcessLoad, 200)
}
}
function AccountLog (){
SendPacket('https://omni.top-academy.ru/profile/get-profile', 'POST', {}).then(res=>{
res = JSON.parse(res);
TeacherLogin = encodeURI(res.teach_info.fio_teach.toLowerCase().replace(" ","_"))
console.log(TeacherLogin)
})
}
(function() {
setTimeout(CheckUpdates, 60000);
setInterval(checkFeedbackOpened, 1000);//
window.CloseImageOnFullscreen = function () {
if (CanOpenImage){
CanOpenImage=false;
document.getElementById('FullscreenView').style.height='0%'
setTimeout(function() {document.getElementById('FullscreenView').style.display='none'}, 510);
setTimeout(CanOpenImage=true, 500);
}
};
window.OpenImageOnFullscreen = function (URL) {
if (CanOpenImage){
CanOpenImage=false;
document.getElementById('FullscreenView').style.display='block';
setTimeout(function() {document.getElementById('FullscreenView').style.height='100%'}, 10);
document.getElementById('FullscreenImg').src=URL;
setTimeout(CanOpenImage=true, 500);
}
};
window.NotImage = function (ID) {
if (document.getElementById(ID) !== null){
document.getElementById(ID).style.display="none";
}
};
ProcessLoad();
setTimeout(AccountLog, 1000)
})();