Created
May 26, 2025 07:46
-
-
Save avin/0607d773a5c1372f1c407808c64fa9ac to your computer and use it in GitHub Desktop.
LLM Text fixer for slack-mod (https://github.com/avin/slack-mod)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(() => { | |
function setupXhrInterceptor(interceptorRules) { | |
const OriginalXMLHttpRequest = window.XMLHttpRequest; | |
const originalOpen = OriginalXMLHttpRequest.prototype.open; | |
const originalSend = OriginalXMLHttpRequest.prototype.send; | |
const getCancelErrorResponseText = () => { | |
return JSON.stringify({ | |
ok: false, | |
error: 'Request cancelled by interceptor', | |
response_metadata: { | |
messages: [ | |
{ | |
type: 'error', | |
text: 'Request cancelled by interceptor' | |
} | |
] | |
} | |
}); | |
} | |
function CustomXMLHttpRequestImpl() { | |
const xhr = new OriginalXMLHttpRequest(); | |
xhr.open = function (method, url, async, username, password) { | |
this._method = method; | |
this._url = new URL(url, window.location.href); | |
return originalOpen.call( | |
this, | |
method, | |
url, | |
async !== false, | |
username, | |
password | |
); | |
}; | |
xhr.send = async function (data) { | |
try { | |
for (const requestRule of interceptorRules.requests) { | |
if (requestRule.match({url: this._url})) { | |
data = await requestRule.process({ | |
requestBody: data, | |
url: this._url | |
}); | |
} | |
} | |
} catch (e) { | |
Object.defineProperty(this, 'status', {writable: true}); | |
Object.defineProperty(this, 'response', {writable: true}); | |
Object.defineProperty(this, 'responseText', {writable: true}); | |
Object.defineProperty(this, 'readyState', {writable: true}); | |
Object.defineProperty(this, 'statusText', {writable: true}); | |
Object.defineProperty(this, 'responseURL', {writable: true}); | |
this.readyState = 4; | |
this.status = 200; | |
this.responseText = getCancelErrorResponseText(); | |
this.response = getCancelErrorResponseText(); | |
this.statusText = 'OK'; | |
this.responseURL = this._url.href; | |
this.getAllResponseHeaders = () => 'content-type: application/json'; | |
if (typeof this.onreadystatechange === 'function') { | |
this.onreadystatechange( | |
new ProgressEvent('readystatechange', { | |
bubbles: false, | |
cancelable: false, | |
lengthComputable: false, | |
loaded: 0, | |
total: 0 | |
}) | |
); | |
} | |
this.dispatchEvent(new Event('readystatechange')); | |
this.dispatchEvent(new Event('load')); | |
this.dispatchEvent(new Event('loadend')); | |
return; | |
} | |
this._data = data; | |
return originalSend.call(this, data); | |
}; | |
xhr.addEventListener('readystatechange', function () { | |
if (this.readyState === 4 && this.status === 200) { | |
for (const responseRule of interceptorRules.responses) { | |
if (responseRule.match({url: this._url})) { | |
responseRule.process({ | |
requestBody: this._data, | |
responseText: this.responseText, | |
url: this._url | |
}); | |
} | |
} | |
} | |
}); | |
return xhr; | |
} | |
// Копируем статические свойства и методы | |
Object.keys(OriginalXMLHttpRequest).forEach(key => { | |
Object.defineProperty( | |
CustomXMLHttpRequestImpl, | |
key, | |
Object.getOwnPropertyDescriptor(OriginalXMLHttpRequest, key) || {} | |
); | |
}); | |
window.XMLHttpRequest = CustomXMLHttpRequestImpl; | |
} | |
const correctText = async (text) => { | |
const OPENAI_API_MODEL = 'gpt-4.1-mini-2025-04-14' | |
const OPENAI_API_URL = 'https://api.proxyapi.ru/openai/v1/chat/completions' | |
const OPENAI_API_KEY = 'sk-6tp00000000000000000000000000000' | |
const OPENAI_SYSTEM_PROMPT = `\ | |
You are Grammar Fix Machine, an assistant that corrects spelling and punctuation errors in the text of chat messages. | |
You must: | |
* Fix received text and provide a corrected version. | |
* Only process and output the received text, without adding anything from yourself. | |
* Do not add a period at the end of one-word sentences if there was no period in the original text either. | |
`; | |
const response = await fetch(OPENAI_API_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${OPENAI_API_KEY}`, | |
}, | |
body: JSON.stringify({ | |
model: OPENAI_API_MODEL, | |
messages: [ | |
{role: 'system', content: OPENAI_SYSTEM_PROMPT}, | |
{role: 'user', content: text} | |
], | |
temperature: 0 | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const data = await response.json(); | |
return data.choices[0].message.content.trim(); | |
}; | |
const interceptorRules = { | |
requests: [ | |
{ | |
match: ({url}) => url.pathname === '/api/chat.postMessage', | |
process: async ({requestBody}) => { | |
try { | |
let blocks = JSON.parse(requestBody.get('blocks')); | |
for (const block of blocks) { | |
if (!Array.isArray(block.elements)) continue; | |
for (const section of block.elements) { | |
if (!Array.isArray(section.elements)) continue; | |
for (const el of section.elements) { | |
if (el.type === 'text' && typeof el.text === 'string') { | |
const original = el.text; | |
try { | |
const corrected = await correctText(original); | |
el.text = corrected; | |
console.log(`[ORIGINAL] ${original}`); | |
console.log(`[CORRECTED] ${corrected}`); | |
} catch (err) { | |
console.log(`[NOT_PROCESSED] "${original}" Reason: ${err.message || err}`); | |
} | |
} else { | |
console.log(`[NOT_PROCESSED] element skipped, type: ${el.type}, not a text element`); | |
} | |
} | |
} | |
} | |
requestBody.set('blocks', JSON.stringify(blocks)); | |
} catch { | |
} | |
return requestBody; | |
}, | |
}, | |
], | |
responses: [], | |
}; | |
setupXhrInterceptor(interceptorRules); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment