Skip to content

Instantly share code, notes, and snippets.

@ninjastic
Last active September 6, 2023 05:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ninjastic/59bde80306c32153a5775b9a570b16b7 to your computer and use it in GitHub Desktop.
Save ninjastic/59bde80306c32153a5775b9a570b16b7 to your computer and use it in GitHub Desktop.
(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