// ==UserScript== // @name 清水河畔表情包计划 // @namespace http://tampermonkey.net/ // @version 0.3.8 // @description 请自己研究尝试 // @author DARK-FLAME-MASTER FROM RIVERSIDE // @match *://*.uestc.edu.cn/forum.php?mod=viewthread* // @match *://*.uestc.edu.cn/forum.php?mod=post* // @match *://*.uestc.edu.cn/home.php?mod=space&do=pm&subop=view* // @icon https://www.google.com/s2/favicons?sz=64&domain=uestc.edu.cn // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.slim.min.js // @require https://cdn.jsdelivr.net/npm/cfb@1.2.2/dist/cfb.min.js // @require https://cdn.jsdelivr.net/npm/vue@3.2.41/dist/vue.global.min.js // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.5.0/jszip.min.js // @license WTFPL // @run-at document-body // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant unsafeWindow // @downloadURL https://update.greasyfork.icu/scripts/452543/%E6%B8%85%E6%B0%B4%E6%B2%B3%E7%95%94%E8%A1%A8%E6%83%85%E5%8C%85%E8%AE%A1%E5%88%92.user.js // @updateURL https://update.greasyfork.icu/scripts/452543/%E6%B8%85%E6%B0%B4%E6%B2%B3%E7%95%94%E8%A1%A8%E6%83%85%E5%8C%85%E8%AE%A1%E5%88%92.meta.js // ==/UserScript== (function () { 'use strict'; unsafeWindow.Vue = Vue let setAddedImg; let storageVar = "emojiSet"; let storageAlbum = "emojiAlbum" let storageGroup = "selectedGroup" let defaultEmoji = [{ tag: "default", tagImg: { id: 'default', src: 'data/attachment/common/star/common_25_icon.png' }, images: [] }] //GM_setValue('version','0.1') let version = GM_getValue('version', 'NULL') if (version != '0.3' && version != 'NULL') { let emoji_set = GM_getValue(storageVar) for (let group of emoji_set) for (let imgs of group.images) imgs.size = 0 GM_setValue(storageVar, emoji_set) } GM_setValue('version', '0.3') document.addEventListener('DOMContentLoaded', function () { let post_params = unsafeWindow.upload?unsafeWindow.upload.settings.post_params:undefined; let emoji = GM_getValue(storageVar, defaultEmoji) let emojiAlbum = GM_getValue(storageAlbum, -1) let selectedGroup = GM_getValue(storageGroup, 0) let formhash = $('#modactions :nth-child(1)').attr('value'); $('body').prepend(`
🤤
{{emoji[selectedGroup].tag}}
🕊️
⚙️
🕊️
♻️
🌟
🔎
🗃️
👷
` ) let app = Vue.createApp({ data() { return { //version, menu: { left: 0, top: 0, visible: false }, exportProgress:0, emojiSize: [64, 128, 512], emojiVisible: false, emojiChangePosInfo: { index: -1, flag: false }, settingVisible: false, addedImg: { id: 'default', src: 'data/attachment/common/star/common_25_icon.png' }, uploadTotalCount: 0, uploadNowCount: 0, renameGroup: false, emojiAlbum: emojiAlbum, selectedGroup: selectedGroup < 0 ? 0 : selectedGroup, selectedEmoji: -1, formhash: formhash, post_params: post_params, emojiTemp: [], emoji: emoji.length == 0 ? defaultEmoji : emoji } }, methods: { switchTag(index) { this.selectedGroup = index }, switchEmoji(index) { this.selectedEmoji = index }, delEmoji(index, group = this.selectedGroup) { this.emoji[group].images.splice(index, 1) }, addEmoji(emoji, group = this.selectedGroup, notice = false) { this.emoji[group].images.unshift(emoji) if (notice) this.notice(`已将表情添加至${this.emoji[group].tag}`) }, addGroup(group) { this.emoji.push(group) return this.emoji.length - 1 }, delGroup(index) { if (confirm(`您确定要删除分组${this.emoji[index].tag}吗?`)) { if (index == this.emoji.length - 1 && index != 0) this.selectedGroup -= 1 this.emoji.splice(index, 1) this.notice("已删除该分组") } }, emoAsTagImg(img, group = this.selectedGroup) { this.emoji[group].tagImg = img }, changeEmoSize(index) { let imgs = this.emoji[this.selectedGroup].images imgs[index].size = (imgs[index].size + 1) % this.emojiSize.length }, changeEmoPos(index, event) { this.emojiChangePosInfo.index = index this.emojiChangePosInfo.flag = true }, changeEmoPosInGroup(index, event) { let images = this.emoji[this.selectedGroup].images if (event.deltaY < -100) if (index != 0) { images[index] = images.splice(index - 1, 1, images[index])[0]; this.emojiChangePosInfo.index-- } if (event.deltaY > 100) if (index != images.length - 1) { images[index] = images.splice(index + 1, 1, images[index])[0]; this.emojiChangePosInfo.index++ } }, insertEmojiInForum(emoji, event) { let img = event.currentTarget let h = img.naturalHeight let w = img.naturalWidth let size = this.emojiSize[emoji.size] let min = Math.max(Math.min(h, w), size) h = Math.ceil(h / min * size) w = Math.ceil(w / min * size) eval(`if(typeof insertHrImage == 'undefined') if(document.getElementById('pmform') != null) seditor_insertunit('reply','[img=${w},${h}]${'https://bbs.uestc.edu.cn/'+emoji.src}[/img]') else seditor_insertunit(document.getElementById('postat') != null ? 'post' : 'fastpost' ,'[img=${w},${h}]${emoji.src}[/img]') else insertText('') `) }, notice(message) { Notification.requestPermission().then((result) => { if (result === 'granted') { let n = new Notification(message); setTimeout(n.close.bind(n), 1800) } }) }, setEmojiAlbum() { emojiAlbum = prompt('请输入表情保存的相册ID:') if (emojiAlbum != null) { this.emojiAlbum = emojiAlbum GM_setValue(storageAlbum, emojiAlbum) notice("相册设置成功") } }, rebuildEmojiByAlbum(params) { let uid = params.split(' ')[0] let album = params.split(' ')[1] let group = this.addGroup({ tag: album, tagImg: { id: 'default', src: 'data/attachment/common/star/common_25_icon.png' }, images: [] }) fetch(`/home.php?mod=space&uid=${uid}&do=album&id=${album}`) .then(data => data.text()) .then(data => { let regex = /共 (\d*) 张图片/ let num = parseInt(data.match(regex)[1]) for (let i = 0; i <= (num - 1) / 20; ++i) { fetch(`/home.php?mod=space&uid=${uid}&do=album&id=${album}&page=${i + 1}`) .then(data => data.text()) .then(data => { let doc = new DOMParser().parseFromString(data, 'text/html'); if (i != parseInt(num / 20)) { for (let j = 1; j <= 20; ++j) this.addEmoji({ id: i * 20 + j, src: doc.querySelector(`#ct > div.mn > div > div.bm_c > ul > li:nth-child(${j}) > a > img`).src, size: 0 }, group) } else { for (let j = 1; j <= num % 20; ++j) this.addEmoji({ id: i * 20 + j, src: doc.querySelector(`#ct > div.mn > div > div.bm_c > ul > li:nth-child(${j}) > a > img`).src, size: 0 }, group) } }) } }) }, getFiles(event) { return event.target.files }, uploadFiles(files, group) { this.uploadTotalCount += 2 * files.length; for (let i = 0; i < files.length; ++i) { let file = { name: files[i].name, size: files[i].size, type: files[i].type, dom: files[i], }; let data = new FormData(); for (let k in this.post_params) data.append(k, this.post_params[k]); data.append('type', 'image'); data.append('filetype', file.type) data.append('Filename', file.name); data.append('Filedata', file.dom); let pid, src; fetch("/misc.php?mod=swfupload&action=swfupload&operation=album", { "headers": { }, "method": "POST", "mode": "cors", "body": data, "credentials": "include", }).then((res) => res.json()).then((data) => { this.uploadNowCount += 1; pid = data.picid; src = data.bigimg; return fetch("/home.php?mod=spacecp&ac=upload", { "headers": { "content-type": "application/x-www-form-urlencoded", }, "body": "title[" + pid + "]=&albumid=" + this.emojiAlbum + "&albumsubmit=true&albumsubmit_btn=true&formhash=" + this.formhash, "method": "POST", "mode": "cors", "credentials": "include" }) }).then((data) => { this.emojiTemp.push({ group: group, img: { id: pid, src: src, size: 0 } }); this.uploadNowCount += 1; }) } }, uploadEif(files, allowedExts = []) { let f = new FileReader() f.readAsBinaryString(files[0]); let addGroup = this.addGroup let uploadFiles = this.uploadFiles f.onload = function () { let eif = CFB.read(this.result, { type: 'binary' }) let validPaths = [] let validContent = eif.FileIndex.filter((e, i) => { if (e.size > 0) { validPaths.push(eif.FullPaths[i]) return true } return false }) let s = new Set() let fileList = {} for (let idx in validContent) { let entry = validContent[idx] let extName = entry.name.substring(entry.name.lastIndexOf(".") + 1) if (entry.type == 2 && (!allowedExts || allowedExts.includes(extName))) { let name = validPaths[idx].substring(0, validPaths[idx].lastIndexOf(".")) if (!s.has(name)) { let group = validPaths[idx].match(/Entry\/(\d+)\//)[1] let f = new File([new Uint8Array(entry.content)], entry.name, { type: "image/" + extName }) if (fileList[group]) { fileList[group].push(f) } else { fileList[group] = [f] } s.add(name + 'fix') } } } for (let group in fileList) { uploadFiles(fileList[group], addGroup({ tag: group, tagImg: { id: 'default', src: 'data/attachment/common/star/common_25_icon.png' }, images: [] })) } } }, uploadJson(files) { const reader = new FileReader() reader.readAsText(files[0], 'UTF-8') reader.onload = function (e) { const emojis = JSON.parse(e.target.result) emoji.push(...emojis) GM_setValue(storageVar, emoji) } }, exportAllJson() { saveAs(new Blob([JSON.stringify(this.emoji)], { type: "text/plain;charset=utf-8" }), '总.json') }, exportGroupJson(group) { saveAs(new Blob([JSON.stringify([this.emoji[group]])], { type: "text/plain;charset=utf-8" }), this.emoji[group].tag + '.json') }, exportAll() { let zip = new JSZip() let createFile = (emoji, tag) => fetch(emoji.src).then(data => data.blob()).then(img => zip.folder(tag).file(emoji.id, img, { binary: true })) let createFiles = this.emoji.map((group) => group.images.map(emoji => createFile(emoji, group.tag))).reduce((now, group) => now.concat(group), []) Promise.all(createFiles) .then(() => zip.generateAsync({ type: "blob" }, (metadata) =>this.exportProgress = metadata.percent/100)) .then(content => saveAs(content, '总.zip')) .then(()=>this.exportProgress = 0) }, exportGroup(group) { let zip = new JSZip() let createFile = emoji => fetch(emoji.src).then(data => data.blob()).then(img => zip.file(emoji.id+emoji.src.substring(emoji.src.lastIndexOf(".")), img, { binary: true })) var createFiles = this.emoji[group].images.map(emoji => createFile(emoji)) Promise.all(createFiles) .then(() => zip.generateAsync({ type: "blob" },(metadata) =>this.exportProgress = metadata.percent/100)) .then(content => saveAs(content, this.emoji[group].tag + '.zip')) .then(()=>this.exportProgress = 0) }, /* exportAllEif() { let eif = CFB.utils.cfb_new() let createFile = (emoji, tag) => fetch(emoji.src).then(data => data.blob()).then(data => data.arrayBuffer()).then(img => CFB.utils.cfb_add(eif, tag + '/' +emoji.id, new Uint8Array(img) )) let createFiles = this.emoji.map((group) => group.images.map(emoji => createFile(emoji, group.tag))).reduce((now, group) => now.concat(group), []) Promise.all(createFiles) .then(() => CFB.write(eif,{type: 'binary'})) .then(content => saveAs(new Blob([content]), '总.eif')) }, exportGroupEif(group) { let eif = CFB.utils.cfb_new() let createFile = emoji => fetch(emoji.src).then(data => data.blob()).then(data => data.arrayBuffer()).then(img => CFB.utils.cfb_add(eif, emoji.id, new Uint8Array(img) , {unsafe:true} )) var createFiles = this.emoji[group].images.map(emoji => createFile(emoji)) Promise.all(createFiles) .then(() => CFB.write(eif,{type: 'binary'})) .then(content => saveAs(new Blob([content]), this.emoji[group].tag + '.eif')) }, */ setAddedImg(img) { this.addedImg = img }, openMenu(e, item) { this.rightClickItem = item; let x = e.pageX; let y = e.pageY; this.menu.top = y; this.menu.left = x; this.menu.visible = true; }, closeMenu() { this.menu.visible = false; }, closeSetting() { this.settingVisible = false; }, }, watch: { uploadNowCount(newCount) { if (newCount == this.uploadTotalCount) { for (let data of this.emojiTemp) { this.addEmoji(data.img, data.group) } this.emojiTemp = [] this.notice("表情上传完成") this.uploadNowCount = 0 this.uploadTotalCount = 0 } }, 'menu.visible'(value) { if (value) { document.body.addEventListener('click', this.closeMenu) } else { document.body.removeEventListener('click', this.closeMenu) } }, /* settingVisible(value) { if (value) { document.body.addEventListener('click', this.closeSetting) } else { document.body.removeEventListener('click', this.closeSetting) } }, */ selectedGroup(newIndex) { GM_setValue(storageGroup, newIndex) }, emoji: { handler(newEmoji, oldEmoji) { if (newEmoji.length == 0) this.emoji = newEmoji = defaultEmoji GM_setValue(storageVar, newEmoji) }, deep: true } }, mounted() { setInterval(() => console.log("运行中"), 1000) this.prompt = prompt setAddedImg = this.setAddedImg console.log(this.emoji) }, computed: { progress() { return this.uploadTotalCount != 0 ? this.uploadNowCount / this.uploadTotalCount : 0 }, position() { return this.emojiVisible ? { left: this.$refs.mine.getBoundingClientRect().left + 'px', bottom: unsafeWindow.innerHeight - this.$refs.mine.getBoundingClientRect().top - document.documentElement.scrollTop + 'px' } : {} }, } }); app.component('progresscircle', { props:['progress','size'], template:` {{parseInt(progress*100)}}% `, /*data() { return { progress: 0, size: 55, } }, render() { return h('svg', { width: size, height: size, viewBox: "0 0 200 200" }, [ h('circle', { id: "circleBg", transform: "rotate(-90 100 100)", cx: "100", cy: "100", r: "70", fill: "none", 'stroke-width': "30", stroke: "gray", 'stroke-dasharray': "434", 'stroke-dashoffset': 434 - progress * 434 }), h('circle', { id: "circle", 'stroke-linecap': "round", transform: "rotate(-90 100 100)", cx: "100", cy: "100", r: "70", fill: "none", 'stroke-width': "30", stroke: "green", 'stroke-dasharray': "434", 'stroke-dashoffset': 434 - progress * 434 }), h('text', { x: "100", y: "100", fill: "#6b778c", 'text-anchor': "middle", 'dominant-baseline': "central", 'font-size': "32" }, [h('tspan', { id: "percent", innerHTML: parseInt(progress * 100) })]) ]) } */ }) app.mount('#emojiTemplate') }) function makeReplyEmoji() { document.addEventListener('DOMContentLoaded', () => $('#fastpostat').before($('#mine'))) setTimeout(function () { $('.t_f>img.zoom').each(function (i) { $(this.previousSibling).after($('
').append($(this))) }) $('ignore_js_op>img').each(function (i) { $(this.previousSibling).after($('
').append($(this))) }) $('.mbn>img').each(function (i) { $(this.previousSibling).after($('
').append($(this))) }) $('.emojiImg').mouseenter(function () { let length = Math.min(this.firstChild.clientWidth, this.firstChild.clientHeight) $('#likeEmo').css({ 'font-size': length * 0.15 + 'px', 'left': 0, 'top': -this.firstChild.clientHeight + 10 + 'px', 'position': 'absolute', 'cursor': 'pointer', 'display': 'inline', }) setAddedImg({ id: $(this.firstChild).attr('id'), src: $(this.firstChild).attr('src'), size: 0 }) $(this).append($('#likeEmo')) }) $('.emojiImg').mouseleave(function () { $('#likeEmo').css('display', 'none') $('#mine_menu').append($('#likeEmo')) }) $('#fastpostsubmit').click(function () { if (!$('#mine_menu')[0].style.display) $('#mine').trigger('click') }) }, 500) return Promise.resolve() } function makePostEmoji() { document.addEventListener('DOMContentLoaded', () => { $("#e_sml").after($('#mine')) $('#mine').css('font-size', '25px') }) return Promise.resolve() } function makePrivateEmoji() { document.addEventListener('DOMContentLoaded', () => { $("#replysml").after($('#mine')) $('#mine').css('font-size', '15px') }) return Promise.resolve() } let trueHideWindow = unsafeWindow.hideWindow unsafeWindow.hideWindow = function (k, all, clear) { if (k == 'reply') { $('#fastpostat').before($('#mine')) if (!$('#mine_menu')[0].style.display) $('#mine').trigger('click') } trueHideWindow(k, all, clear) } let trueShowMenu = unsafeWindow.showMenu unsafeWindow.showMenu = function (v) { trueShowMenu(v) if (v.menuid == 'fwin_reply') $('#postat').before($('#mine')) } let trueDoane = unsafeWindow.doane; unsafeWindow.doane = function (e, preventDefault, stopPropagation) { stopPropagation = isUndefined(stopPropagation) ? 0 : stopPropagation; return trueDoane(e, preventDefault, stopPropagation) } let emojiHandler = [ { regex: /https?:\/\/(?:bbs\.uestc\.edu\.cn|bbs-uestc-edu-cn-s\.vpn\.uestc\.edu\.cn(?::8118)?)\/forum\.php\?mod=viewthread.*/i, handler: makeReplyEmoji, }, { regex: /https?:\/\/(?:bbs\.uestc\.edu\.cn|bbs-uestc-edu-cn-s\.vpn\.uestc\.edu\.cn(?::8118)?)\/forum\.php\?mod=post.*/i, handler: makePostEmoji, }, { regex: /https?:\/\/(?:bbs\.uestc\.edu\.cn|bbs-uestc-edu-cn-s\.vpn\.uestc\.edu\.cn(?::8118)?)\/home\.php\?mod=space.*/i, handler: makePrivateEmoji, }, ] function matchHandlers(url) { for (let { regex, handler } of emojiHandler) { let match = url.match(regex); if (match) { return handler(); } } return Promise.reject(); } matchHandlers(unsafeWindow.location.href) //setInterval(function(){$('#postat').before($('#mine'))},1000) })();