Last active
September 6, 2023 05:02
-
-
Save ninjastic/59bde80306c32153a5775b9a570b16b7 to your computer and use it in GitHub Desktop.
This file contains 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
(async () => { | |
// Single Topic version | |
// This script will find all posts (created by the authenticated user) on the current page and update their images | |
// options | |
const provider = 'talkimg' | |
const providers = { | |
talkimg: { | |
url: 'https://proxy.ninjastic.space/?url=https://talkimg.com/api/1/upload', | |
apiKey: 'chv_AiD_124562a509c5fadffba3e15a3a31f8241855c36609c497a325396124b370b138a1d5ecda8061410b4a3478bdf26b51c5589e23d7e277a15dedda70577ca79995', | |
albumId: '', | |
uploadsPerMinute: 20, | |
deleteOnError: true, | |
}, | |
imgbb: { | |
url: 'https://api.imgbb.com/1/upload', | |
apiKey: '', | |
uploadsPerMinute: 10, | |
deleteOnError: false, | |
} | |
} | |
const imageLinkRegex = /https:\/\/i\.imgur\.com\/.*?\.(png|jpg|jpeg|gif)/gi | |
const decoder = new TextDecoder('windows-1252') | |
const parser = new DOMParser() | |
let lastReq | |
if (providers[provider] === undefined) { | |
console.log(`%cERROR: Provider ${provider} does not exists.`, 'color: red; font-weight: bold;') | |
return | |
} | |
if (provider === 'imgbb' && !providers.imgbb.apiKey) { | |
console.log('%cERROR: Missing imgbb API key. Please edit the code and input yours (providers -> imgbb -> apiKey). More info: https://api.imgbb.com', 'color: red; font-weight: bold;') | |
return | |
} | |
let emojiRegex = await fetch('https://proxy.ninjastic.space/?url=https://raw.githubusercontent.com/mathiasbynens/emoji-test-regex-pattern/main/dist/latest/javascript.txt').then(response => response.status === 200 ? response.text() : null) | |
if (!emojiRegex || !'⭐'.match(emojiRegex)) { | |
console.log('%cERROR: Could not fetch Emoji regex', 'color: red; font-weight: bold;') | |
return | |
} | |
const encodeStr = (rawStr) => { | |
return rawStr.replace(new RegExp(`${emojiRegex}|[\u00A0-\u9999<>&]`, 'g'), (i) => `&#${i.codePointAt(0)};`) | |
} | |
const fetchThrottled = async (url, ...rest) => { | |
const timeRemaining = lastReq ? lastReq.getTime() + 1000 * 1 - new Date().getTime() : 0 | |
if (timeRemaining > 0) { | |
await new Promise(resolve => setTimeout(resolve, timeRemaining)) | |
} | |
lastReq = new Date() | |
return await fetch(url, ...rest) | |
} | |
const uploadImage = { | |
talkimg: async (image, description) => { | |
const formData = new FormData() | |
formData.append('type', 'file') | |
formData.append('format', 'json') | |
formData.append('description', String(description)) | |
if (providers.talkimg.albumId) { | |
formData.append('album_id', providers.talkimg.albumId) | |
} | |
formData.append('source', image) | |
const upload = await fetchThrottled(providers.talkimg.url, { | |
method: 'POST', | |
headers: { 'X-API-Key': providers.talkimg.apiKey }, | |
mode: 'cors', | |
body: formData, | |
}) | |
const response = await upload.json() | |
if (response.status_code === 200) { | |
return { url: response.image.url, deleteUrl: response.image.delete_url } | |
} | |
console.log('Could not upload, error:', response?.error?.message ?? response) | |
return undefined | |
}, | |
imgbb: async (image, name) => { | |
const formData = new FormData() | |
formData.append('image', image) | |
formData.append('name', String(name)) | |
const upload = await fetchThrottled(`${providers.imgbb.url}?key=${providers.imgbb.apiKey}`, { | |
method: 'POST', | |
mode: 'cors', | |
body: formData, | |
}) | |
const response = await upload.json() | |
if (response.status === 200) { | |
return { url: response.data.url, deleteUrl: response.data.delete_url } | |
} | |
console.log('Could not upload, error:', response?.error?.message ?? response) | |
return undefined | |
} | |
} | |
const decodeProxyImages = (html) => html.replaceAll(/img.*?src="(.*?)"\s/g, (text, imgUrl) => { | |
const directImgUrl = imgUrl | |
.replace(/https:\/\/ip\.bitcointalk\.org\/\?u=/, '') | |
.replace(/&.*/, '') | |
const decodedUrl = decodeURIComponent(directImgUrl) | |
return text.replace(imgUrl, decodedUrl) | |
}) | |
const getSesc = async () => { | |
const html = await fetchThrottled('https://bitcointalk.org/more.php').then(async response => decoder.decode(await response.arrayBuffer())) | |
return html.match(/https\:\/\/bitcointalk\.org\/index\.php\?action=logout;sesc=(.*?)"\>/)?.at(1) | |
} | |
const getQuote = async ({ topicId, postId, sesc }) => { | |
const url = `https://bitcointalk.org/index.php?action=quotefast;xml;quote=${postId};topic=${topicId};sesc=${sesc}` | |
const html = await fetchThrottled(url).then(async response => decoder.decode(await response.arrayBuffer())) | |
const $ = parser.parseFromString(html, 'text/html') | |
const quote = $.querySelector('quote').textContent | |
return quote.replace(/^\[quote.*?\]\n?/, '').replace(/\[\/quote\]$/, '') | |
} | |
const editPost = async ({ topicId, postId, title, message, sesc }) => { | |
const formData = new FormData() | |
formData.append('topic', String(topicId)) | |
formData.append('subject', encodeStr(title)) | |
formData.append('message', encodeStr(message)) | |
formData.append('sc', sesc) | |
formData.append('goback', String(1)) | |
const { redirected } = await fetchThrottled(`https://bitcointalk.org/index.php?action=post2;msg=${postId}`, { method: 'POST', body: formData }) | |
return redirected | |
} | |
const me = document.querySelector('#hellomember > b').textContent | |
if (!me) { | |
console.log('Error: Could not get account username, maybe not authenticated?') | |
return | |
} | |
if (document.querySelector('#bodyarea > table.tborder > tbody > tr > td:nth-child(1) > img').getAttribute('src') === 'https://bitcointalk.org/Themes/custom1/images/topic/normal_post_locked.gif') { | |
console.log('Error: Topic is locked') | |
return | |
} | |
const getPosts = () => { | |
return [...document.querySelectorAll('#quickModForm > table.bordercolor > tbody > tr')] | |
.filter(element => | |
element.querySelector('.post') && | |
window.getComputedStyle(element).getPropertyValue('display') === 'table-row' && | |
element.querySelector('.poster_info').querySelector('b > a').textContent === me).map(element => { | |
return { | |
topicId: Number(element.querySelector('.td_headerandpost td:nth-child(2) a').getAttribute('href').match(/topic=(\d+)/)?.at(1)), | |
postId: Number(element.querySelector('.td_headerandpost td:nth-child(2) a').getAttribute('href').match(/#msg(\d+)/)?.at(1)), | |
title: element.querySelector('.td_headerandpost td:nth-child(2) a[href*=msg]').textContent, | |
username: element.querySelector('.poster_info').querySelector('b > a').textContent, | |
userId: Number(element.querySelector('.poster_info').querySelector('b > a').getAttribute('href').match(/;u=(\d+)/)?.at(1)), | |
links: [...new Set(decodeProxyImages(element.querySelector('.post').innerHTML).match(imageLinkRegex))] ?? [] | |
} | |
}) | |
.filter(post => post.links.length > 0) | |
} | |
const posts = getPosts() | |
if (!posts.length) { | |
console.log('No posts with Imgur links were found.') | |
return | |
} | |
let numberUploads = 0 | |
for await (const post of posts) { | |
const images = [] | |
for await (const link of post.links) { | |
const blob = await fetchThrottled(link).then(async response => response.blob()) | |
images.push({ link, blob }) | |
} | |
const uploadedImages = [] | |
for await (const image of images) { | |
if (numberUploads >= providers[provider].uploadsPerMinute) { | |
numberUploads = 0 | |
console.log('Upload API limited, waiting 1 minute...') | |
await new Promise(resolve => setTimeout(resolve, 1000 * 60)) | |
} | |
console.log(`[${post.postId}] Uploading image...`) | |
const uploaded = await uploadImage[provider](image.blob, post.postId) | |
if (uploaded?.url) { | |
numberUploads += 1 | |
const oldLink = post.links.find(link => link === image.link) | |
uploadedImages.push({ old: oldLink, new: uploaded.url, deleteUrl: uploaded.deleteUrl }) | |
console.log(`[${post.postId}] Uploaded:`, uploaded.url) | |
} | |
} | |
if (uploadedImages.length > 0) { | |
const sesc = await getSesc() | |
const currPost = await getQuote({ topicId: post.topicId, postId: post.postId, sesc }) | |
let newContent = currPost | |
for (const uploadedImage of uploadedImages) { | |
newContent = newContent.replaceAll(uploadedImage.old, uploadedImage.new) | |
} | |
console.log(`[${post.postId}] Editing post https://bitcointalk.org/index.php?topic=${post.topicId}.msg${post.postId}#msg${post.postId}`) | |
const edited = await editPost({ topicId: post.topicId, postId: post.postId, title: post.title, message: newContent, sesc }) | |
if (!edited && !providers[provider].deleteOnError) { | |
console.log(`[${post.postId}] Could not edit post (maybe locked?)...`) | |
} | |
if (!edited && providers[provider].deleteOnError) { | |
console.log(`[${post.postId}] Could not edit post (maybe locked?), deleting uploaded images...`) | |
for (const uploadedImage of uploadedImages) { | |
await fetchThrottled(uploadedImage.deleteUrl, { redirect: 'manual' }) | |
} | |
} | |
} else { | |
console.log(`[${post.postId}] No images were uploaded, skiping edit...`) | |
} | |
} | |
console.log('-- Finished! --') | |
console.log('Make sure you donate to joker_josue for taking the time (and money) to create TalkImg!') | |
console.log('bc1qhwnncpdd8gfzqwjkk9n052wf7g9mvks3xaa7qa') | |
console.log('Verify on his website footer:', 'https://www.talkimg.com') | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment