Give away free awards on Reddit
// ==UserScript==
// @name Reddit Free Awards
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Give away free awards on Reddit
// @author nrmu9
// @match https://www.reddit.com/*
// @license CC-BY-NC-SA-4.0
// @grant none
// ==/UserScript==
(function () {
"use strict";
const AWARD_ICON = `<svg style="transform: translateY(1px)" fill="currentColor" width="16" height="16" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M28 8.75h-0.211c0.548-0.833 0.874-1.854 0.874-2.952 0-0.448-0.054-0.884-0.157-1.3l0.008 0.037c-0.487-1.895-2.008-3.337-3.912-3.702l-0.031-0.005c-0.234-0.035-0.505-0.055-0.78-0.055-2.043 0-3.829 1.096-4.804 2.732l-0.014 0.026-2.973 4.279-2.974-4.279c-0.989-1.662-2.776-2.758-4.818-2.758-0.275 0-0.545 0.020-0.81 0.058l0.030-0.004c-1.935 0.37-3.455 1.812-3.934 3.672l-0.008 0.035c-0.095 0.379-0.149 0.815-0.149 1.263 0 1.097 0.326 2.119 0.886 2.972l-0.013-0.021h-0.212c-1.794 0.002-3.248 1.456-3.25 3.25v3c0.002 1.343 0.817 2.495 1.979 2.99l0.021 0.008v10.002c0.002 1.794 1.456 3.248 3.25 3.25h20c1.794-0.001 3.249-1.456 3.25-3.25v-10.002c1.183-0.503 1.998-1.656 2-2.998v-3c-0.002-1.794-1.456-3.248-3.25-3.25h-0zM28.75 12v3c-0.006 0.412-0.338 0.744-0.749 0.75h-10.751v-4.5h10.75c0.412 0.006 0.744 0.338 0.75 0.749v0.001zM21.027 4.957c0.544-1.009 1.593-1.683 2.8-1.683 0.104 0 0.207 0.005 0.309 0.015l-0.013-0.001c0.963 0.195 1.718 0.915 1.963 1.842l0.004 0.018c0.021 0.149 0.033 0.322 0.033 0.497 0 1.28-0.635 2.412-1.608 3.097l-0.012 0.008h-6.112zM5.911 5.147c0.248-0.944 1.002-1.664 1.949-1.857l0.016-0.003c0.092-0.010 0.199-0.015 0.307-0.015 1.204 0 2.251 0.675 2.783 1.667l0.008 0.017 2.636 3.793h-6.113c-0.984-0.692-1.619-1.823-1.619-3.101 0-0.177 0.012-0.351 0.036-0.521l-0.002 0.020zM3.25 12c0.006-0.412 0.338-0.744 0.749-0.75h10.751v4.5h-10.75c-0.412-0.006-0.744-0.338-0.75-0.749v-0.001zM5.25 28v-9.75h9.5v10.5h-8.75c-0.412-0.006-0.744-0.338-0.75-0.749v-0.001zM26.75 28c-0.006 0.412-0.338 0.744-0.749 0.75h-8.751v-10.5h9.5z"></path></svg>`;
const style = document.createElement("style");
style.textContent = `
.free-award-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0 0.5rem;
height: 32px;
border-radius: 9999px;
cursor: pointer;
color: var(--color-neutral-content);
font-family: var(--font-sans);
font-size: 12px;
font-weight: 600;
background: transparent;
border: none;
}
.free-award-btn:hover {
background-color: var(--color-neutral-background-hover);
}
.free-award-btn svg {
width: 16px;
height: 16px;
}
.award-modal-overlay {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.award-modal-overlay.visible {
opacity: 1;
}
`;
document.head.appendChild(style);
function createAwardButton(onClick, isComment = false) {
const btn = document.createElement("button");
const baseClasses =
"button border-md flex flex-row justify-center items-center h-xl font-semibold relative text-12 inline-flex items-center px-sm";
const variantClass = isComment ? "button-plain-weak" : "button-secondary";
btn.className = `${baseClasses} ${variantClass}`;
btn.style.cssText =
"height: var(--size-button-sm-h); font: var(--font-button-sm);";
btn.innerHTML = `
<span class="flex items-center">
<span class="flex text-16 me-[var(--rem6)]">${AWARD_ICON}</span>
<span>Free Award</span>
</span>
`;
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
onClick();
};
return btn;
}
function openAwardSelection(thingId, isComment) {
const script = document.createElement("script");
script.textContent = `
(async () => {
const getToken = () => {
const c = document.cookie.match(/csrf_token=([^;]+)/);
if (c) return c[1];
const a = document.querySelector('shreddit-app');
return a?.csrfToken || a?.getAttribute('spp') || a?.getAttribute('csrf-token');
};
const showToast = async (message, isError = false) => {
const findToaster = () => {
const alertController = document.querySelector('alert-controller');
return alertController?.shadowRoot?.querySelector('toaster-lite');
};
let toaster = findToaster();
if (!toaster) {
for (let i = 0; i < 50; i++) {
await new Promise(r => setTimeout(r, 100));
toaster = findToaster();
if (toaster) break;
}
}
if (!toaster) {
alert(message);
return;
}
toaster.dispatchEvent(new CustomEvent('show-toast', {
bubbles: true,
composed: true,
detail: {
message,
level: 3,
namedContent: {}
}
}));
};
const ctx = { id: "${thingId}", type: "${
isComment ? "Comment" : "Post"
}" };
const token = getToken();
if (!token) return showToast('Error: Could not find CSRF token.', true);
let author = null;
const selector = "${isComment}" === "true" ? 'shreddit-comment[thingid="${thingId}"]' : 'shreddit-post';
const el = document.querySelector(selector);
if (el?.getAttribute('author')) author = el.getAttribute('author');
const titleText = author ? \`Award \${author}'s \${ctx.type}\` : \`Award \${ctx.type}\`;
const awards = [
{ name: 'Heartwarming', id: 'award_free_heartwarming', img: 'https://i.redd.it/snoovatar/snoo_assets/marketing/Heartwarming_40.png' },
{ name: 'Popcorn', id: 'award_free_popcorn_2', img: 'https://i.redd.it/snoovatar/snoo_assets/marketing/Popcorn_40.png' },
{ name: 'Bravo', id: 'award_free_bravo', img: 'https://i.redd.it/snoovatar/snoo_assets/marketing/bravo_40.png' },
{ name: 'Regret', id: 'award_free_regret_2', img: 'https://i.redd.it/snoovatar/snoo_assets/marketing/regret_40.png' },
{ name: 'Mindblown', id: 'award_free_mindblown', img: 'https://i.redd.it/snoovatar/snoo_assets/marketing/mindblown_40.png' }
];
const container = document.createElement('div');
container.className = 'award-modal-overlay';
Object.assign(container.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.85)', zIndex: '999999', display: 'flex',
alignItems: 'center', justifyContent: 'center',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif'
});
const dialog = document.createElement('div');
Object.assign(dialog.style, {
backgroundColor: '#1a1a1b', color: 'white', padding: '32px 24px',
borderRadius: '24px', border: '1px solid #343536', textAlign: 'center',
boxShadow: '0 12px 40px rgba(0,0,0,0.8)', width: '360px',
boxSizing: 'border-box', maxHeight: '90vh', overflowY: 'auto'
});
dialog.innerHTML = \`<h2 style="margin:0 0 8px 0;font-size:22px;font-weight:700;color:white;line-height:1.2;letter-spacing:-0.5px">\${titleText}</h2><p style="margin:0 0 24px 0;font-size:14px;color:#818384;line-height:1.4">Select a free award:</p>\`;
const btnList = document.createElement('div');
Object.assign(btnList.style, { display: 'flex', flexDirection: 'column', gap: '12px' });
awards.forEach(award => {
const btn = document.createElement('button');
Object.assign(btn.style, {
height: '56px', display: 'flex', alignItems: 'center', justifyContent: 'center',
position: 'relative', cursor: 'pointer', backgroundColor: '#d7dadc',
border: 'none', borderRadius: '28px', fontSize: '18px', fontWeight: '700',
color: '#1a1a1b', width: '100%', margin: '0', padding: '0 50px',
boxSizing: 'border-box', transition: 'transform 0.1s'
});
btn.innerHTML = \`<img src="\${award.img}" style="height:32px;width:32px;position:absolute;left:20px;top:50%;transform:translateY(-50%);flex-shrink:0"><span style="line-height:1;display:block;width:100%;text-align:center">\${award.name}</span>\`;
btn.onclick = async () => {
btn.disabled = true;
btn.querySelector('span').innerText = 'Sending...';
try {
const res = await fetch('https://www.reddit.com/svc/shreddit/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Csrf-Token': token },
body: JSON.stringify({
operation: 'CreateAwardOrder',
variables: {
input: {
nonce: crypto.randomUUID(),
thingId: ctx.id,
awardId: award.id,
isAnonymous: false
}
},
csrf_token: token
})
});
const j = await res.json();
if (j.data?.createAwardOrder?.ok) {
showToast('Award Sent!');
close();
setTimeout(() => location.reload(), 2000);
} else {
let errorMsg = j.data?.createAwardOrder?.errors?.[0]?.message || j.errors?.[0]?.message || 'Unknown error';
if (errorMsg === 'Error creating award order') {
errorMsg += ' (You might be rate limited)';
}
showToast('Failed: ' + errorMsg, true);
btn.disabled = false;
btn.querySelector('span').innerText = award.name;
}
} catch (e) {
showToast('Network Error', true);
btn.disabled = false;
btn.querySelector('span').innerText = award.name;
}
};
btnList.appendChild(btn);
});
dialog.appendChild(btnList);
const cancelBtn = document.createElement('button');
cancelBtn.innerText = 'Cancel';
Object.assign(cancelBtn.style, {
background: 'transparent', color: '#818384', border: 'none',
marginTop: '20px', cursor: 'pointer', fontSize: '15px',
fontWeight: '600', width: '100%', padding: '8px'
});
const close = () => {
container.classList.remove('visible');
setTimeout(() => {
if (container.parentNode) document.body.removeChild(container);
}, 200);
};
cancelBtn.onclick = close;
container.onclick = (e) => {
if (e.target === container) close();
};
dialog.appendChild(cancelBtn);
container.appendChild(dialog);
document.body.appendChild(container);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
container.classList.add('visible');
});
});
})();
`;
document.body.appendChild(script);
document.body.removeChild(script);
}
function processPost(post) {
if (post.dataset.freeAwardProcessed) return;
const postId = post.id || post.getAttribute("postId");
const existingShareBtn = post.querySelector(
'shreddit-post-share-button[slot="share-button"]'
);
if (existingShareBtn && postId) {
const btn = createAwardButton(
() => openAwardSelection(postId, false),
false
);
btn.setAttribute("slot", "share-button");
post.insertBefore(btn, existingShareBtn);
post.dataset.freeAwardProcessed = "true";
return;
}
if (post.shadowRoot && postId) {
const container = post.shadowRoot.querySelector(
".shreddit-post-container"
);
if (container) {
const awardBtn = container.querySelector("award-button");
if (awardBtn) {
const btn = createAwardButton(
() => openAwardSelection(postId, false),
false
);
awardBtn.parentNode.insertBefore(btn, awardBtn.nextSibling);
post.dataset.freeAwardProcessed = "true";
return;
}
}
}
}
function processComment(comment) {
if (comment.dataset.freeAwardProcessed) return;
const actionRow = comment.querySelector("shreddit-comment-action-row");
if (!actionRow) return;
const shareBtn = actionRow.querySelector('[slot="comment-share"]');
const thingId = comment.getAttribute("thingid");
if (shareBtn && thingId) {
const btn = createAwardButton(
() => openAwardSelection(thingId, true),
true
);
btn.setAttribute("slot", "comment-award");
shareBtn.parentNode.insertBefore(btn, shareBtn);
comment.dataset.freeAwardProcessed = "true";
}
}
function run() {
document.querySelectorAll("shreddit-post").forEach(processPost);
document.querySelectorAll("shreddit-comment").forEach(processComment);
}
run();
const observer = new MutationObserver(() => run());
observer.observe(document.body, { childList: true, subtree: true });
})();