[Neko0] VRChat Avatar 无限收藏夹

无限收藏虚拟形象 Limitless Favorite Avatar

目前为 2023-03-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         [Neko0] VRChat Avatar 无限收藏夹
// @description  无限收藏虚拟形象 Limitless Favorite Avatar
// @version      1.0.0
// @author       JoJunIori
// @namespace    neko0-web-tools
// @icon         https://assets.vrchat.com/www/favicons/favicon.ico
// @homepageURL  https://github.com/nekozero/neko0-web-tools
// @supportURL   https://github.com/nekozero/neko0-web-tools/issues
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// @run-at       document-idle
// @license      AGPLv3
// @require      https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/js/solid.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/js/fontawesome.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]
// @require      https://unpkg.com/@popperjs/core@2
// @require      https://unpkg.com/tippy.js@6
// @require      https://cdn.jsdelivr.net/npm/[email protected]/build/alertify.min.js
// @resource     IMPORTED_CSS_1 https://cdn.jsdelivr.net/npm/[email protected]/build/css/alertify.rtl.min.css
// @match        *://vrchat.com/*
// @resource     IMPORTED_CSS_2 https://cdn.jsdelivr.net/gh/nekozero/[email protected]/convenience/vrchat/style.css
// @resource     html-avatar-btn https://cdn.jsdelivr.net/gh/nekozero/[email protected]/convenience/vrchat/html-avatar-btn.html
// @resource     html-avatar-list https://cdn.jsdelivr.net/gh/nekozero/[email protected]/convenience/vrchat/html-avatar-list.html
// @resource     html-btn-group https://cdn.jsdelivr.net/gh/nekozero/[email protected]/convenience/vrchat/html-btn-group.html
// @resource     language https://cdn.jsdelivr.net/gh/nekozero/[email protected]/convenience/vrchat/language.json
// ==/UserScript==

/** 初始化设定 开始 */
// 设置项默认值
let setting = {
	lang: 'zh_cn',
}

// 判断是否存在设定
if (GM_getValue('VLAF_setting') === undefined) {
	GM_setValue('VLAF_setting', setting)
} else {
	let store = GM_getValue('VLAF_setting')
	$.each(setting, function (i) {
		if (store[i] === undefined) {
			store[i] = setting[i]
		}
	})
	GM_setValue('VLAF_setting', store)
}

// 示例模型列表
let avatars = []
if (GM_getValue('VLAF_avatars') === undefined) {
	GM_setValue('VLAF_avatars', avatars)
}

/** 初始化设定 结束 */

// 提示框位置
alertify.set('notifier', 'position', 'top-center')

// 实时获取最新设置
let getSet = () => {
	return GM_getValue('VLAF_setting')
}
// 实时获取最新模型列表
let getAvtrs = () => {
	return GM_getValue('VLAF_avatars')
}

// 文本内容多语言替换
const text = JSON.parse(GM_getResourceText('language'))[getSet().lang]
console.log('text', text)

// 置入Style
GM_addStyle(GM_getResourceText('IMPORTED_CSS_1'))
GM_addStyle(GM_getResourceText('IMPORTED_CSS_2'))

// 正则替换DOM内“变量”
// From: https://gist.github.com/cybercase/2298e242e82d32b15787
if (!String.prototype.format) {
	String.prototype.format = function (dict) {
		return this.replace(/{(\w+)}/g, function (match, key) {
			return typeof dict[key] !== 'undefined' ? dict[key] : match
		})
	}
}

// 左侧导航栏
;(function () {
	// 置入DOM
	function domBtnGroup() {
		let html = GM_getResourceText('html-btn-group')
		let output = html.format(text)
		$('.leftbar .btn-group-vertical').prepend(output)
	}
	// 检测页面内容置入插件DOM
	var timer = setInterval(detection, 300)
	detection()
	function detection() {
		var neko0 = document.querySelector('.limitless')
		if (!neko0) {
			domBtnGroup()
		} else {
			clearInterval(timer)
			alertify.success(text.mounted)
		}
	}
})()

// 判断已收藏
let isInVLAF = avtr_id => {
	let store = getAvtrs()
	return store.find(obj => obj.id === avtr_id)
}
// 格式化当前时间
let getNowDate = () => {
	// 定义一个函数来补齐两位数
	function pad(num) {
		return num < 10 ? '0' + num : num
	}

	// 获取当前时间的 Date 对象
	let date = new Date()

	// 获取年月日时分秒毫秒
	let year = date.getFullYear()
	let month = pad(date.getMonth() + 1)
	let day = pad(date.getDate())
	let hour = pad(date.getHours())
	let minute = pad(date.getMinutes())
	let second = pad(date.getSeconds())
	let millisecond = pad(date.getMilliseconds())

	// 拼接成 2022-07-19T20:50:50.033Z 这种格式
	let formatted = `${year}-${month}-${day}T${hour}:${minute}:${second}.${millisecond}Z`

	// 打印结果
	return formatted
}

// 马上切换
let select = avtr_id => {
	url = window.location.origin + '/api/1/avatars/' + avtr_id + '/select'
	axios
		.put(url)
		.then(function (response) {
			console.log(response)
			alertify.success(text.operation_succeeded)
		})
		.catch(function (error) {
			console.log(error)
			alertify.error(text.operation_failed)
		})
		.finally(function () {})
}
// 收藏到系统收藏夹
let favorites = avtr_id => {
	url = window.location.origin + '/api/1/favorites'
	val = {
		type: 'avatar',
		favoriteId: avtr_id,
		tags: ['avatars1'],
	}
	axios
		.post(url, val)
		.then(function (response) {
			console.log(response)
			alertify.success(text.operation_succeeded)
		})
		.catch(function (error) {
			console.log(error)
			let msg = error.response.data.error.message
			let avatars_full = msg === "You already have 50 favorite avatars in group 'avatars1'"
			let avatars_added = msg === 'You already have that avatar favorited'
			if (avatars_full) {
				alertify.error(text.avatars_full)
			} else if (avatars_added) {
				alertify.warning(text.avatars_added)
			} else {
				alertify.error(text.operation_failed)
			}
		})
		.finally(function () {})
}
// 收藏到无限收藏夹
let limitless = avtr_id => {
	url = window.location.origin + '/api/1/avatars/' + avtr_id
	axios
		.get(url)
		.then(function (response) {
			console.log('limitless', response)
			alertify.success(text.operation_succeeded)
			let data = response.data

			let store = getAvtrs()
			const result = isInVLAF(avtr_id)

			if (result) {
				console.log('存在')
				store = store.filter(function (obj) {
					return obj.id !== avtr_id
				})
				$('#collect').text(text.btn_collect).removeClass('text-danger border-danger')
			} else {
				console.log('不存在')
				data.addTime = getNowDate()
				store.push(data)
				$('#collect').text(text.btn_collect_r).addClass('text-danger border-danger')
			}
			GM_setValue('VLAF_avatars', store)
		})
		.catch(function (error) {
			console.log(error)
		})
		.finally(function () {})
}

// 不同页面
let page_is_avtr_own = document.location.pathname === '/home/avatars'
let page_is_avtr_details = document.location.pathname.indexOf('/home/avatar/avtr_') !== -1
let page_is_limitless = document.location.pathname === '/home/limitless'

if (page_is_avtr_own) {
	console.log('page_is_avtr_own')
	// 当前使用Avatar
	// let current_avtr_id = document.querySelector('[data-scrollkey]').getAttribute('data-scrollkey')
	// console.log(current_avtr_id)
	// let current_avtr_info = null
	// ;(function () {
	// 	url =
	// 		'https://vrchat.com/api/1/users/' +
	// 		document.querySelector('[aria-label="User Status"]').getAttribute('href').substring(11) +
	// 		'/avatar'
	// 	axios
	// 		.get(url)
	// 		.then(function (response) {
	// 			console.log(response)
	// 			current_avtr_info = response.data
	// 		})
	// 		.catch(function (error) {
	// 			console.log(error)
	// 		})
	// 		.finally(function () {

	// 		})
	// })()
	// 算了暂时先不改这个
} else if (page_is_avtr_details) {
	// 当前浏览Avatar
	let current_avtr_id = window.location.pathname.substring(13)

	console.log('page_is_avtr_details', isInVLAF(current_avtr_id), getAvtrs())

	// 置入DOM
	function domAvatar() {
		let html = GM_getResourceText('html-avatar-btn')
		let output = html.format(text)
		$('.col-xs-12.content-scroll  .home-content .row:nth-child(2) .col-4 .btn-group-vertical')
			.attr('id', 'neko0')
			.append(output)
		if (isInVLAF(current_avtr_id)) {
			$('#collect').text(text.btn_collect_r).addClass('text-danger border-danger')
		}

		tippy('#transmit', {
			content: text.tippy_transmit,
		})
		tippy('#use', {
			content: text.tippy_use,
		})
		tippy('#collect', {
			content: text.tippy_collect,
		})

		$('#transmit').click(() => {
			favorites(current_avtr_id)
		})
		$('#use').click(() => {
			select(current_avtr_id)
		})
		$('#collect').click(() => {
			limitless(current_avtr_id)
		})
	}

	// 监测页面变换
	const _historyWrap = function (type) {
		const orig = history[type]
		const e = new Event(type)
		return function () {
			const rv = orig.apply(this, arguments)
			e.arguments = arguments
			window.dispatchEvent(e)
			return rv
		}
	}
	history.pushState = _historyWrap('pushState')
	history.replaceState = _historyWrap('replaceState')
	window.addEventListener('pushState', function (e) {
		console.log('change pushState')
	})
	window.addEventListener('replaceState', function (e) {
		console.log('change replaceState')
		detection()
	})

	// 绑定点击事件
	// 打开设置窗口
	// $('.n-box .button.switch').click(() => {
	// 	$('.n-box').toggleClass('open')
	// })

	// setTimeout(() => {
	// alertify.success("You've clicked OK")
	// window.alertify = alertify
	// console.log('alertify')
	// }, 1000)

	// 检测页面内容置入插件DOM
	var timer = setInterval(detection, 300)
	detection()
	function detection() {
		var neko0 = document.querySelector('.neko0')
		if (!neko0) {
			domAvatar()
		} else {
			clearInterval(timer)
		}
	}
	console.log(text.mounted)
} else if (page_is_limitless) {
	console.log('page_is_limitless', getAvtrs())
	// 置入DOM
	function domLimitless() {
		let html = GM_getResourceText('html-avatar-list')
		let output = html
		$('.home-content').append(output)

		new Vue({
			el: '#neko0',
			data: {
				text: text,
				items: getAvtrs(),
			},
			methods: {
				formattedDate: function (str) {
					const dateStr = str
					const date = new Date(dateStr)
					const year = date.getFullYear()
					const month = date.getMonth() + 1
					const day = date.getDate()
					const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day
						.toString()
						.padStart(2, '0')}`
					console.log(formattedDate)
					return formattedDate
				},
				hasWindows: function (obj) {
					// 定义一个变量来存储检查结果
					let hasWindows = false

					// 遍历对象中的 unityPackages 数组
					for (let package of obj.unityPackages) {
						// 如果某个元素的 platform 属性等于 standalonewindows,就将结果设为 true,并跳出循环
						if (package.platform === 'standalonewindows') {
							hasWindows = true
							break
						}
					}

					return hasWindows
				},
				hasAndroid: function (obj) {
					// 定义一个变量来存储检查结果
					let hasAndroid = false

					// 遍历对象中的 unityPackages 数组
					for (let package of obj.unityPackages) {
						// 如果某个元素的 platform 属性等于 android,就将结果设为 true,并跳出循环
						if (package.platform === 'android') {
							hasAndroid = true
							break
						}
					}

					return hasAndroid
				},
				favorites: function (avtr_id) {
					favorites(avtr_id)
				},
				select: function (avtr_id) {
					select(avtr_id)
				},
				limitless: function (avtr_id) {
					limitless(avtr_id)
					$('[dat-a="' + avtr_id + '"]')
						.parents('.avatar-li')
						.remove()
				},
			},
			created: function () {
				let _this = this
				window.add_data = _this.add_data
			},

			mounted() {
				tippy('.transmit', {
					content: text.tippy_transmit,
				})
				tippy('.use', {
					content: text.tippy_use,
				})
				tippy('.collect', {
					content: text.tippy_collect,
				})
			},
		})
	}

	// 检测页面内容置入插件DOM
	var timer = setInterval(detection, 300)
	detection()
	function detection() {
		var neko0 = document.querySelector('.neko0')
		if (!neko0) {
			domLimitless()
		} else {
			clearInterval(timer)
		}
	}
	console.log(text.mounted)
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址