Created
November 28, 2025 20:32
-
-
Save Sultan-papagani/0ce267d278f5ab3b0b6fd9991a4ad4ae to your computer and use it in GitHub Desktop.
Reddit Post Summarizer with Ollama & Model Dropdown (Tampermonkey)
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
| // ==UserScript== | |
| // @name Reddit Post Summarizer with Ollama & Model Dropdown | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.5 | |
| // @description Summarize Reddit posts with Ollama, select model from dropdown | |
| // @match https://www.reddit.com/* | |
| // @grant GM.xmlHttpRequest | |
| // @connect 127.0.0.1 | |
| // @connect 127.0.0.1:11434 | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const OLLAMA_URL = 'http://127.0.0.1:11434/api/generate'; | |
| const TAGS_URL = 'http://127.0.0.1:11434/api/tags'; | |
| let selectedModel = localStorage.getItem('ollama_model') || 'granite3.1-moe'; | |
| let availableModels = []; | |
| function fetchModels(callback) { | |
| GM.xmlHttpRequest({ | |
| method: 'GET', | |
| url: TAGS_URL, | |
| onload: function(res) { | |
| if (res.status >= 200 && res.status < 300) { | |
| try { | |
| const data = JSON.parse(res.responseText); | |
| availableModels = data.models.map(m => m.name); | |
| if (!availableModels.includes(selectedModel)) selectedModel = availableModels[0]; | |
| localStorage.setItem('ollama_model', selectedModel); | |
| callback(); | |
| } catch (e) { | |
| console.error('Failed to parse models', e); | |
| } | |
| } | |
| }, | |
| onerror: function(err) { | |
| console.error('Failed to fetch models', err); | |
| } | |
| }); | |
| } | |
| function collectText(container) { | |
| const paragraphs = Array.from(container.querySelectorAll('p, li')); | |
| return paragraphs.map(p => p.innerText.trim()).join('\n'); | |
| } | |
| function summarizePost(container) { | |
| appendSummary(container, null); // show loading | |
| const textToSummarize = collectText(container); | |
| if (!textToSummarize) return; | |
| const payload = { | |
| model: selectedModel, | |
| //summarize the following text very shortly: | |
| prompt: " Very shortly summarize the following text: " + textToSummarize, | |
| stream: false | |
| }; | |
| GM.xmlHttpRequest({ | |
| method: 'POST', | |
| url: OLLAMA_URL, | |
| headers: { 'Content-Type': 'application/json' }, | |
| data: JSON.stringify(payload), | |
| onload: function(res) { | |
| if (res.status >= 200 && res.status < 300) { | |
| try { | |
| const obj = JSON.parse(res.responseText); | |
| let summary = obj?.response || 'No response field found'; | |
| // Format Markdown bold and line breaks | |
| summary = summary.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>'); | |
| appendSummary(container, summary); | |
| } catch (e) { | |
| console.error('Failed to parse Ollama response:', e, res.responseText); | |
| appendSummary(container, 'Ollama is not running'); | |
| } | |
| } else { | |
| console.error('Ollama error:', res.status, res.statusText); | |
| appendSummary(container, 'Ollama is not running'); | |
| } | |
| }, | |
| onerror: function(err) { | |
| console.error('Ollama request failed:', err); | |
| appendSummary(container, 'Ollama is not running'); | |
| } | |
| }); | |
| } | |
| function appendSummary(container, summary = null) { | |
| // Check for existing summary outside the container | |
| let summaryEl = container.nextElementSibling; | |
| if (!summaryEl || !summaryEl.classList.contains('tm-ollama-summary')) { | |
| summaryEl = document.createElement('div'); | |
| summaryEl.className = 'tm-ollama-summary'; | |
| summaryEl.style.backgroundColor = '#f0f0f0'; | |
| summaryEl.style.padding = '10px'; | |
| summaryEl.style.borderRadius = '8px'; | |
| summaryEl.style.border = '1px solid #ccc'; | |
| summaryEl.style.marginTop = '10px'; | |
| summaryEl.style.whiteSpace = 'pre-wrap'; | |
| summaryEl.style.position = 'relative'; | |
| summaryEl.style.width = '100%'; | |
| summaryEl.style.clear = 'both'; | |
| // Header with dropdown | |
| const header = document.createElement('div'); | |
| header.style.display = 'flex'; | |
| header.style.alignItems = 'center'; | |
| header.style.justifyContent = 'space-between'; | |
| header.style.marginBottom = '5px'; | |
| const title = document.createElement('span'); | |
| title.innerHTML = '✨ <strong>Usefull AI Summarization</strong>'; | |
| title.style.fontSize = '14px'; | |
| const select = document.createElement('select'); | |
| select.style.fontSize = '10px'; | |
| select.style.padding = '2px'; | |
| select.style.height = '26px'; | |
| select.style.minWidth = '60px'; | |
| select.style.maxWidth = '150px'; | |
| select.style.overflow = 'hidden'; | |
| select.style.textOverflow = 'ellipsis'; | |
| select.style.whiteSpace = 'nowrap'; | |
| availableModels.forEach(model => { | |
| const option = document.createElement('option'); | |
| option.value = model; | |
| option.textContent = model; | |
| if (model === selectedModel) option.selected = true; | |
| select.appendChild(option); | |
| }); | |
| select.addEventListener('change', () => { | |
| selectedModel = select.value; | |
| localStorage.setItem('ollama_model', selectedModel); | |
| content.innerHTML = 'Loading...'; | |
| summarizePost(container); | |
| }); | |
| header.appendChild(title); | |
| header.appendChild(select); | |
| // Content div | |
| const content = document.createElement('div'); | |
| content.style.fontSize = '13px'; | |
| content.innerHTML = summary ? summary : 'Loading...'; | |
| summaryEl.appendChild(header); | |
| summaryEl.appendChild(content); | |
| // Insert summary **after the container** (as a sibling) | |
| container.insertAdjacentElement('afterend', summaryEl); | |
| } else { | |
| const content = summaryEl.querySelector('div:nth-child(2)'); | |
| content.innerHTML = summary ? summary : 'Loading...'; | |
| } | |
| } | |
| function init() { | |
| // Only run on a post page | |
| if (!window.location.pathname.includes('/comments/')) return; | |
| fetchModels(() => { | |
| const observer = new MutationObserver(() => { | |
| const postContainers = document.querySelectorAll('shreddit-post-text-body'); | |
| postContainers.forEach(container => { | |
| // Skip if already summarized | |
| if (container.dataset.summarized) return; | |
| // Skip if container is inside the feed (main page) | |
| if (container.closest('shreddit-feed')) return; | |
| container.dataset.summarized = 'true'; | |
| summarizePost(container); | |
| }); | |
| }); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| }); | |
| } | |
| init(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment