Skip to content

Instantly share code, notes, and snippets.

@avin
Created May 26, 2025 07:46
Show Gist options
  • Save avin/0607d773a5c1372f1c407808c64fa9ac to your computer and use it in GitHub Desktop.
Save avin/0607d773a5c1372f1c407808c64fa9ac to your computer and use it in GitHub Desktop.
LLM Text fixer for slack-mod (https://github.com/avin/slack-mod)
(() => {
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