Skip to content

Instantly share code, notes, and snippets.

@pyrho
Created August 26, 2025 16:45
Show Gist options
  • Select an option

  • Save pyrho/3d794fd96d358b2e5c8b58fc1d703181 to your computer and use it in GitHub Desktop.

Select an option

Save pyrho/3d794fd96d358b2e5c8b58fc1d703181 to your computer and use it in GitHub Desktop.
A tampermonkey script to automatically populate fields when saving a bookmark to linkhut
// ==UserScript==
// @name Linkhut Auto-Fill Title and Summary
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Automatically fetch page title and summary when pasting URLs in linkhut
// @author You
// @match *://*/
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
console.log('Running');
// Only run on linkhut pages that contain the add link form
if (!document.querySelector('#link_url')) {
return;
}
console.log("in here");
// Add some styling for loading indicator
GM_addStyle(`
.linkhut-loading {
opacity: 0.6;
pointer-events: none;
}
.linkhut-status {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.linkhut-error {
color: #d32f2f;
}
.linkhut-success {
color: #388e3c;
}
`);
const urlInput = document.querySelector('#link_url');
const titleInput = document.querySelector('#link_title');
const notesInput = document.querySelector('#link_notes');
// Create status element
const statusElement = document.createElement('div');
statusElement.className = 'linkhut-status';
urlInput.parentNode.insertBefore(statusElement, urlInput.nextSibling);
function isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (_) {
return false;
}
}
function setStatus(message, type = '') {
statusElement.textContent = message;
statusElement.className = `linkhut-status ${type}`;
}
function setLoading(isLoading) {
if (isLoading) {
titleInput.classList.add('linkhut-loading');
notesInput.classList.add('linkhut-loading');
} else {
titleInput.classList.remove('linkhut-loading');
notesInput.classList.remove('linkhut-loading');
}
}
function extractMetaContent(html, property) {
const patterns = [
new RegExp(`<meta\\s+property=["']${property}["']\\s+content=["']([^"']+)["']`, 'i'),
new RegExp(`<meta\\s+content=["']([^"']+)["']\\s+property=["']${property}["']`, 'i'),
new RegExp(`<meta\\s+name=["']${property}["']\\s+content=["']([^"']+)["']`, 'i'),
new RegExp(`<meta\\s+content=["']([^"']+)["']\\s+name=["']${property}["']`, 'i')
];
for (const pattern of patterns) {
const match = html.match(pattern);
if (match) {
return match[1];
}
}
return null;
}
function extractTitle(html) {
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
return titleMatch ? titleMatch[1].trim() : null;
}
function extractSummary(html) {
// Try different meta tags for description/summary
const descriptionSources = [
'og:description',
'twitter:description',
'description',
'twitter:title',
'og:title'
];
for (const source of descriptionSources) {
const content = extractMetaContent(html, source);
if (content && content.length > 20) { // Only use if substantial content
return content;
}
}
return null;
}
function fetchPageInfo(url) {
setLoading(true);
setStatus('Fetching page info...', '');
GM_xmlhttpRequest({
method: 'GET',
url: url,
timeout: 10000,
onload: function(response) {
setLoading(false);
if (response.status >= 200 && response.status < 300) {
const html = response.responseText;
// Extract title
const title = extractTitle(html);
if (title && !titleInput.value.trim()) {
titleInput.value = title;
}
// Extract summary/description
const summary = extractSummary(html);
if (summary && !notesInput.value.trim()) {
notesInput.value = summary;
}
if (title || summary) {
setStatus('✓ Page info fetched successfully', 'linkhut-success');
} else {
setStatus('⚠ No title or description found', 'linkhut-error');
}
} else {
setStatus(`✗ Failed to fetch page (${response.status})`, 'linkhut-error');
}
// Clear status after 3 seconds
setTimeout(() => setStatus(''), 3000);
},
onerror: function() {
setLoading(false);
setStatus('✗ Error fetching page info', 'linkhut-error');
setTimeout(() => setStatus(''), 3000);
},
ontimeout: function() {
setLoading(false);
setStatus('✗ Request timed out', 'linkhut-error');
setTimeout(() => setStatus(''), 3000);
}
});
}
// Handle paste events
urlInput.addEventListener('paste', function(e) {
setTimeout(() => {
const url = urlInput.value.trim();
if (isValidUrl(url)) {
fetchPageInfo(url);
}
}, 100); // Small delay to ensure paste is complete
});
// Handle input events (for manual typing)
let inputTimeout;
urlInput.addEventListener('input', function(e) {
clearTimeout(inputTimeout);
inputTimeout = setTimeout(() => {
const url = urlInput.value.trim();
if (isValidUrl(url)) {
fetchPageInfo(url);
}
}, 1000); // Wait 1 second after user stops typing
});
console.log('Linkhut Auto-Fill script loaded');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment