MC-Skin

在网页里添加一个MC小人

// ==UserScript==
// @name         MC-Skin
// @namespace    https://viayoo.com/
// @version      2.4
// @description  在网页里添加一个MC小人
// @author       undefined303
// @license MIT
// @run-at       document-end
// @match        *
// @include      *
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @require      data:text/javascript,const%20origdef%20%3D%20window.define%3B
// @require      data:text/javascript,window.define%20%3D%20undefined%3B
// @require    https://fastly.jsdelivr.net/npm/[email protected]/bundles/skinview3d.bundle.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/build/three.min.js
// @require      data:text/javascript,window.define%20%3D%20origdef%3B
// ==/UserScript==
(function() {
	'use strict'
	var skin = GM_getValue("skin", null);
	if (self != top) {
		return;
	}
	console.log("%cMcSkin.js", "color:orange");
	var defaultRotation = GM_getValue("defaultRotation", -0.25);
	const box = document.createElement("div");
	document.documentElement.append(box);
	const shadow = box.attachShadow({
		mode: "closed"
	});
	const inner = document.createElement("main");
	shadow.append(inner);
	var dialog = inner.appendChild(document.createElement("dialog"));
	dialog.setAttribute("style", `border:none !important;
border-radius:10px !important;
width:min(70vw,350px) !important;
max-width:100vw !important;
text-align:center !important;
 padding:40px 0px !important;
box-shadow:0px 0px 7px 1px rgba(0,0,0,.3) !important;
backdrop-filter: blur(50px);
background-color: rgba(255, 255, 255, 0.8);
outline:none !important;
font-size:0px;
`)
	var span = document.body.appendChild(document.createElement("span"));
	span.setAttribute("style", "font-size:1.2em");
	var fontSize = window.getComputedStyle(span).fontSize;
	document.body.removeChild(span);
	dialog.addEventListener("click", e => {
		const dialogDimensions = dialog.getBoundingClientRect()
		if (
			e.clientX < dialogDimensions.left ||
			e.clientX > dialogDimensions.right ||
			e.clientY < dialogDimensions.top ||
			e.clientY > dialogDimensions.bottom
		) {
			dialog.close()
		}
	})
	var removeAllChild = function(node) {
		while (node.hasChildNodes()) {
			node.removeChild(node.lastChild);
		}
	}
	skin = GM_getValue("skin", skin);

	var uploadSkin = function(isSave) {
		dialog.close();
		return new Promise((resolve, reject) => {
			let input = document.createElement('input');
			input.type = 'file';
			input.accept = 'image/png';
			input.style.display = 'none';
			input.multiple = false;
			input.addEventListener('change', (event) => {
				let file = event.target.files[0];
				if (!file) {
					reject(new Error('No file selected'));
					return;
				}
				if (file.type !== 'image/png') {
					reject(new Error('Only PNG files are allowed'));
					return;
				}
				let reader = new FileReader();
				reader.onload = (e) => {
					try {
						const base64 = e.target.result;
						skinViewer.loadSkin(base64);
						skin = base64;
						if (isSave) {
							GM_setValue("skin", base64);
						}
						resolve(base64);
					} catch (error) {
						reject(error);
					}
				};
				reader.onerror = (error) => reject(error);
				reader.readAsDataURL(file);
			});
			document.body.appendChild(input);
			input.click();
			setTimeout(() => {
				document.body.removeChild(input);
			}, 200)
		});
	}

	var createSkinPickerDialog = function(isSave, info) {
		removeAllChild(dialog);
		let span = dialog.appendChild(document.createElement("span"));
		span.style.fontSize = fontSize;
		span.innerText = info;
		span.style.display = "block";
		let wrap = dialog.appendChild(document.createElement("div"));
		wrap.style.display = "block";
		let nameInp = wrap.appendChild(document.createElement("input"));
		nameInp.placeholder = "使用正版ID获取皮肤";
		nameInp.setAttribute("style", `
outline:none;
border:none;
border-bottom:2px solid black;
background:transparent;
margin-right:10px;
`)
		nameInp.addEventListener("input", function() {
			if (nameInp.value != "") {
				uploadBtn.innerText = "获取皮肤";
				uploadBtn.onclick = function() {
					let span1 = dialog.appendChild(document.createElement("span"));
					span1.style.fontSize = fontSize;
					span1.innerText = `获取中 ...`;
					span1.style.display = "block";
					const username = nameInp.value.trim();
					GM_xmlhttpRequest({
						method: 'GET',
						url: `https://api.mojang.com/users/profiles/minecraft/${username}`,
						onload: function(uuidResponse) {
							try {
								const uuidData = JSON.parse(uuidResponse.responseText);
								const uuid = uuidData.id;
								GM_xmlhttpRequest({
									method: 'GET',
									url: `https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`,
									onload: function(profileResponse) {
										try {
											const profileData = JSON.parse(profileResponse.responseText);
											const texturesProp = profileData.properties.find(p => p.name === 'textures');
											if (!texturesProp) {
												alert('未获取到皮肤');
												dialog.close();
											}
											const texturesJson = atob(texturesProp.value);
											const texturesData = JSON.parse(texturesJson);
											const skinUrl = texturesData.textures.SKIN.url;
											GM_xmlhttpRequest({
												method: "GET",
												url: skinUrl,
												responseType: "blob",
												onload: function(response) {
													const reader = new FileReader();
													reader.onloadend = function() {
														skinViewer.loadSkin(reader.result);
														skin = reader.result;
														if (isSave) {
															GM_setValue("skin", reader.result);
														}
														dialog.close();
													}
													reader.readAsDataURL(response.response);
												},
												onerror: function(e) {
													alert("皮肤加载错误")
													dialog.close();
												}
											});
										} catch (e) {
											alert(`
                                   ${ e.message.includes('default') ? e.message : 'API请求失败,无法获取皮肤信息,请检查ID是否正确,或者检查网络连接'}`);
											dialog.close();
										}
									},
									onerror: function(e) {
										alert(`API请求失败,无法获取皮肤信息,请检查ID是否正确,或者检查网络连接`);
										dialog.close();
									}
								});
							} catch (e) {
								alert(`${e.responseText ? JSON.parse(e.responseText).errorMessage : 'API请求失败,无法获取皮肤信息,请检查ID是否正确,或者检查网络连接'}
                    `);
								dialog.close();
							}
						},
						onerror: function(e) {
							alert(`${e.responseText ? JSON.parse(e.responseText).errorMessage : 'API请求失败无法获取皮肤信息,请检查ID是否正确,或者检查网络连接'}`);
							dialog.close();
						}
					});
				}
			} else {
				uploadBtn.innerText = "上传皮肤";
				uploadBtn.onclick = function() {
					uploadSkin(isSave);
				};
			}
		})
		let uploadBtn = wrap.appendChild(document.createElement("button"));
		uploadBtn.onclick = uploadSkin;
		uploadBtn.setAttribute("style", `
color:white;
background:#6F8DE1;
border:none;
outline:none;
padding:5px 10px;
border-radius:10px;
margin-top:20px;
`)
		uploadBtn.style.fontSize = fontSize;
		uploadBtn.innerText = "上传皮肤";

	}
	if (!skin) {
		createSkinPickerDialog(true, `[MC Skin] 初次使用需要上传皮肤文件`);
		dialog.showModal();
		dialog.focus();
		dialog.blur();
	}
	var opacity = GM_getValue("opacity", "0.85");
	var positionLeft;
	var positionTop;
	var w = 130;
	var h = 200;
	var positionSetting = {
		top: {
			top: 0
		},
		bottom: {
			top: `calc(100vh - ${h}px)`,
		}
	}
	var position = positionSetting.bottom;
	var canvas = document.createElement("canvas");
	canvas.style.position = "fixed";
	positionLeft = GM_getValue("positionLeft", `calc(100vw - ${w}px)`);
	positionTop = GM_getValue("positionTop", position.top);
	canvas.style.top = positionTop;
	canvas.style.left = positionLeft;
	canvas.style.zIndex = 999999999999;
	canvas.style.pointerEvents = "none";
	canvas.style.opacity = opacity;
	document.body.appendChild(canvas);
	let skinViewer = new skinview3d.SkinViewer({
		canvas: canvas,
		width: w,
		height: h,
		skin: skin
	});
	var addAnimation = function() {}
	var idleAnimation = new skinview3d.FunctionAnimation((player, pr) => {
		const t = pr * 2;
		// Arm swing
		const basicArmRotationZ = Math.PI * 0.02;
		player.skin.leftArm.rotation.z = Math.cos(t) * 0.03 + basicArmRotationZ;
		player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ;
		// Always add an angle for cape around the x axis
		const basicCapeRotationX = Math.PI * 0.06;
		player.cape.rotation.x = Math.sin(t) * 0.01 + basicCapeRotationX;
		player.rotation.y = defaultRotation;
		addAnimation(player, pr)
	});
	skinViewer.animation = idleAnimation;
	skinViewer.controls.enablePan = false;
	skinViewer.controls.enableZoom = false;
	skinViewer.controls.enableRotate = false;


	const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), -10);
	const raycaster = new THREE.Raycaster();
	const mouse = new THREE.Vector2();
	const pointOfIntersection = new THREE.Vector3();
	const head = skinViewer.playerObject.skin.head;
	var isPlayingAfkAnimation;
	var timeout0;
	var AfkAnimation = () => {
		head.rotation.x = 0;
		head.rotation.y = 0;
		head.rotation.z = 0;
		addAnimation = (pl, pr) => {
			var kT = 13.5;
			var sin0 = (x) => {
				var r = Math.pow(Math.abs(Math.sin(x)), 1 / 1.5)
				return Math.sin(x) > 0 ? r : -r
			}
			var kD = 0.25;
			var t1 = Math.abs(sin0(pr / 2 * kT))
			pl.skin.body.rotation.x = 0.4537860552 * (1 - kD * t1);
			pl.skin.body.position.z = 1.3256181 * (1 - kD * t1) - 3.4500310377 * (1 - kD * t1);
			pl.skin.body.position.y = -6 - 2.103677462 * (1 - kD * t1);
			pl.skin.head.position.y = -3.618325234674 * (1 - kD * t1);
			pl.skin.leftArm.position.z = 3.618325234674 * (1 - kD * t1) - 3.4500310377 * (1 - kD * t1);
			pl.skin.rightArm.position.z = pl.skin.leftArm.position.z;
			pl.skin.leftArm.rotation.x = 0.510367746202 * (1 - kD * t1);
			pl.skin.rightArm.rotation.x = pl.skin.leftArm.rotation.x;
			pl.skin.leftArm.rotation.z = 0.1 * (1 - kD * t1);
			pl.skin.rightArm.rotation.z = -pl.skin.leftArm.rotation.z;
			pl.skin.leftArm.position.y = -2 - 2.53943318 * (1 - kD * t1);
			pl.skin.rightArm.position.y = pl.skin.leftArm.position.y;
			pl.skin.rightLeg.position.z = -3.4500310377 * (1 - kD * t1);
			pl.skin.leftLeg.position.z = pl.skin.rightLeg.position.z;
			var mD = 1.5
			var t = sin0(pr * kT) * mD
			pl.skin.leftLeg.rotation.z = -Math.asin((pl.skin.leftLeg.position.x - 1.9) / 12)
			pl.skin.leftLeg.position.x = t + 1.9
			pl.skin.rightLeg.rotation.z = pl.skin.leftLeg.rotation.z
			pl.skin.rightLeg.position.x = t - 1.9
			pl.skin.body.position.x = t / 2
			pl.skin.leftArm.position.x = t / 2 + 5 - 0.5 * sin0(Math.max(pr - 0.25 / kT, 0) * kT)
			pl.skin.rightArm.position.x = t / 2 - 5 - 0.5 * sin0(Math.max(pr - 0.25 / kT, 0) * kT)
			pl.skin.body.rotation.z = -pl.skin.rightLeg.rotation.z
			pl.skin.leftArm.rotation.z = Math.asin(sin0(Math.max(pr - 0.25 / kT, 0) * kT) * mD / 12) + Math.PI / 18
			pl.skin.rightArm.rotation.z = pl.skin.leftArm.rotation.z - 2 * Math.PI / 18
			pl.skin.leftArm.position.y = -2.5 * Math.sin(pl.skin.leftLeg.rotation.z) - 2 - 2.53943318 * (1 - kD * Math.abs(sin0(pr / 2 * kT)));
			pl.skin.rightArm.position.y = 2.5 * Math.sin(pl.skin.rightLeg.rotation.z) - 2 - 2.53943318 * (1 - kD * Math.abs(sin0(pr / 2 * kT)));
			pl.skin.head.rotation.z = pl.skin.body.rotation.z * 1 / 3
		}
	}
	isPlayingAfkAnimation = false;
	timeout0 = setTimeout(() => {
		AfkAnimation();
		isPlayingAfkAnimation = true;
	}, 300000)
	var handleAfkAnimation = () => {
		clearTimeout(timeout0);
		if (isPlayingAfkAnimation) {
			addAnimation = () => {}
			var pl = skinViewer.playerObject;
			pl.skin.head.rotation.set(0, 0, 0);
			pl.skin.leftArm.rotation.set(0, 0, 0);
			pl.skin.rightArm.rotation.set(0, 0, 0);
			pl.skin.leftLeg.rotation.set(0, 0, 0);
			pl.skin.rightLeg.rotation.set(0, 0, 0);
			pl.skin.body.rotation.set(0, 0, 0);
			pl.skin.head.position.y = 0;
			pl.skin.body.position.y = -6;
			pl.skin.body.position.z = 0;
			pl.skin.rightArm.position.x = -5;
			pl.skin.rightArm.position.y = -2;
			pl.skin.rightArm.position.z = 0;
			pl.skin.leftArm.position.x = 5;
			pl.skin.leftArm.position.y = -2;
			pl.skin.leftArm.position.z = 0;
			pl.skin.rightLeg.position.x = -1.9;
			pl.skin.rightLeg.position.y = -12;
			pl.skin.rightLeg.position.z = -0.1;
			pl.skin.leftLeg.position.x = 1.9;
			pl.skin.leftLeg.position.y = -12;
			pl.skin.leftLeg.position.z = -0.1;
			isPlayingAfkAnimation = false;
		}
		timeout0 = setTimeout(() => {
			AfkAnimation();
			isPlayingAfkAnimation = true;
		}, 300000)
	}

	function clamp(num, min, max) {
		return num <= min ? min : num >= max ? max : num;
	}

	function stopAddedAnimation() {
		_t0 = undefined;
		_t1 = undefined;
		z0 = undefined;
		progress1 = undefined;
		progress2 = undefined;
		progress3 = undefined;
		endRotationX = undefined;
		progress4 = undefined;
		progress5 = undefined;
		endRotationXR = undefined;
		endRotationXL = undefined;
		isTimeoutSetted = true;
		clearTimeout(waveTimeout);
		addAnimation = () => {}
	}
	var waveTimeout;
	var isTimeoutSetted = false;
	var _t0;
	var _t1;
	var z0;

	function handleWaveAnimation() {
		function wave() {
			addAnimation = (player, progress) => {
				_t0 = !_t0 ? progress : _t0;
				const t = (progress - _t0) * 2.1 * Math.PI;
				if (t <= Math.PI * 4) {
					player.skin.leftArm.rotation.x = -2.21;
					player.skin.leftArm.rotation.z = Math.cos(t) * 0.5;
				} else {
					_t1 = _t1 == undefined ? progress : _t1;
					z0 = z0 == undefined ? player.skin.leftArm.rotation.z : z0;
					var t1 = Math.cos((progress - _t1) * 15);
					if (t1 < 0) {
						t1 = 0;
						stopAddedAnimation();
						return;
					}
					player.skin.leftArm.rotation.x = -2.21 * t1
					player.skin.leftArm.rotation.z = z0 * t1;

				}
			}
		}
		if (!isTimeoutSetted) {
			waveTimeout = setTimeout(wave, 800);
		}
		isTimeoutSetted = true;
	}

	function handleMove(x, y) {
		handleAfkAnimation();
		const canvasRect = canvas.getBoundingClientRect();
		mouse.x = (((x - canvasRect.left) / canvasRect.width) * 2 - 1) / (window.innerWidth / canvasRect.width);
		mouse.y = clamp((-((y - canvasRect.top) / canvasRect.height) * 2 + 1) / (window.innerHeight / canvasRect.height) + 0.4 - 0.52 / (window.innerHeight / canvasRect.height), -0.5, 0.9);
		raycaster.setFromCamera(mouse, skinViewer.camera);
		raycaster.ray.intersectPlane(plane, pointOfIntersection);
		head.lookAt(pointOfIntersection);
	}

	window.addEventListener("mousemove", e => {
		var x = e.clientX;
		var y = e.clientY;
		handleMove(x, y);
		const canvasRect = canvas.getBoundingClientRect();
		if (x >= canvasRect.left && x <= canvasRect.left + canvasRect.width && y >= canvasRect.top && y <= canvasRect.top + canvasRect.height) {
			handleWaveAnimation();
		} else {
			clearTimeout(waveTimeout);
			isTimeoutSetted = false;
		}
	});
	window.addEventListener("touchstart", e => {
		handleMove(e.targetTouches[0].clientX, e.targetTouches[0].clientY)
	});
	window.addEventListener("touchmove", e => {
		handleMove(e.targetTouches[0].clientX, e.targetTouches[0].clientY)
	});

	var progress1;
	var progress2;
	var progress3;
	var endRotationX;
	var ws;

	function handleMouseWheelEvent(event) {
		handleAfkAnimation();
		try {
			clearTimeout(ws)
		} catch (e) {}
		event = event || window.event;
		let delta = event.wheelDelta || -event.detail;
		var k = Math.pow(Math.abs(delta / 120), 1 / 3);
		if (delta > 0) {
			addAnimation = function(player, progress) {
				if (!progress1) {
					progress1 = progress;
					isTimeoutSetted = true;
					clearTimeout(waveTimeout);
				}
				progress2 = undefined;
				player.skin.rightArm.rotation.x = -0.1 + (Math.floor((progress - progress1) / (Math.PI / (13 * k))) % 2 == 0 ? (-Math.acos(Math.cos((k * 13 * (progress - progress1 - Math.PI / (13 * k))))) * 0.5) : -0.5);
				player.skin.leftArm.rotation.x = 0;
			}
		} else {
			addAnimation = function(player, progress) {
				if (!progress2) {
					progress2 = progress;
					isTimeoutSetted = true;
					clearTimeout(waveTimeout);
				}
				progress1 = undefined;
				player.skin.rightArm.rotation.x = -0.1 + ((Math.floor((progress - progress2) / (Math.PI / (6 * 2 * k))) % 2 == 0) ? (-Math.abs(Math.asin(Math.sin(6 * k * (progress - progress2)))) * 0.8) : 0);
				player.skin.leftArm.rotation.x = 0;
			}
		}
		ws = setTimeout(() => {
			addAnimation = function(player, progress) {
				if (!endRotationX) {
					endRotationX = player.skin.rightArm.rotation.x;
					progress3 = progress;
				}
				player.skin.rightArm.rotation.x = Math.min(4 * (progress - progress3) + endRotationX, 0);
				if (player.skin.rightArm.rotation.x == 0) {
					progress3 = undefined;
					endRotationX = undefined;
					stopAddedAnimation();
				}
			}
			progress1 = undefined;
			progress2 = undefined;
		}, 300)
	}
	window.onmousewheel = document.onmousewheel = handleMouseWheelEvent;
	window.onmousedown = function() {
		handleAfkAnimation();
		var progress0
		addAnimation = function(player, progress) {
			if (!progress0) {
				progress0 = progress;
				isTimeoutSetted = true;
				clearTimeout(waveTimeout);
			}
			player.rotation.y = defaultRotation;
			const t = (progress - progress0) * 20;
			player.skin.rightArm.rotation.x = -0.4537860552 * 2 + 2 * Math.sin(t + Math.PI) * 0.3;
			const basicArmRotationZ = 0.01 * Math.PI + 0.06;
			player.skin.rightArm.rotation.z = -Math.cos(t) * 0.403 + basicArmRotationZ;
			player.skin.body.rotation.y = -Math.cos(t) * 0.06;
			player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.077;
			player.skin.leftArm.rotation.z = -Math.cos(t) * 0.015 + 0.13 - 0.05;
			player.skin.leftArm.position.z = Math.cos(t) * 0.3;
			player.skin.leftArm.position.x = 5 - Math.cos(t) * 0.05;
			if (t >= Math.PI * 2) {
				player.skin.rightArm.rotation.x = 0;
				stopAddedAnimation();
			}
		}
	}
	var timeout;
	var progress4;
	var progress5;
	var time0 = -1;
	var endRotationXL;
	var endRotationXR;

	function handleInputEvent() {
		try {
			clearTimeout(timeout)
		} catch (e) {}
		var deltaTime;
		if (time0 == -1) {
			time0 = Date.now();
		} else {
			let t = Date.now();
			deltaTime = t - time0;
			time0 = t;
		}
		let k = 5 / Math.pow(deltaTime + 1, 1 / 3.6)
		k = Number.isNaN(k) ? 1 : k;
		addAnimation = function(player, progress) {
			if (!progress4) {
				progress4 = progress;
				isTimeoutSetted = true;
				clearTimeout(waveTimeout);
			}
			var pr = progress - progress4;
			player.skin.leftArm.rotation.z = -0.27;
			player.skin.rightArm.rotation.z = 0.27;
			player.skin.leftArm.rotation.x = -Math.abs(Math.PI / 6 * Math.sin(pr * 5 * k)) - 0.6;
			player.skin.rightArm.rotation.x = -Math.abs(Math.PI / 6 * Math.cos(pr * 5 * k)) - 0.6;
		}
		timeout = setTimeout(() => {
			addAnimation = function(player, progress) {
				if (!progress5) {
					progress5 = progress;
					endRotationXL = player.skin.leftArm.rotation.x;
					endRotationXR = player.skin.rightArm.rotation.x;
				}
				player.skin.leftArm.rotation.z = 0;
				player.skin.rightArm.rotation.z = 0;
				player.skin.rightArm.rotation.x = Math.min(4 * (progress - progress5) + endRotationXR, 0);
				player.skin.leftArm.rotation.x = Math.min(4 * (progress - progress5) + endRotationXL, 0);
				player.skin.rightArm.rotation.z = Math.min(4 * (progress - progress5) + 0.27, 0);
				player.skin.leftArm.rotation.z = Math.max(-4 * (progress - progress5) - 0.27, 0);
				if (player.skin.rightArm.rotation.x == 0 && player.skin.leftArm.rotation.x == 0 && player.skin.rightArm.rotation.z == 0 && player.skin.leftArm.rotation.z == 0) {
					stopAddedAnimation();
				}
			}
			progress4 = undefined;
		}, 600)
	}

	document.addEventListener('keydown', () => {
		handleAfkAnimation();
		handleInputEvent();
	});

	GM_registerMenuCommand("调整透明度", function() {
		removeAllChild(dialog)
		var d1 = dialog.appendChild(document.createElement("div"))
		d1.setAttribute("style", `padding-bottom:15px !important;
font-size:` + fontSize)
		var inp = dialog.appendChild(document.createElement("input"));
		inp.min = 0;
		inp.max = 1;
		inp.step = 0.01;
		inp.type = "range";
		inp.setAttribute("style", `height:5px !important;
  width:85% !important;
  webkit-appearance: none !important;
  accent-color:#6F8DE1 !important;
  vertical-align:middle !important;
outline:none !important;
margin-left:7.5% !important;
display:block !important;
`)
		inp.addEventListener("input", () => {
			d1.innerHTML = (inp.value * 100).toFixed() + "%";
			canvas.style.opacity = inp.value;
			opacity = inp.value;
		})
		var d2 = dialog.appendChild(document.createElement("div"))
		d2.setAttribute("style", `
margin-top:20px !important;
font-size:` + fontSize.replace(/px/, "") / 1.3 + "px")
		d2.innerText = "设置仅对本次当前网页生效,保存设置请单击菜单中 保存当前设置";
		dialog.showModal();
		dialog.focus();
		dialog.blur();
		inp.value = canvas.style.opacity;
		d1.innerHTML = (inp.value * 100).toFixed() + "%";
	})
	var moveListeners = [];
	var moveMenuId;
	var finishMoveMenuId;

	function move() {
		GM_unregisterMenuCommand(moveMenuId);
		finishMoveMenuId = GM_registerMenuCommand("完成移动", finishMove);

		function makeDraggable(element) {
			element.style.pointerEvents = "auto";
			let isDragging = false;
			let startX, startY, initialLeft, initialTop;
			const parsePosition = (type) => {
				var originalPosition = (type == "left" ? (getComputedStyle(element).left.replace(/px/, "") / window.innerWidth) * 100 + "vw" : (getComputedStyle(element).top.replace(/px/, "") / window.innerHeight) * 100 + "vh")
				const value = originalPosition;
				const match = value.match(/(-?\d+\.?\d*)v[w|h]/);
				return match ? parseFloat(match[1]) : 0;
			};
			const pxToVW = (px) => (px / window.innerWidth) * 100;
			const pxToVH = (px) => (px / window.innerHeight) * 100;
			const startDrag = (clientX, clientY) => {
				isDragging = true;
				initialLeft = parsePosition('left');
				initialTop = parsePosition('top');
				startX = clientX;
				startY = clientY;
			};
			const handleMove = (clientX, clientY) => {
				if (!isDragging) return;
				const deltaX = clientX - startX;
				const deltaY = clientY - startY;
				element.style.left = `${initialLeft + pxToVW(deltaX)}vw`;
				element.style.top = `${initialTop + pxToVH(deltaY)}vh`;
				if ((initialLeft + pxToVW(deltaX)) + pxToVW(0.5 * (getComputedStyle(element).width.replace(/px/, ""))) >= 50) {
					defaultRotation = -Math.abs(defaultRotation);
				} else {
					defaultRotation = Math.abs(defaultRotation);
				}
			};
			const addEvent = (target, type, handler) => {
				moveListeners.push({
					target: target,
					type: type,
					handler: handler
				})
				target.addEventListener(type, handler);
			}
			addEvent(element, 'mousedown', e => startDrag(e.clientX, e.clientY));
			addEvent(element, 'touchstart', e => startDrag(e.touches[0].clientX, e.touches[0].clientY));
			addEvent(document, 'mousemove', e => handleMove(e.clientX, e.clientY));
			addEvent(document, 'touchmove', e => handleMove(e.touches[0].clientX, e.touches[0].clientY));
			['mouseup', 'touchend'].forEach(type => addEvent(document, type, () => isDragging = false));
		}
		makeDraggable(canvas)
		canvas.style.border = "5px solid red";
	}

	function finishMove() {
		moveListeners.forEach((item) => {
			item.target.removeEventListener(item.type, item.handler);
			canvas.style.border = "none";
			positionLeft = canvas.style.left;
			positionTop = canvas.style.top;
		})
		moveMenuId = GM_registerMenuCommand("移动", move);
		GM_unregisterMenuCommand(finishMoveMenuId);
		canvas.style.pointerEvents = "none";
	}
	moveMenuId = GM_registerMenuCommand("移动", move);
	GM_registerMenuCommand("保存当前设置", () => {
		GM_setValue("positionLeft", positionLeft);
		GM_setValue("positionTop", positionTop);
		GM_setValue("opacity", opacity);
		GM_setValue("skin", skin);
		GM_setValue("defaultRotation", defaultRotation);
		alert(`[MC Skin]
保存成功,当前参数为:
${GM_getValue("positionLeft")?"位置:left "+GM_getValue("positionLeft")+" top:"+GM_getValue("positionTop")+"\n":""}${GM_getValue("opacity")?"透明度"+GM_getValue("opacity")+"\n":""}${GM_getValue("skin")?"皮肤"+GM_getValue("skin"):""}`)
	})
	GM_registerMenuCommand("重置当前设置", () => {
		GM_deleteValue("positionLeft");
		GM_deleteValue("positionTop");
		GM_deleteValue("opacity");
		GM_deleteValue("skin");
		GM_deleteValue("defaultRotation");
	})
	GM_registerMenuCommand("更换皮肤", function() {
		createSkinPickerDialog(false, `选择皮肤 如需保存请点击菜单中 保存当前设置`)

		dialog.showModal();
		dialog.focus();
		dialog.blur();
	})

})();