Pixelplanet+

Customize the PixelPlanet interface with extended personalization options and revert to default.

目前为 2025-02-15 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Pixelplanet+
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.1
  5. // @author Pixel, join dsc.gg/turkmenlippf
  6. // @description Customize the PixelPlanet interface with extended personalization options and revert to default.
  7. // @match https://pixelplanet.fun/*
  8. // @grant GM_addStyle
  9. // @grant GM_addStyle
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_setClipboard
  12. // @icon https://files.catbox.moe/qb2prb.png
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. GM_addStyle(`
  19. @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css');
  20. @import url('https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap');
  21. `);
  22.  
  23. // Varsayılan stiller
  24. const defaultCSS = `
  25. body {
  26. margin: 0;
  27. font-family: 'Montserrat', sans-serif;
  28. font-size: 16px;
  29. background: #c4c4c4;
  30. }
  31. .menu > div { background-color: transparent !important; }
  32. `;
  33.  
  34. // Kullanıcı ayarları
  35. const settings = {
  36. buttonColor: '#4CAF50',
  37. buttonHoverColor: '#ff91a6',
  38. fontColor: '#000000',
  39. fontSize: '16',
  40. fontFamily: 'Arial',
  41. menuColor: '#ffffff',
  42. backgroundOpacity: '1',
  43. backgroundImage: '',
  44. cursorURL: ''
  45. };
  46.  
  47. // Sayfa yüklendiğinde ayarları uygula
  48. applyStoredStyles();
  49.  
  50. // Buton ekle
  51. addCustomizationButton();
  52.  
  53. // Ayarları yükle ve uygula
  54. function applyStoredStyles() {
  55. const storedSettings = loadSettings();
  56. applyCustomStyles(storedSettings);
  57. }
  58.  
  59. // Ayarları localStorage'dan yükle
  60. function loadSettings() {
  61. Object.keys(settings).forEach(key => {
  62. settings[key] = getStoredValue(key, settings[key]);
  63. });
  64. return settings;
  65. }
  66.  
  67. // LocalStorage'dan bir değeri al
  68. function getStoredValue(key, defaultValue) {
  69. return localStorage.getItem(key) || defaultValue;
  70. }
  71.  
  72. // LocalStorage'a bir değeri kaydet
  73. function setStoredValue(key, value) {
  74. localStorage.setItem(key, value);
  75. }
  76.  
  77. // Stilleri uygula
  78. function applyCustomStyles({ buttonColor, buttonHoverColor, fontColor, fontSize, fontFamily, menuColor, backgroundOpacity, backgroundImage, cursorURL }) {
  79. GM_addStyle(`
  80. body {
  81. background-color: rgba(255, 255, 255, ${backgroundOpacity});
  82. background-image: url(${backgroundImage});
  83. font-size: ${fontSize}px;
  84. font-family: ${fontFamily};
  85. color: ${fontColor};
  86. }
  87. .actionbuttons, .actionbuttons button,
  88. .coorbox, .onlinebox, .cooldownbox, #palettebox {
  89. background-color: ${buttonColor} !important;
  90. color: white !important;
  91. }
  92. .actionbuttons:hover, .actionbuttons button:hover,
  93. .coorbox:hover, .onlinebox:hover, .cooldownbox:hover, #palettebox:hover {
  94. background-color: ${buttonHoverColor} !important;
  95. }
  96. .customMenu, .modal.USERAREA.show, .modal.HELP.show, .modal.SETTINGS.show {
  97. background-color: ${menuColor} !important;
  98. color: ${fontColor};
  99. border-radius: 10px;
  100. box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
  101. }
  102. .window.CHAT.show {
  103. background-image: url(${backgroundImage}) !important;
  104. background-size: cover;
  105. background-position: center;
  106. }
  107. * {
  108. cursor: url('${cursorURL}') 16 16, auto !important;
  109. }
  110. `);
  111. }
  112.  
  113. // Buton ekle
  114. function addCustomizationButton() {
  115. const customizationButton = document.createElement('div');
  116. customizationButton.id = 'customizationButton';
  117. customizationButton.className = 'actionbuttons';
  118. customizationButton.setAttribute('role', 'button');
  119. customizationButton.innerHTML = `
  120. <i class="fa fa-plus-square" aria-hidden="true" style="vertical-align: middle; font-size: 19px; color: #FFFFFF;"></i>
  121. `;
  122. customizationButton.style.position = 'fixed';
  123. customizationButton.style.left = '16px';
  124. customizationButton.style.top = '37%';
  125. customizationButton.style.zIndex = '9999';
  126. customizationButton.style.transform = 'translateY(-50%)';
  127.  
  128. document.body.appendChild(customizationButton);
  129. customizationButton.addEventListener('click', showCustomizationPanel);
  130. }
  131.  
  132. // Ayar panelini göster
  133. function showCustomizationPanel() {
  134. const panelHTML = `
  135. <div class="modal SETTINGS show customMenu" style="
  136. z-index: 9999;
  137. width: 50%;
  138. max-width: 500px;
  139. max-height: 80vh;
  140. overflow-y: auto;
  141. padding: 20px;
  142. position: fixed;
  143. top: 50%;
  144. left: 50%;
  145. transform: translate(-50%, -50%);
  146. background-color: #ffffff;
  147. border: 1px solid #ccc;
  148. border-radius: 12px;
  149. box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
  150. transition: all 0.3s ease;
  151. font-family: 'Pixelify Sans', sans-serif;
  152. ">
  153. <h2 style="text-align: center; font-size: 1.4em; margin-bottom: 1em;">Settings</h2>
  154. <div class="modal-topbtn close" role="button" title="Close" tabindex="-1" style="
  155. position: absolute;
  156. top: 10px;
  157. right: 10px;
  158. font-size: 1.2em;
  159. cursor: pointer;
  160. ">✕</div>
  161. <div class="content" style="display: flex; flex-direction: column; gap: 15px;">
  162. <div class="setitem">
  163. <label>Button Color:</label>
  164. <input type="color" id="buttonColorPicker" value="${settings.buttonColor}" />
  165. </div>
  166. <div class="setitem">
  167. <label>Button Hover Color:</label>
  168. <input type="color" id="buttonHoverColorPicker" value="${settings.buttonHoverColor}" />
  169. </div>
  170. <div class="setitem">
  171. <label>Font Color:</label>
  172. <input type="color" id="fontColorPicker" value="${settings.fontColor}" />
  173. </div>
  174. <div class="setitem">
  175. <label>Font Size:</label>
  176. <input type="number" id="fontSizePicker" min="10" max="30" value="${settings.fontSize}" style="width: 80px;" /> px
  177. </div>
  178. <div class="setitem">
  179. <label>Font Family:</label>
  180. <select id="fontFamilyPicker" style="padding: 5px; border-radius: 5px;">
  181. <option value="Arial" ${settings.fontFamily === 'Arial' ? 'selected' : ''}>Arial</option>
  182. <option value="Verdana" ${settings.fontFamily === 'Verdana' ? 'selected' : ''}>Verdana</option>
  183. <option value="Helvetica" ${settings.fontFamily === 'Helvetica' ? 'selected' : ''}>Helvetica</option>
  184. <option value="Tahoma" ${settings.fontFamily === 'Tahoma' ? 'selected' : ''}>Tahoma</option>
  185. <option value="Pixelify Sans" ${settings.fontFamily === 'Pixelify Sans' ? 'selected' : ''}>Pixelify Sans</option>
  186. </select>
  187. </div>
  188. <div class="setitem">
  189. <label>Menu Color:</label>
  190. <input type="color" id="menuColorPicker" value="${settings.menuColor}" />
  191. </div>
  192. <div class="setitem">
  193. <label>Background Opacity:</label>
  194. <input type="range" id="backgroundOpacity" min="0.1" max="1" step="0.1" value="${settings.backgroundOpacity}" />
  195. </div>
  196. <div class="setitem">
  197. <label>Chat Background Image URL:</label>
  198. <input type="text" id="backgroundImage" value="${settings.backgroundImage}" style="width: 100%;" placeholder="Enter URL here" />
  199. </div>
  200. <div class="setitem">
  201. <label>Custom Cursor URL:</label>
  202. <input type="text" id="cursorURL" value="${settings.cursorURL}" style="width: 100%;" placeholder="Enter cursor URL here" />
  203. </div>
  204. <button id="saveButton" style="background-color: #4CAF50; color: white; padding: 10px 20px; border: none; cursor: pointer; border-radius: 5px;">Save</button>
  205. <button id="resetButton" style="background-color: #f44336; color: white; padding: 10px 20px; border: none; cursor: pointer; border-radius: 5px;">Reset to Default</button>
  206. <button id="exportButton" style="background-color: #2196F3; color: white; padding: 10px 20px; border: none; cursor: pointer; border-radius: 5px;">Export Settings</button>
  207. <button id="importButton" style="background-color: #FF9800; color: white; padding: 10px 20px; border: none; cursor: pointer; border-radius: 5px;">Import Settings</button>
  208.  
  209. </div>
  210. </div>
  211. `;
  212.  
  213. const modalContainer = document.createElement('div');
  214. modalContainer.innerHTML = panelHTML;
  215. document.body.appendChild(modalContainer);
  216.  
  217. // Kapatma butonuna tıklandığında paneli kapat
  218. document.querySelector('.modal-topbtn.close').addEventListener('click', () => {
  219. modalContainer.remove();
  220. });
  221.  
  222. // Ayarları kaydet
  223. document.getElementById('saveButton').addEventListener('click', () => {
  224. settings.buttonColor = document.getElementById('buttonColorPicker').value;
  225. settings.buttonHoverColor = document.getElementById('buttonHoverColorPicker').value;
  226. settings.fontColor = document.getElementById('fontColorPicker').value;
  227. settings.fontSize = document.getElementById('fontSizePicker').value;
  228. settings.fontFamily = document.getElementById('fontFamilyPicker').value;
  229. settings.menuColor = document.getElementById('menuColorPicker').value;
  230. settings.backgroundOpacity = document.getElementById('backgroundOpacity').value;
  231. settings.backgroundImage = document.getElementById('backgroundImage').value;
  232. settings.cursorURL = document.getElementById('cursorURL').value;
  233.  
  234. saveSettings();
  235. applyStoredStyles();
  236. modalContainer.remove();
  237. });
  238.  
  239. // Varsayılan ayarlara sıfırlama
  240. document.getElementById('resetButton').addEventListener('click', () => {
  241. resetToDefaultStyles();
  242. modalContainer.remove();
  243. });
  244.  
  245. // Ayarları dışa aktar
  246. document.getElementById('exportButton').addEventListener('click', () => {
  247. const jsonSettings = JSON.stringify(settings);
  248. const blob = new Blob([jsonSettings], { type: 'application/json' });
  249. const link = document.createElement('a');
  250. link.href = URL.createObjectURL(blob);
  251. link.download = 'settings.json';
  252. link.click();
  253. });
  254.  
  255. // Ayarları içe aktar
  256. document.getElementById('importButton').addEventListener('click', () => {
  257. const input = document.createElement('input');
  258. input.type = 'file';
  259. input.accept = '.json';
  260. input.click();
  261.  
  262. input.addEventListener('change', (event) => {
  263. const file = event.target.files[0];
  264. if (file && file.name.endsWith('.json')) {
  265. const reader = new FileReader();
  266. reader.onload = function () {
  267. try {
  268. const importedSettings = JSON.parse(reader.result);
  269. Object.keys(importedSettings).forEach(key => {
  270. settings[key] = importedSettings[key];
  271. });
  272. saveSettings();
  273. applyStoredStyles();
  274. } catch (e) {
  275. alert('Failed to import settings.');
  276. }
  277. };
  278. reader.readAsText(file);
  279. } else {
  280. alert('Invalid file format.');
  281. }
  282. });
  283. });
  284.  
  285. }
  286.  
  287. // Ayarları kaydet
  288. function saveSettings() {
  289. Object.keys(settings).forEach(key => {
  290. setStoredValue(key, settings[key]);
  291. });
  292. }
  293.  
  294. // Varsayılan ayarlara dön
  295. function resetToDefaultStyles() {
  296. settings.buttonColor = rgba(226, 226, 226, 0.80);
  297. settings.buttonHoverColor = '#ff91a6';
  298. settings.fontColor = '#000000';
  299. settings.fontSize = '16';
  300. settings.fontFamily = 'Arial';
  301. settings.menuColor = '#ffffff';
  302. settings.backgroundOpacity = '1';
  303. settings.backgroundImage = '';
  304. settings.cursorURL = '';
  305. saveSettings();
  306. applyStoredStyles();
  307. }
  308.  
  309.  
  310. })();
  311.  
  312. (function() {
  313. 'use strict';
  314.  
  315. // Buton oluşturma
  316. let uploadBtn = document.createElement("button");
  317. uploadBtn.innerHTML = '<img src="https://files.catbox.moe/potman.png" style="width: 16px; height: 16px;">'; // Ataş ikonu
  318. uploadBtn.style.padding = "5px";
  319. uploadBtn.style.fontSize = "12px";
  320. uploadBtn.style.color = "white";
  321. uploadBtn.style.border = "1px solid black"; // 1px kalınlığında siyah border eklendi
  322. uploadBtn.style.borderRadius = "3px"; // İsteğe bağlı: Köşeleri yuvarlak yapmak için
  323. uploadBtn.style.cursor = "pointer";
  324. uploadBtn.style.display = "flex";
  325. uploadBtn.style.alignItems = "center";
  326. uploadBtn.style.justifyContent = "center";
  327. uploadBtn.style.marginLeft = "5px"; // Sohbet giriş kutusundan biraz uzaklaştırmak için
  328. uploadBtn.style.backgroundColor = "transparent"; // Arka planı şeffaf yap
  329.  
  330. // Dosya seçme input'u
  331. let fileInput = document.createElement("input");
  332. fileInput.type = "file";
  333. fileInput.style.display = "none";
  334. document.body.appendChild(fileInput);
  335.  
  336. // Butona tıklanınca dosya seç
  337. uploadBtn.addEventListener("click", function() {
  338. fileInput.click();
  339. });
  340.  
  341. fileInput.addEventListener("change", function() {
  342. if (fileInput.files.length === 0) return;
  343.  
  344. let file = fileInput.files[0];
  345. let formData = new FormData();
  346. formData.append("reqtype", "fileupload");
  347. formData.append("userhash", "");
  348. formData.append("fileToUpload", file);
  349.  
  350. GM_xmlhttpRequest({
  351. method: "POST",
  352. url: "https://catbox.moe/user/api.php",
  353. data: formData,
  354. onload: function(response) {
  355. if (response.status === 200) {
  356. let link = response.responseText.trim();
  357. GM_setClipboard(link);
  358.  
  359. // Sohbet kutusuna linki ekle
  360. let chatInput = document.querySelector("input.chtipt");
  361. if (chatInput) {
  362. chatInput.value = link;
  363. }
  364. } else {
  365. console.error("Yükleme başarısız oldu!");
  366. }
  367. }
  368. });
  369. });
  370.  
  371. // Sohbet kutusunu bul ve butonu içine ekle
  372. let chatInputContainer = document.querySelector("form.chatinput");
  373. if (chatInputContainer) {
  374. chatInputContainer.appendChild(uploadBtn);
  375. } else {
  376. console.error("Sohbet giriş kutusu bulunamadı!");
  377. }
  378. })();
  379. let notificationRadius=300;const NOTIFICATION_TIME=2000;let pixelList=[];let canvas;let notifCircles=[];const args=window.location.href.split(',');let globalScale=1;let viewX=parseInt(args[args.length-3]);let viewY=parseInt(args[args.length-2]);const PING_OP=0xB0;const REG_MCHUNKS_OP=0xA3;const PIXEL_UPDATE_OP=0xC1;const REG_CANVAS_OP=0xA0;if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",init);}else{init();}function init(){setTimeout(radarMain);}function showInfo(info){if(info.text.length>0){closeModal();const wrapper=document.createElement('div');wrapper.innerHTML=`<div class="Alert show" id="my_modal"><h2>Останній закріп</h2><p>${info.text}</p><button type="button" id="my_button">OK</button></div>`;document.body.appendChild(wrapper);const button=document.querySelector('#my_button');button.addEventListener('click',closeModal);}}function closeModal(){const modal=document.querySelector('#my_modal');if(modal)modal.remove();}async function loadFile(src){const resp=await fetch(src);const blob=await resp.blob();return new File([blob],'result.png',{type:'image/png',});}async function loadInfo(src){const resp=await fetch(src);return await resp.json();}function worldToScreen(x,y){return[((x-viewX)*globalScale)+(canvas.width/2),((y-viewY)*globalScale)+(canvas.height/2),];}function render(){try{const ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);if(globalScale>0){const curTime=Date.now();let index=pixelList.length;while(index>0){index--;let[setTime,x,y,i,j,color]=pixelList[index];const timePassed=curTime-setTime;if(timePassed>NOTIFICATION_TIME){pixelList.splice(index,1);continue;}const[sx,sy]=worldToScreen(x,y).map((z)=>z+globalScale/2);if(sx<0||sy<0||sx>canvas.width||sx>canvas.height){pixelList.splice(index,1);continue;}const notRadius=timePassed/NOTIFICATION_TIME*notificationRadius;const circleScale=notRadius/100;ctx.save();ctx.scale(circleScale,circleScale);ctx.drawImage(notifCircles[color],Math.round(sx/circleScale-100),Math.round(sy/circleScale-100),);ctx.restore();}}}catch(err){console.error(`Render error`,err,);}setTimeout(render,10);}function addPixel(x,y,i,j,color){for(let k=0;k<pixelList.length;k++){if(pixelList[k][3]===i&&pixelList[k][4]===j){pixelList[k][1]=x;pixelList[k][2]=y;pixelList[k][5]=color;return;}}pixelList.unshift([Date.now(),x,y,i,j,color]);}function getPixelFromChunkOffset(i,j,offset,canvasSize){const tileSize=256;const x=i*tileSize-canvasSize/2+offset%tileSize;const y=j*tileSize-canvasSize/2+Math.trunc(offset/tileSize);return[x,y];}function renderPixel(i,j,offset,color){const canvasSize=65536;const[x,y]=getPixelFromChunkOffset(i,j,offset,canvasSize);addPixel(x,y,i,j,color);}function renderPixels({i,j,pixels}){pixels.forEach((pxl)=>{const[offset,color]=pxl;renderPixel(i,j,offset,color);});}function clamp(n,min,max){return Math.max(min,Math.min(n,max));}function updateScale(viewscale){globalScale=viewscale;notificationRadius=clamp(viewscale*10,20,400);}function updateView(val){viewX=val[0];viewY=val[1];}function onWindowResize(){canvas.width=window.innerWidth;canvas.height=window.innerHeight;}function dehydratePing(){return new Uint8Array([PING_OP]).buffer;}function dehydrateRegMChunks(chunks){const buffer=new ArrayBuffer(1+1+chunks.length*2);const view=new Uint16Array(buffer);view[0]=REG_MCHUNKS_OP;for(let cnt=0;cnt<chunks.length;cnt+=1){view[cnt+1]=chunks[cnt];}return buffer;}function hydratePixelUpdate(data){const i=data.getUint8(1);const j=data.getUint8(2);const pixels=[];let off=data.byteLength;while(off>3){const color=data.getUint8(off-=1);const offsetL=data.getUint16(off-=2);const offsetH=data.getUint8(off-=1)<<16;pixels.push([offsetH|offsetL,color]);}return{i,j,pixels,};}function onBinaryMessage(buffer){if(buffer.byteLength===0)return;const data=new DataView(buffer);const opcode=data.getUint8(0);if(opcode===PIXEL_UPDATE_OP||opcode===145){renderPixels(hydratePixelUpdate(data));}}function dehydrateRegCanvas(canvasId){const buffer=new ArrayBuffer(1+1);const view=new DataView(buffer);view.setInt8(0,REG_CANVAS_OP);view.setInt8(1,Number(canvasId));return buffer;}function onMessage({data:message}){try{if(typeof message!=='string'){onBinaryMessage(message);}}catch(err){console.error(`An error occurred while parsing websocket message ${message}`,err,);}}function socketConnect(i,url,allChunks){const ws=new WebSocket(url);ws.binaryType='arraybuffer';ws.onopen=()=>{console.log(`Socket ${i} opened`);ws.send(dehydrateRegCanvas(0));const chunkids=[];for(let j=17000*i;j<17000*(i+1)&&j<allChunks.length;j++){chunkids.push(allChunks[j]);}ws.send(dehydrateRegMChunks(chunkids));};ws.onmessage=onMessage;ws.onclose=()=>{console.log(`Socket ${i} closed`);setTimeout(()=>{socketConnect(i,url,allChunks)},1000);};ws.onerror=(err)=>{console.error('Socket encountered error, closing socket',err);};setInterval(()=>{if(ws.readyState!==WebSocket.CLOSED){ws.send(dehydratePing());}},23000)}async function radarMain(){canvas=document.createElement('canvas');canvas.style.position='fixed';canvas.style.top='0';canvas.style.left='0';canvas.style.zIndex='0';canvas.style.pointerEvents='none';onWindowResize();const ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);document.body.appendChild(canvas);window.addEventListener('resize',onWindowResize);const colors=await loadColors();notifCircles=colors.map((color)=>{const circle=document.createElement('canvas');circle.width=200;circle.height=200;const ctx=circle.getContext('2d');ctx.fillStyle=`rgba(${color[0]},${color[1]},${color[2]},0.5)`;ctx.beginPath();ctx.arc(100,100,100,0,2*Math.PI);ctx.closePath();ctx.fill();return circle;});pixelPlanetEvents.on('setscale',updateScale);pixelPlanetEvents.on('setviewcoordinates',updateView);setTimeout(render,10);const url=`${window.location.protocol==='https:'?'wss:':'ws:'}//${window.location.host}/ws`;const allChunks=[];for(let i=0;i<=255;i++){for(let j=0;j<=255;j++){allChunks.push((i<<8)|j);}}for(let i=0;i<4;i++){setTimeout(()=>{socketConnect(i,url,allChunks)});}}async function loadColors(){const resp=await fetch('/api/me');const data=await resp.json();for(const[key,canvas]of Object.entries(data['canvases'])){if(canvas['ident']===window.location.hash.substring(1,2)){return canvas['colors'];}}return[[255,0,0],[0,255,0],[0,0,255],];}