Untitled
unknown
javascript
3 years ago
10 kB
3
Indexable
// ==UserScript== // @name ForsenPlace Script (Modified by YisusOnDev for esPlace) // @namespace https://github.com/YisusOnDev/esplace-war // @version 16 // @description Script // @author ForsenPlace // @match https://www.reddit.com/r/place/* // @match https://new.reddit.com/r/place/* // @icon https://cdn.frankerfacez.com/emoticon/545961/4 // @require https://cdn.jsdelivr.net/npm/toastify-js // @resource TOASTIFY_CSS https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css // @updateURL https://github.com/YisusOnDev/esplace-war/raw/main/script.user.js // @downloadURL https://github.com/YisusOnDev/esplace-war/main/script.user.js // @grant GM_getResourceText // @grant GM_addStyle // @grant GM.xmlHttpRequest // @connect reddit.com // ==/UserScript== const ORDERS_URL = 'https://raw.githubusercontent.com/YisusOnDev/esplace-war/main/orders.json' const ORDER_UPDATE_DELAY = 4 * 60 * 1000 const TOAST_DURATION = 10 * 1000 const MAP_ERROR_RETRY_DELAY = 6 * 1000 const PARSE_ERROR_REFRESH_DELAY = 10 * 1000 const AFTER_PAINT_DELAY = 5.25 * 60 * 1000 const CHECK_AGAIN_DELAY = 30 * 1000 const REFRESH_TOKEN_DELAY = 30 * 60 * 1000 const COLOR_TO_INDEX = { '#6D001A': 0, '#BE0039': 1, '#FF4500': 2, '#FFA800': 3, '#FFD635': 4, '#FFF8B8': 5, '#00A368': 6, '#00CC78': 7, '#7EED56': 8, '#00756F': 9, '#009EAA': 10, '#00CCC0': 11, '#2450A4': 12, '#3690EA': 13, '#51E9F4': 14, '#493AC1': 15, '#6A5CFF': 16, '#94B3FF': 17, '#811E9F': 18, '#B44AC0': 19, '#E4ABFF': 20, '#DE107F': 21, '#FF3881': 22, '#FF99AA': 23, '#6D482F': 24, '#9C6926': 25, '#FFB470': 26, '#000000': 27, '#515252': 28, '#898D90': 29, '#D4D7D9': 30, '#FFFFFF': 31 }; const INDEX_TO_NAME = { '0': 'burgundy', '1': 'dark red', '2': 'red', '3': 'orange', '4': 'yellow', '5': 'pale yellow', '6': 'dark green', '7': 'green', '8': 'light green', '9': 'dark teal', '10': 'teal', '11': 'light teal', '12': 'dark blue', '13': 'blue', '14': 'light blue', '15': 'indigo', '16': 'periwinkle', '17': 'lavender', '18': 'dark purple', '19': 'purple', '20': 'pale purple', '21': 'magenta', '22': 'pink', '23': 'light pink', '24': 'dark brown', '25': 'brown', '26': 'beige', '27': 'black', '28': 'dark gray', '29': 'gray', '30': 'light gray', '31': 'white' }; var currentOrdersByPrio = []; var accessToken; var canvas = document.createElement('canvas'); (async function () { GM_addStyle(GM_getResourceText('TOASTIFY_CSS')); canvas.width = 2000; canvas.height = 2000; canvas.style.display = 'none'; canvas = document.body.appendChild(canvas); // Get the token Toastify({ text: 'Obtaining access token...', duration: TOAST_DURATION }).showToast(); accessToken = await getAccessToken(); Toastify({ text: 'Obtained access token!', duration: TOAST_DURATION }).showToast(); // Start working await updateOrders(); executeOrders(); // Periodically refresh the orders setInterval(updateOrders, ORDER_UPDATE_DELAY); // Periodically refresh the token setInterval(async () => { Toastify({ text: 'Refreshing access token...', duration: TOAST_DURATION }).showToast(); accessToken = await getAccessToken(); Toastify({ text: 'Refreshed access token!', duration: TOAST_DURATION }).showToast(); }, REFRESH_TOKEN_DELAY) })(); async function getAccessToken() { const usingOldReddit = window.location.href.includes('new.reddit.com'); const url = usingOldReddit ? 'https://new.reddit.com/r/place/' : 'https://www.reddit.com/r/place/'; const response = await fetch(url); const responseText = await response.text(); return responseText.split('\"accessToken\":\"')[1].split('"')[0]; } function updateOrders() { fetch(ORDERS_URL).then(async (response) => { if (!response.ok) return console.warn('Couldn\'t get orders (error response code)'); const newOrders = await response.json(); if (JSON.stringify(newOrders) !== JSON.stringify(currentOrdersByPrio)) { currentOrdersByPrio = newOrders; Toastify({ text: `Obtained new orders!`, duration: TOAST_DURATION }).showToast(); } }).catch((e) => console.warn('Couldn\'t get orders', e)); } async function executeOrders() { var ctx; try { ctx = await getCanvasFromUrl(await getCurrentImageUrl('0'), 0, 0); ctx = await getCanvasFromUrl(await getCurrentImageUrl('1'), 1000, 0); ctx = await getCanvasFromUrl(await getCurrentImageUrl('2'), 0, 1000); ctx = await getCanvasFromUrl(await getCurrentImageUrl('3'), 1000, 1000); } catch (e) { console.warn('Error obtaining map', e); Toastify({ text: `Couldn\'t get map. Trying again in ${MAP_ERROR_RETRY_DELAY / 1000} seconds...`, duration: MAP_ERROR_RETRY_DELAY }).showToast(); setTimeout(executeOrders, MAP_ERROR_RETRY_DELAY); return; } for (const [prioIndex, orders] of currentOrdersByPrio.entries()) { let start = Math.floor(Math.random() * orders.length); for (let offset = 0; offset < orders.length; offset++) { const order = orders[(start + offset) % orders.length] const x = order[0]; const y = order[1]; const colorId = order[2]; const rgbaAtLocation = ctx.getImageData(x, y, 1, 1).data; const hex = rgbToHex(rgbaAtLocation[0], rgbaAtLocation[1], rgbaAtLocation[2]); const currentColorId = COLOR_TO_INDEX[hex]; // If the pixel color is already correct skip if (currentColorId == colorId) continue; Toastify({ text: `Changing pixel on ${x}, ${y} with priority ${prioIndex + 1} from ${INDEX_TO_NAME[currentColorId]} to ${INDEX_TO_NAME[colorId]}`, duration: TOAST_DURATION }).showToast(); const res = await place(x, y, colorId); const data = await res.json(); try { if (data.errors) { const error = data.errors[0]; const nextPixel = error.extensions.nextAvailablePixelTs + 3000; const nextPixelDate = new Date(nextPixel); const delay = nextPixelDate.getTime() - Date.now(); Toastify({ text : `Too early to place pixel! Next pixel at ${ nextPixelDate.toLocaleTimeString()}`, duration: delay }).showToast(); setTimeout(executeOrders, delay); } else { const nextPixel = data.data.act.data[0].data.nextAvailablePixelTimestamp + 3000; const nextPixelDate = new Date(nextPixel); const delay = nextPixelDate.getTime() - Date.now(); Toastify({ text : `Pixel placed on ${x}, ${y}! Next pixel at ${nextPixelDate.toLocaleTimeString()}`, duration: delay }).showToast(); setTimeout(executeOrders, delay); } } catch (e) { // The token probably expired, refresh and hope for the best console.warn ('Error parsing response', e); Toastify({ text : `Error parsing response after placing pixel. Refreshing the page in ${PARSE_ERROR_REFRESH_DELAY / 1000} seconds...`, duration: PARSE_ERROR_REFRESH_DELAY }).showToast(); setTimeout(() => { window.location.reload(); }, PARSE_ERROR_REFRESH_DELAY); } return; } } Toastify({ text: `Every pixel is correct! checking again in ${CHECK_AGAIN_DELAY / 1000} seconds...`, duration: CHECK_AGAIN_DELAY }).showToast(); setTimeout(executeOrders, CHECK_AGAIN_DELAY); } function place(x, y, color) { return fetch('https://gql-realtime-2.reddit.com/query', { method: 'POST', body: JSON.stringify({ 'operationName': 'setPixel', 'variables': { 'input': { 'actionName': 'r/replace:set_pixel', 'PixelMessageData': { 'coordinate': { 'x': x % 1000, 'y': y % 1000 }, 'colorIndex': color, 'canvasIndex': getCanvasIndex(x, y) } } }, 'query': 'mutation setPixel($input: ActInput!) { act(input: $input) { data { ... on BasicMessage { id data { ... on GetUserCooldownResponseMessageData { nextAvailablePixelTimestamp __typename } ... on SetPixelResponseMessageData { timestamp __typename } __typename } __typename } __typename } __typename } }' }), headers: { 'origin': 'https://hot-potato.reddit.com', 'referer': 'https://hot-potato.reddit.com/', 'apollographql-client-name': 'mona-lisa', 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); } function getCanvasIndex(x, y) { if (x <= 999) { return y <= 999 ? 0 : 2; } else { return y <= 999 ? 1 : 3; } } async function getCurrentImageUrl(tag) { return new Promise((resolve, reject) => { const ws = new WebSocket('wss://gql-realtime-2.reddit.com/query', 'graphql-ws'); ws.onopen = () => { ws.send(JSON.stringify({ 'type': 'connection_init', 'payload': { 'Authorization': `Bearer ${accessToken}` } })); ws.send(JSON.stringify({ 'id': '1', 'type': 'start', 'payload': { 'variables': { 'input': { 'channel': { 'teamOwner': 'AFD2022', 'category': 'CANVAS', 'tag': tag } } }, 'extensions': {}, 'operationName': 'replace', 'query': 'subscription replace($input: SubscribeInput!) { subscribe(input: $input) { id ... on BasicMessage { data { __typename ... on FullFrameMessageData { __typename name timestamp } } __typename } __typename } }' } })); }; ws.onmessage = (message) => { const { data } = message; const parsed = JSON.parse(data); if (!parsed.payload || !parsed.payload.data || !parsed.payload.data.subscribe || !parsed.payload.data.subscribe.data) return; ws.close(); resolve(parsed.payload.data.subscribe.data.name + `?noCache=${Date.now() * Math.random()}`); } ws.onerror = reject; }); } function getCanvasFromUrl(url, x, y) { return new Promise((resolve, reject) => { var ctx = canvas.getContext('2d'); GM.xmlHttpRequest({ method: "GET", url: url, responseType: 'blob', onload: function(response) { var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(this.response); var img = new Image(); img.onload = () => { ctx.drawImage(img, x, y); resolve(ctx); }; img.src = imageUrl; } }); }); } function rgbToHex(r, g, b) { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase(); }
Editor is loading...