// ==UserScript== // @name MT论坛移动端网页版增强 // @namespace https://bbs.binmt.cc/ // @version 1.0.21 // @description 在侧边栏注入 12项功能独立开关 // @match https://bbs.binmt.cc/* // @match http://bbs.binmt.cc/* // @connect img.binmt.cc // @connect https://img.binmt.cc // @connect icdn.binmt.cc // @connect https://icdn.binmt.cc // @connect bbs.binmt.cc // @connect https://bbs.binmt.cc // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @run-at document-idle // ==/UserScript== (function () { 'use strict'; var S = { get: function(k,d){ try{var v=(typeof GM_getValue==='function')?GM_getValue(k,null):null; if(v==null)v=localStorage.getItem('mt_'+k); return (v==null)?d:v;}catch(e){return d;} }, set: function(k,v){ try{if(typeof GM_setValue==='function')GM_setValue(k,v);}catch(e){} try{localStorage.setItem('mt_'+k,v);}catch(e){} } }; // 活动文档上下文:UBB 相关函数全部通过 MTDOC/MTWIN 定位元素, // 默认等于本脚本所在 window(顶层页或 iframe 自身)。父页向小窗 iframe 注入时, // 会临时把这些切换到 iframe 的 document/window,从而复用同一套完整逻辑(弹窗/上传/重写按钮)。 var MTDOC=document, MTWIN=window; var FEATS=[ {key:'autoSign',name:'自动签到',desc:'打开网页自动签到'}, {key:'urlLink',name:'URL超链接',desc:'链接自动转超链接'}, {key:'copyCode',name:'代码复制按钮',desc:'代码块一键复制'}, {key:'autoPage',name:'评论自动上下页',desc:'自动加载上下页评论'}, {key:'guideNext',name:'导读自动下一页',desc:'滚动到底自动加载'}, {key:'autoReply',name:'隐藏帖自动回复',desc:'自动回复并解锁'}, {key:'hideOnly',name:'只看隐藏贴',desc:'仅显示含隐藏内容帖',def:false}, {key:'personalBlack',name:'个人小黑屋',desc:'按uid隐藏帖子',def:false}, {key:'ubbBar',name:'UBB快捷输入栏',desc:'论坛输入时显示UBB代码按钮'}, {key:'autoExpand',name:'帖子自动展开',desc:'自动展开帖子完整内容',hide:true}, {key:'latestPost',name:'导读最新帖子',desc:'导读顶部显示最新发布帖子',hide:true}, {key:'smallWindow',name:'小窗浏览帖子',desc:'点击帖子从底部滑入打开'}, {key:'autoOpenRebox',name:'小窗自动展开回复框',desc:'小窗打开后自动展开底部回复框(已废弃)',def:false,hide:true}, {key:'replyRedirect',name:'回复跳独立页',desc:'点击回复直接进入独立回复页(非源脚本功能,默认关)',def:false,hide:true} ]; function defOf(k){ for(var i=0;i=0;i--){ var cur=list[i]; if(cur===node||(cur.contains&&cur.contains(node)))return; if(node.contains&&node.contains(cur))list.splice(i,1); } list.push(node); } function collectMutationRoots(muts){ var out=[]; for(var i=0;i()()【】\[\]]+)/gi; for(var k=0;klast)frag.appendChild(document.createTextNode(txt.slice(last,e0))); last=e0; if(m.index===re.lastIndex)re.lastIndex++; continue; } var at=m.index+pre.length; if(at>last)frag.appendChild(document.createTextNode(txt.slice(last,at))); var a=document.createElement('a'); a.setAttribute('href',url); a.setAttribute('target','_blank'); a.setAttribute('data-mt-url','1'); a.textContent=url; a.className='mt-url-local'; frag.appendChild(a); last=m.index+m[0].length; if(m.index===re.lastIndex)re.lastIndex++; } if(last=0;i--){if(j[i].parentNode)j[i].parentNode.removeChild(j[i]);} var s=''; try{s=clone.textContent||'';}catch(e){} s=s.replace(/\u00a0/g,' '); return s.split('\n').filter(function(L){var x=L.replace(/\s/g,'');return !/^(本帖隐藏的内容|隐藏的内容|回复可见|回复后可见|登录后可见|登录可见|购买后可见|以下内容需要回复才能看到)[::]?$/.test(x);}).join('\n'); } function collectBlocks(root){ var picked=[]; function push(b){ if(!b||!b.nodeName)return; if(b.nodeName==='SCRIPT'||b.nodeName==='STYLE')return; if(b.getAttribute&&b.getAttribute('data-mt-copy')==='1')return; if(b.nodeName==='BLOCKQUOTE'||(b.closest&&b.closest('blockquote')))return; for(var k=0;k0)return p;} } else { try{var q=new URLSearchParams(location.search); var pg=q.get('page'); pg=parseInt(pg||'1',10); if(isFinite(pg)&&pg>0)return pg;}catch(e){} } var sel=document.querySelector('#dumppage'); if(sel&&sel.selectedIndex>=0)return sel.selectedIndex+1; return 1; } function totalPage(){ var sel=document.querySelector('#dumppage'); if(sel&&sel.options){return Math.max(1,sel.options.length);} var as=document.querySelectorAll('.comiis_page a'); return 1; } // ---- 构造指定页 URL(主用手段,稳定可靠) ---- // 与原页 URL 格式保持一致:伪静态用 thread-{tid}-{p}-1.html,动态用 forum.php?mod=viewthread&tid={tid}&page={p} function pageUrl(p){ if(isStatic) return 'https://bbs.binmt.cc/thread-'+st.tid+'-'+p+'-1.html'; var q=new URLSearchParams(location.search); var extra=[]; // 保留 mobile / from / 等其它参数,避免与原页不一致 for(var key of ['mobile','from','mobile2']){ if(q.get(key)) extra.push(key+'='+encodeURIComponent(q.get(key))); } var s='forum.php?mod=viewthread&tid='+encodeURIComponent(st.tid)+'&page='+p; if(extra.length)s+='&'+extra.join('&'); return new URL(s,location.origin).href; } // ---- 分页条里所有 (按顺序:上一页 / 页码select / 下一页),仅作兜底 ---- function pagerLinks(){ var box=document.querySelector(MULTI_SEL); if(!box)return []; var as=box.querySelectorAll('a.bg_f.b_ok'); var out=[]; for(var i=0;ist.total)return ''; var links=pagerLinks(); for(var i=0;i200)txt=txt.slice(0,200); return 'hash:'+txt; } function existingPids(){ var o={}; var t=target(); if(!t)return o; var ns=t.querySelectorAll(ITEM_SEL); for(var i=0;i=st.total)return; if(Date.now()-lastLoadAt0 && anchor){ var newTop=anchor.getBoundingClientRect().top+(window.pageYOffset||document.documentElement.scrollTop||0)-offset; window.scrollTo(0, newTop); } else if(inserted>0){ // 无锚点时兜底:保持原滚动位置不变(不额外跳动) window.scrollTo(0, oldScroll); } prevCool=Date.now(); // 仅成功前插后才进入冷却,避免「未加载成功却锁死」 }).catch(function(){busy=false;}); } // ---- 滚动触发 ---- var raf=null; function schedule(){ if(raf)return; raf=requestAnimationFrame(function(){ raf=null; var d=document.documentElement; var atBottom=(d.scrollHeight-d.scrollTop-d.clientHeight)<500; // 放宽顶部阈值:锚点可能把 scrollTop 停在中部,但只要列表顶距视口足够近就算「靠近顶部」 var listTopAt=(function(){ var t=target(); return t?t.getBoundingClientRect().top:Infinity; })(); var atTop=(d.scrollTop<200) || (listTopAt>-300 && listTopAt<300); var cooled=(Date.now()-prevCool)<400; // 前插后 400ms 内抑制向上加载,防止连环翻页又不过度锁死 // 独立判断(不再用 else if 互斥):靠近底部加载下一页,靠近顶部加载上一页, // 避免「锚点居中时 atBottom 吞掉向上加载」的问题 if(atBottom)loadNext(); if(atTop && !cooled)loadPrev(); }); } window.addEventListener('scroll',schedule,{passive:true}); // 补充触发点:手指/滚动停止(touchend、scroll 静止)后再判断一次, // 修复「快速滑动在顶部停留过短而错过 rAF 触发窗口」的问题 function settle(){ clearTimeout(window.__mtSettle); window.__mtSettle=setTimeout(function(){ schedule(); },120); } window.addEventListener('touchend',settle,{passive:true}); window.addEventListener('scroll',settle,{passive:true}); // 初始:若内容不足以撑满一屏,主动向下加载 setTimeout(function(){ var d=document.documentElement; if(target()&&d.scrollHeight<=d.clientHeight+500 && st.max=0)return sel.selectedIndex+1; try{ var cp=new URLSearchParams(location.search).get('page'); cp=parseInt(cp||'1',10); if(isFinite(cp)&&cp>0)return cp; }catch(e){} return 1; } function totalPage(){ var sel=document.querySelector('#dumppage'); if(sel&&sel.options)return Math.max(1,sel.options.length); return 1; } // 从 URL 解析 view 与 index 参数,用于构造分页 URL function param(k,d){ try{var v=new URLSearchParams(location.search).get(k); return v||d;}catch(e){return d;} } function qs(p){ var q=new URLSearchParams(); q.set('mod','guide'); var idx=param('index',''); if(idx)q.set('index',idx); q.set('view',param('view','newthread')); q.set('page',String(p)); return 'https://bbs.binmt.cc/forum.php?'+q.toString(); } function curList(){return document.querySelector(LIST);} // 收集当前已存在的帖子链接集合(去重键) function existingKeys(){ var o={}; var d=curList(); if(!d)return o; var ex=d.querySelectorAll(ITEM); for(var i=0;i=st.total)return; if(Date.now()-lastLoadAt]*>/g,' ').replace(/ /g,' ').replace(/\s+/g,' ').trim(); // 失败分支:不写记录 → 当天再次进入页面会重试(对齐优化版 clearSignInfo 行为) if(/需要先登录|请先登录/.test(plain)){ signToast('自动签到:请先登录'); return; } if(/请稍后再试|黑名单|绑定手机号|不允许使用|非法字符/.test(plain)){ signToast('自动签到:'+plain.slice(0,20)); return; } // 幂等分支:今天已签过(跨设备/重复请求),静默记录 if(/今日已签|今日已经签到/.test(plain)){ finish(true); return; } // 成功分支:纯文本「已签到/签到成功」,或 button 格式返回的 HTML(含 .con 金币 / .line 排名) if(/已签到|签到成功/.test(plain)||/class=["']con["']/.test(text)){ finish(true); var coin='',rank=''; var cm=text.match(/class=["']con["'][^>]*>([\s\S]*?)<\/[a-z0-9]+>/i); if(cm){ var nm=cm[1].replace(/<[^>]*>/g,'').match(/(\d+)/); if(nm)coin=nm[1]; } var rm=text.match(/class=["']line["'][^>]*>([\s\S]*?)<\/[a-z0-9]+>/i); if(rm){ var nm2=rm[1].replace(/<[^>]*>/g,'').match(/(\d+)/); if(nm2)rank=nm2[1]; } signToast('自动签到成功'+(rank?',排名 '+rank:'')+(coin?',奖励 '+coin+' 金币':'')); return; } // 未知内容:改用 format=text 再确认一次,仍未知则不写记录(当日可重试) if(!isRetry)retryText(); } function retryText(){ var u2='https://bbs.binmt.cc/plugin.php?id=k_misign:sign&operation=qiandao&format=text&formhash='+fh; GET(u2,function(c,b){ handle(c,b,true); }); } // 主请求:format=button(对齐 MT论坛移动端优化 脚本,成功时返回金币/排名 HTML)。 // 同源 fetch 必带登录 cookie;Via 的 GM_xmlhttpRequest 仅作回退。 var u='https://bbs.binmt.cc/k_misign-sign.html?operation=qiandao&format=button&formhash='+fh+'&inajax=1&ajaxtarget=midaben_sign'; try{ fetch(u,{credentials:'include'}).then(function(r){ return r.text().then(function(t){ handle(r.status,t,false); }); }) .catch(function(){ retryText(); }); }catch(e){ retryText(); } } // ========== 隐藏帖自动回复 ========== function autoReply(){ if(!on('autoReply'))return; if(window.__autoReply)return; window.__autoReply=true; if(!/thread-\d+/.test(location.pathname)&&!/mod=viewthread/i.test(location.search))return; var TEXTS=['看看隐藏','感谢分享','论坛有你更精彩','看看是什么','谢谢分享']; var RIK='mtar_reply_index',DP='mtar_done_v11_',LRK='mtar_last_reply',FM=90000,PK='mtar_pending',FUK='mtar_flood_until_',RCM={}; function ts(){ var d=new Date(); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); } function ri(){ var i=parseInt(localStorage.getItem(RIK)||'0',10); if(isNaN(i)||i<0)i=0; return i%TEXTS.length; } function rt(){ return TEXTS[ri()]; } function adv(){ localStorage.setItem(RIK,String((ri()+1)%TEXTS.length)); } function uid(){ try{if(typeof discuz_uid!=='undefined'&&discuz_uid)return String(discuz_uid);}catch(e){} var m=document.documentElement.innerHTML.match(/discuz_uid\s*=\s*'(\d+)'/); return m?m[1]:''; } function tid(){ var m=location.pathname.match(/thread-(\d+)-/); if(m)return m[1]; try{var q=new URLSearchParams(location.search); if(q.get('tid'))return q.get('tid');}catch(e){} return ''; } function hasLocked(){ var s=document.querySelectorAll('.comiis_quote,.locked,div.locked,.t_f,.postmessage,.comiis_postli'); for(var i=0;i60000){sessionStorage.removeItem(PK+'_'+t);return false;} return true;}catch(e){return false;} } function setPend(t,o){ try{if(o)sessionStorage.setItem(PK+'_'+t,String(Date.now()));else sessionStorage.removeItem(PK+'_'+t);}catch(e){} } function floodUntil(t){ try{var v=parseInt(localStorage.getItem(FUK+t)||'0',10);return isFinite(v)?v:0;}catch(e){return 0;} } function setFlood(t,ms){ try{if(ms>0)localStorage.setItem(FUK+t,String(Date.now()+ms));else localStorage.removeItem(FUK+t);}catch(e){} } function parseFloodWait(txt){ var s=String(txt||''); var m=s.match(/(?:少于|间隔|等待|剩余|请(?:稍候|稍后))[^\d]{0,30}(\d+)\s*秒/i); if(!m)m=s.match(/(\d+)\s*秒/i); var n=m?parseInt(m[1],10):30; if(!isFinite(n)||n<1)n=30; if(n>3600)n=3600; return n; } function submit(cb){ var f=document.getElementById('fastpostform'),mb=document.getElementById('needmessage'); if(!f||!mb){cb({ok:false,fl:false,fs:0});return;} var act=(f.getAttribute('action')||'').replace(/&/g,'&'); if(!act){cb({ok:false,fl:false,fs:0});return;} var url=act+(act.indexOf('?')>=0?'&':'?')+'handlekey=fastpost&loc=1&inajax=1'; var fd=new URLSearchParams(); fd.set('formhash',f.elements.formhash?f.elements.formhash.value:''); if(f.elements.noticeauthor)fd.set('noticeauthor',f.elements.noticeauthor.value||''); fd.set('message',rt()); fd.set('replysubmit','回复'); var x=new XMLHttpRequest(),ended=false; function finish(v){if(ended)return;ended=true;cb(v);} try{x.open('POST',url,true);x.withCredentials=true;x.setRequestHeader('Content-Type','application/x-www-form-urlencoded');x.setRequestHeader('X-Requested-With','XMLHttpRequest'); x.onreadystatechange=function(){if(x.readyState!==4)return;var txt=x.responseText||'',isFlood=/两次发表间隔少于|间隔少于\s*\d+\s*秒|请稍候再发表|请稍后再发表|灌水/i.test(txt); var ok=x.status===200&&/succeedhandle_fastpost|回复发布成功/.test(txt)&&!/errorhandle_fastpost/.test(txt)&&!isFlood; finish({ok:ok,fl:isFlood,fs:isFlood?parseFloodWait(txt):0});}; x.onerror=function(){finish({ok:false,fl:false,fs:0});}; x.ontimeout=function(){finish({ok:false,fl:false,fs:0});}; x.timeout=20000; x.send(fd.toString()); }catch(e){finish({ok:false,fl:false,fs:0});}} function viewpidHtml(t,p,cb){ var x=new XMLHttpRequest(),doneX=false; function finish(v){if(doneX)return;doneX=true;cb(v);} try{x.open('POST','forum.php?mod=viewthread&tid='+encodeURIComponent(t)+'&viewpid='+encodeURIComponent(p)+'&mobile=2',true);x.withCredentials=true; x.setRequestHeader('Content-Type','application/x-www-form-urlencoded');x.setRequestHeader('X-Requested-With','XMLHttpRequest'); x.onreadystatechange=function(){if(x.readyState!==4)return;if(x.status!==200){finish('');return;}var raw=x.responseText||'',m=raw.match(//);finish(m?m[1]:raw);}; x.onerror=function(){finish('');};x.ontimeout=function(){finish('');};x.timeout=15000;x.send(''); }catch(e){finish('');}} function replaceFloor(t,p,cb){ viewpidHtml(t,p,function(html){ if(!html||html.indexOf('id="pid'+p)<0){cb(false);return;} var tg=document.getElementById('pid'+p)||document.getElementById('post_'+p); if(!tg){cb(false);return;} try{var fr=document.createElement('div');fr.innerHTML=html;var n=fr.querySelector('#pid'+p)||fr.querySelector('#post_'+p)||fr.firstElementChild;if(!n){cb(false);return;}tg.outerHTML=n.outerHTML;cb(true);}catch(e){cb(false);}}); } function unlock(t,ps,cb){ if(!ps||!ps.length){cb(true);return;} var arr=ps.slice(),idx=0,okn=0; function one(){ if(idx>=arr.length){cb(okn===arr.length);return;} var p=arr[idx++]; replaceFloor(t,p,function(ok){if(ok)okn++; setTimeout(one,1200+Math.floor(Math.random()*1800));}); } one(); } function scheduleRun(t,ms){ clearTimeout(window.__mtarTimer); window.__mtarTimer=setTimeout(function(){setPend(t,false);run();},Math.max(1000,ms)); } function scheduleUnlock(t,ps,attempt){ clearTimeout(window.__mtarUnlockTimer); window.__mtarUnlockTimer=setTimeout(function(){if(!hasLocked())return;unlock(t,ps,function(ok){if(ok&&!hasLocked())return; if(attempt<5){scheduleUnlock(t,lockedPids(),attempt+1);}else{setTimeout(function(){if(hasLocked())location.reload();},1500);}});},attempt===0?300:Math.min(8000,1500*attempt));} function run(){ var t=tid(); if(!t||!isMobile()||!hasLocked())return; var u=uid(); if(!u||u==='0')return; var ps=lockedPids(); if(done(t)){unlock(t,ps,function(ok){if(!ok||hasLocked())scheduleUnlock(t,lockedPids(),1);});return;} if(pend(t))return; var fu=floodUntil(t),now=Date.now(); if(fu>now){scheduleRun(t,fu-now+500+Math.floor(Math.random()*1000));return;} var f=document.getElementById('fastpostform'); if(f&&(f.querySelector('[name="seccodeverify"]')||f.querySelector('[name="secqaa"]')))return; if(hasMine(u)){markDone(t);unlock(t,ps,function(ok){if(!ok||hasLocked())scheduleUnlock(t,lockedPids(),1);});return;} setPend(t,true); setTimeout(function(){try{ if(!hasLocked()){setPend(t,false);return;} var mb=document.getElementById('needmessage'); if(mb&&mb.value&&mb.value.trim()&&mb.value.trim()!==rt()){setPend(t,false);return;} var lr=parseInt(localStorage.getItem(LRK)||'0',10),since=Date.now()-lr; if(since=200&&r.status<400,r.responseText); }; r.onerror=function(){fin(false,'');}; r.ontimeout=function(){fin(false,'');}; r.send(null); }catch(e){fin(false,'');} })(Q.shift()); } } function itemOf(n){ var p=n,fb=null,d=0; while(p&&p!==document.body&&d++<10){ var tag=p.tagName||'',c=typeof p.className==='string'?p.className:'',id=p.id||''; if(/^(LI|TR|TBODY|ARTICLE)$/.test(tag)&&(/(?:^|[ _-])(?:forumlist_li|thread|topic|post)(?:[ _-]|$)/i.test(c)||/^normalthread_/i.test(id)))return p; if(!fb&&/^(LI|TR|ARTICLE)$/.test(tag))fb=p; p=p.parentElement; } return fb||n.parentElement; } function scan(root){ var links=collectScoped(root||document,'a[href*="thread-"],a[href*="mod=viewthread"]'); for(var i=0;ia){ var s=ta.value.substring(a,b); if(s)return s; } } var w=window.getSelection&&window.getSelection(); if(w&&w.toString)return w.toString(); return ''; }catch(e){ return ''; } } function ubbChangeText(id,a,b){ try{ var ta=document.getElementById(id); if(!ta)return; var st=(typeof ta.selectionStart==='number')?ta.selectionStart:ta.value.length; var fn=(typeof ta.selectionEnd==='number')?ta.selectionEnd:ta.value.length; if(st>fn){var t=st;st=fn;fn=t;} var all=ta.value,sel=all.substring(st,fn); var nt; if(b)nt=all.substring(0,st)+a+sel+b+all.substring(fn,all.length); else nt=all.substring(0,st)+a+all.substring(fn,all.length); ta.value=nt; var cur=st+a.length+sel.length+(b?b.length:0); try{ta.selectionStart=ta.selectionEnd=cur;ta.focus();}catch(e){} }catch(e){} } // ========== 图片上传(MT论坛图床 img.binmt.cc)========== function mtImageInsert(url){ try{ var ta=document.getElementById('needmessage'); if(!ta)return; var tag='[img]'+url+'[/img]'; var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage',tag,''); } else{ ubbInsertText(ta,tag); } }catch(e){} } function mtImageInsertLink(url){ try{ var ta=document.getElementById('needmessage'); if(!ta)return; var tag='[url='+url+'][img]'+url+'[/img][/url]\n'; var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage',tag,''); } else{ ubbInsertText(ta,tag); } }catch(e){} } function mtImageInsertLink2(imgUrl,linkUrl){ try{ var ta=document.getElementById('needmessage'); if(!ta)return; var tag='[url='+linkUrl+'][img]'+imgUrl+'[/img][/url]\n'; var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage',tag,''); } else{ ubbInsertText(ta,tag); } }catch(e){} } function mtImageStyle(){ if(document.getElementById('mt-img-style'))return; var st=document.createElement('style'); st.id='mt-img-style'; st.textContent='@keyframes mtfade{from{opacity:0}to{opacity:1}}@keyframes mtscale{from{transform:scale(.92);opacity:0}to{transform:scale(1);opacity:1}}'+ '#mt-img-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;animation:mtfade .18s ease;}'+ '#mt-img-box{width:100%;max-width:320px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,.22),0 2px 8px rgba(0,0,0,.08);animation:mtscale .18s ease;}'+ '#mt-img-title{font-size:17px;font-weight:600;color:#1a1a1a;padding:22px 22px 0;}'+ '#mt-img-sub{font-size:12px;color:#999;padding:4px 22px 12px;word-break:break-all;}'+ '#mt-img-body{padding:0 22px 10px;}'+ '#mt-img-url{width:100%;box-sizing:border-box;padding:10px 2px;border:none;border-bottom:1.5px solid #e0e2e6;border-radius:0;font-size:16px;color:#16181d;background:transparent;outline:none;transition:border-color .18s;}'+ '#mt-img-url::placeholder{color:#b0b5bc;font-size:15px;}'+ '#mt-img-url:hover{border-bottom-color:#cdd1d6;}'+ '#mt-img-url:focus{border-bottom-color:#3a76f0;}'+ '#mt-img-link{width:100%;box-sizing:border-box;padding:10px 2px;border:none;border-bottom:1.5px solid #e0e2e6;border-radius:0;font-size:14px;color:#16181d;background:transparent;outline:none;transition:border-color .18s;margin-top:10px;}'+ '#mt-img-link::placeholder{color:#b0b5bc;font-size:13px;}'+ '#mt-img-link:hover{border-bottom-color:#cdd1d6;}'+ '#mt-img-link:focus{border-bottom-color:#3a76f0;}'+ '#mt-img-actions{display:flex;justify-content:flex-end;align-items:center;padding:4px 14px 14px;gap:6px;}'+ '#mt-img-actions .mb{min-width:56px;padding:10px 14px;border:none;background:none;font-size:15px;font-weight:500;letter-spacing:.2px;text-align:center;cursor:pointer;color:#6b7280;border-radius:8px;transition:background .15s;}'+ '#mt-img-actions .mb.primary{color:#3a76f0;font-weight:600;}'+ '#mt-img-actions .mb:active{background:#eef1f6;}'+ '#mt-img-actions .mb:hover{background:#f3f5f9;}'+ '#mt-img-file{display:none;}'+ '#mt-img-msg{font-size:12px;color:#f0433f;padding:0 22px 4px;min-height:16px;}'; (document.head||document.documentElement).appendChild(st); } function mtRainbowApply(){ try{ var t=ubbSelText(); if(!t){ alert('请选中一段文字'); return; } // 精确移植 APP 原生 set_rainbow 彩虹渐变算法(红→黄→绿→青→蓝→品红 循环,步长40,空格跳过) var r=255,g=0,b=0,i=1,j=0,istep=40,out=''; function hex(v){ var s=parseInt(v).toString(16); return s.length===1?'0'+s:s; } var n=t.length; for(var k=0;k-1){ if(i===2)r-=istep; } else if(i===2){ i=3; r=0; } if(b+istep<256){ if(i===3)b+=istep; } else if(i===3){ i=4; b=255; } if(g-istep>-1){ if(i===4)g-=istep; } else if(i===4){ i=5; g=0; } if(r+istep<256){ if(i===5)r+=istep; } else if(i===5){ i=6; r=255; } if(b-istep>-1){ if(i===6)b-=istep; } else if(i===6){ i=1; b=0; } out+='[color=#'+(hex(r)+hex(g)+hex(b)).toUpperCase()+']'+ch+'[/color]'; } else { out+=ch; } } ubbChangeText('needmessage',out,''); }catch(e){} } function mtColorStyle(){ if(document.getElementById('mt-color-style'))return; var st=document.createElement('style'); st.id='mt-color-style'; st.textContent='@keyframes mtfade{from{opacity:0}to{opacity:1}}@keyframes mtscale{from{transform:scale(.92);opacity:0}to{transform:scale(1);opacity:1}}'+ '#mt-color-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:2147483001;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;animation:mtfade .18s ease;}'+ '#mt-color-box{width:100%;max-width:320px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,.22),0 2px 8px rgba(0,0,0,.08);animation:mtscale .18s ease;}'+ '#mt-color-title{font-size:17px;font-weight:600;color:#1a1a1a;padding:22px 22px 0;}'+ '#mt-color-prev{display:flex;align-items:center;gap:12px;padding:14px 22px 12px;}'+ '#mt-color-prev .sw{width:42px;height:42px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#000;box-shadow:inset 0 1px 2px rgba(0,0,0,.08);}'+ '#mt-color-prev .hex{font-size:16px;font-family:monospace;color:#1a1a1a;letter-spacing:.5px;}'+ '.mt-color-row{display:flex;align-items:center;gap:10px;padding:5px 22px;}'+ '.mt-color-row .lb{width:14px;font-size:13px;font-weight:600;color:#555;}'+ '.mt-color-row .vx{width:30px;font-size:12px;color:#999;font-family:monospace;text-align:right;}'+ '.mt-color-row input[type=range]{flex:1;height:24px;accent-color:#3a76f0;}'+ '#mt-color-actions{display:flex;justify-content:flex-end;align-items:center;padding:14px 14px;gap:6px;}'+ '#mt-color-actions .mb{min-width:56px;padding:10px 14px;border:none;background:none;font-size:15px;font-weight:500;letter-spacing:.2px;text-align:center;cursor:pointer;color:#6b7280;border-radius:8px;transition:background .15s;}'+ '#mt-color-actions .mb.primary{color:#3a76f0;font-weight:600;}'+ '#mt-color-actions .mb:active{background:#eef1f6;}'+ '#mt-color-actions .mb:hover{background:#f3f5f9;}'; (document.head||document.documentElement).appendChild(st); } function mtColorDialog(){ try{ mtColorStyle(); if(document.getElementById('mt-color-mask'))return; var r=0,g=0,b=0; // 初始黑 #000000(同 APP 原生 progress=1) function hex(v){ var s=parseInt(v).toString(16); return s.length===1?'0'+s:s; } function rgb(){ return '#'+(hex(r)+hex(g)+hex(b)).toUpperCase(); } var mask=document.createElement('div'); mask.id='mt-color-mask'; var box=document.createElement('div'); box.id='mt-color-box'; var title=document.createElement('div'); title.id='mt-color-title'; title.textContent='选色器'; // 预览 var prev=document.createElement('div'); prev.id='mt-color-prev'; var sw=document.createElement('div'); sw.className='sw'; var hexEl=document.createElement('span'); hexEl.className='hex'; hexEl.textContent='#000000'; prev.appendChild(sw); prev.appendChild(hexEl); // 三个 RGB 滑条 function mkRow(lb){ var row=document.createElement('div'); row.className='mt-color-row'; var l=document.createElement('span'); l.className='lb'; l.textContent=lb; var s=document.createElement('input'); s.type='range'; s.min='0'; s.max='255'; s.value='0'; var v=document.createElement('span'); v.className='vx'; v.textContent='00'; row.appendChild(l); row.appendChild(s); row.appendChild(v); return {row:row,s:s,v:v}; } var R=mkRow('R'),G=mkRow('G'),B=mkRow('B'); function refresh(){ sw.style.background=rgb(); hexEl.textContent=rgb(); } function bind(X){ X.s.addEventListener('input',function(){ var val=parseInt(X.s.value)||0; if(X===R)r=val;else if(X===G)g=val;else b=val; X.v.textContent=hex(val).toUpperCase(); refresh(); }); } bind(R);bind(G);bind(B); // 快捷取色 input color var cbtn=document.createElement('input'); cbtn.type='color'; cbtn.value='#000000'; cbtn.style.cssText='margin:0 16px 8px;width:56px;height:32px;border:none;background:none;cursor:pointer;padding:0;'; cbtn.addEventListener('input',function(){ var c=cbtn.value.replace('#',''); r=parseInt(c.substr(0,2),16)||0; g=parseInt(c.substr(2,2),16)||0; b=parseInt(c.substr(4,2),16)||0; R.s.value=r;G.s.value=g;B.s.value=b; R.v.textContent=hex(r).toUpperCase();G.v.textContent=hex(g).toUpperCase();B.v.textContent=hex(b).toUpperCase(); refresh(); }); // 按钮 var actions=document.createElement('div'); actions.id='mt-color-actions'; var cb=document.createElement('div'); cb.className='mb'; cb.textContent='取消'; var ob=document.createElement('div'); ob.className='mb primary'; ob.textContent='确定'; function apply(){ try{ var col=rgb(); var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage','[color='+col+']','[/color]'); } else { var ta=document.getElementById('needmessage'); ubbInsertText(ta,'[color='+col+'][/color]'); } }catch(e){} finally{ try{mask.remove();}catch(e){} } } ob.addEventListener('click',apply); cb.addEventListener('click',function(){ try{mask.remove();}catch(e){} }); actions.appendChild(cb); actions.appendChild(ob); box.appendChild(title); box.appendChild(prev); box.appendChild(R.row); box.appendChild(G.row); box.appendChild(B.row); box.appendChild(cbtn); box.appendChild(actions); mask.appendChild(box); mask.addEventListener('click',function(e){ if(e.target===mask){ try{mask.remove();}catch(err){} } }); (document.body||document.documentElement).appendChild(mask); }catch(e){} } function mtDialogStyle(){ if(document.getElementById('mt-dlg-style'))return; var st=document.createElement('style'); st.id='mt-dlg-style'; st.textContent='#mt-dlg-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:2147483002;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;animation:mtfade .18s ease;}'+ '@keyframes mtfade{from{opacity:0}to{opacity:1}}'+ '#mt-dlg-box{width:100%;max-width:320px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,.22),0 2px 8px rgba(0,0,0,.08);animation:mtscale .18s ease;}'+ '@keyframes mtscale{from{transform:scale(.92);opacity:0}to{transform:scale(1);opacity:1}}'+ '#mt-dlg-title{font-size:19px;font-weight:600;line-height:1.4;color:#16181d;padding:28px 28px 0;letter-spacing:.2px;}'+ '#mt-dlg-body{padding:22px 28px 0;}'+ '.mt-dlg-field{margin-bottom:22px;}'+ '.mt-dlg-field .lb{display:block;font-size:14px;font-weight:500;color:#3a3f47;margin-bottom:10px;letter-spacing:.2px;}'+ '.mt-dlg-field .lb .st{color:#f0433f;margin-right:3px;font-weight:600;}'+ '.mt-dlg-field input{width:100%;height:40px;padding:0 2px;border:none;border-bottom:1.5px solid #e0e2e6;border-radius:0;font-size:16px;color:#16181d;background:transparent;box-sizing:border-box;outline:none;transition:border-color .18s;}'+ '.mt-dlg-field input::placeholder{color:#b0b5bc;font-size:15px;}'+ '.mt-dlg-field input:hover{border-bottom-color:#cdd1d6;}'+ '.mt-dlg-field input:focus{border-bottom-color:#3a76f0;}'+ '#mt-dlg-msg{font-size:13px;color:#f0433f;padding:0 28px 0;min-height:20px;margin-top:-12px;margin-bottom:10px;}'+ '#mt-dlg-actions{display:flex;justify-content:flex-end;align-items:center;padding:6px 18px 18px;gap:8px;}'+ '#mt-dlg-actions .mb{min-width:60px;padding:11px 16px;border:none;background:none;font-size:15px;font-weight:500;letter-spacing:.3px;text-align:center;cursor:pointer;color:#6b7280;border-radius:10px;transition:background .15s;}'+ '#mt-dlg-actions .mb.primary{color:#3a76f0;font-weight:600;}'+ '#mt-dlg-actions .mb:active{background:#eef1f6;}'+ '#mt-dlg-actions .mb:hover{background:#f3f5f9;}'+ '.mt-align-wrap{display:flex;gap:8px;padding:0 18px;margin:4px 0 6px;}'+ '.mt-align-wrap .mt-align-opt{flex:1;padding:11px 0;border:1px solid #e0e2e6;border-radius:10px;font-size:15px;font-weight:500;text-align:center;cursor:pointer;color:#3a76f0;background:#f7f9fc;transition:background .15s;}'+ '.mt-align-wrap .mt-align-opt:active{background:#eef1f6;}'+ '.mt-align-wrap .mt-align-opt:hover{background:#eef1f6;}'; (document.head||document.documentElement).appendChild(st); } function mtDialogShell(title){ try{ mtDialogStyle(); var mask=document.createElement('div'); mask.id='mt-dlg-mask'; var box=document.createElement('div'); box.id='mt-dlg-box'; var t=document.createElement('div'); t.id='mt-dlg-title'; t.textContent=title; var body=document.createElement('div'); body.id='mt-dlg-body'; var msg=document.createElement('div'); msg.id='mt-dlg-msg'; box.appendChild(t); box.appendChild(body); box.appendChild(msg); mask.appendChild(box); mask.addEventListener('click',function(e){ if(e.target===mask){ try{mask.remove();}catch(err){} } }); (document.body||document.documentElement).appendChild(mask); return {mask:mask,body:body,msg:msg,close:function(){try{mask.remove();}catch(e){}}}; }catch(e){ return null; } } function mtDlgField(body,label,required,placeholder){ try{ var f=document.createElement('div'); f.className='mt-dlg-field'; var l=document.createElement('label'); l.className='lb'; if(required){ var st=document.createElement('span'); st.className='st'; st.textContent='*'; l.appendChild(st); } l.appendChild(document.createTextNode(label)); var inp=document.createElement('input'); inp.type='text'; if(placeholder)inp.placeholder=placeholder; f.appendChild(l); f.appendChild(inp); body.appendChild(f); return inp; }catch(e){ return null; } } // URL / Email 超链接:双输入弹窗(地址必填,名称选填,空则沿用地址) function mtLinkDialog(kind){ try{ var isUrl=(kind==='URL'); var sh=mtDialogShell(isUrl?'插入链接':'插入邮箱链接'); if(!sh)return; var url=mtDlgField(sh.body,isUrl?'链接地址':'邮箱地址',true,isUrl?'https://example.com':'name@example.com'); var name=mtDlgField(sh.body,'显示文本',false,'留空则显示地址本身'); var actions=document.createElement('div'); actions.id='mt-dlg-actions'; var cb=document.createElement('div'); cb.className='mb'; cb.textContent='取消'; var ob=document.createElement('div'); ob.className='mb primary'; ob.textContent='确定'; function ok(){ try{ var u=(url.value||'').trim(); if(!u){ sh.msg.textContent=(isUrl?'链接地址':'邮箱地址')+'不能为空'; try{url.focus();}catch(e){} return; } var nm=(name.value||'').trim(); if(!nm)nm=u; var tag=(isUrl?'[url=':'[email=')+u+']'+nm+(isUrl?'[/url]':'[/email]')+'\n'; var ta=document.getElementById('needmessage'); ubbInsertText(ta,tag); sh.close(); }catch(e){} } ob.addEventListener('click',ok); url.addEventListener('keydown',function(e){ if(e.key==='Enter')ok(); }); name.addEventListener('keydown',function(e){ if(e.key==='Enter')ok(); }); cb.addEventListener('click',function(){ sh.close(); }); actions.appendChild(cb); actions.appendChild(ob); sh.body.appendChild(actions); }catch(e){} } // 字号文字:1~9 弹窗 function mtSizeDialog(){ try{ var sh=mtDialogShell('文字大小'); if(!sh)return; var f=document.createElement('div'); f.className='mt-dlg-field'; var l=document.createElement('span'); l.className='lb'; l.textContent='大小'; var inp=document.createElement('input'); inp.type='number'; inp.min='1'; inp.max='9'; inp.value='3'; inp.placeholder='1~9'; f.appendChild(l); f.appendChild(inp); sh.body.appendChild(f); var hint=document.createElement('div'); hint.style.cssText='font-size:12px;color:#999;padding:2px 0;'; hint.textContent='文字大小可选 1~9'; sh.body.appendChild(hint); var actions=document.createElement('div'); actions.id='mt-dlg-actions'; var cb=document.createElement('div'); cb.className='mb'; cb.textContent='取消'; var ob=document.createElement('div'); ob.className='mb primary'; ob.textContent='确定'; function apply(v){ try{ var s=String(v); var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage','[size='+s+']','[/size]'); } else { var ta=document.getElementById('needmessage'); ubbInsertText(ta,'[size='+s+'][/size]'); } }catch(e){} sh.close(); } function ok(){ var v=parseInt(inp.value); if(!v||v<1||v>9){ sh.msg.textContent='请输入 1~9 之间的数字'; return; } apply(v); } ob.addEventListener('click',ok); inp.addEventListener('keydown',function(e){ if(e.key==='Enter')ok(); }); cb.addEventListener('click',function(){ sh.close(); }); actions.appendChild(cb); actions.appendChild(ob); sh.body.appendChild(actions); }catch(e){} } // 对齐文本:居左/居中/居右 三选项 function mtAlignMenu(){ try{ var sh=mtDialogShell('对齐文本'); if(!sh)return; var opts=[['left','居左'],['center','居中'],['right','居右']]; var wrap=document.createElement('div'); wrap.className='mt-align-wrap'; function apply(v){ try{ var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage','[align='+v+']','[/align]'); } else { var ta=document.getElementById('needmessage'); ubbInsertText(ta,'[align='+v+'][/align]'); } }catch(e){} sh.close(); } opts.forEach(function(o){ var b=document.createElement('div'); b.className='mt-align-opt'; b.textContent=o[1]; b.addEventListener('click',function(){ apply(o[0]); }); wrap.appendChild(b); }); sh.body.appendChild(wrap); var actions=document.createElement('div'); actions.id='mt-dlg-actions'; var cb=document.createElement('div'); cb.className='mb'; cb.textContent='取消'; cb.addEventListener('click',function(){ sh.close(); }); actions.appendChild(cb); sh.body.appendChild(actions); }catch(e){} } // 网络视频:视频地址弹窗 function mtMediaDialog(){ try{ var sh=mtDialogShell('网络视频'); if(!sh)return; var url=mtDlgField(sh.body,'视频地址',true,'视频链接或地址'); var actions=document.createElement('div'); actions.id='mt-dlg-actions'; var cb=document.createElement('div'); cb.className='mb'; cb.textContent='取消'; var ob=document.createElement('div'); ob.className='mb primary'; ob.textContent='确定'; function ok(){ try{ var u=(url.value||'').trim(); if(!u){ sh.msg.textContent='视频地址不可为空'; return; } var sel=ubbSelText(); if(sel){ ubbChangeText('needmessage','[media=x,500,375]'+u+'[/media]',''); } else { var ta=document.getElementById('needmessage'); ubbInsertText(ta,'[media=x,500,375]'+u+'[/media]'); } sh.close(); }catch(e){} } ob.addEventListener('click',ok); url.addEventListener('keydown',function(e){ if(e.key==='Enter')ok(); }); cb.addEventListener('click',function(){ sh.close(); }); actions.appendChild(cb); actions.appendChild(ob); sh.body.appendChild(actions); }catch(e){} } function mtImageDialog(){ mtImageStyle(); if(document.getElementById('mt-img-mask')){ return; } var mask=document.createElement('div'); mask.id='mt-img-mask'; var box=document.createElement('div'); box.id='mt-img-box'; var title=document.createElement('div'); title.id='mt-img-title'; title.textContent='网络图片'; var sub=document.createElement('div'); sub.id='mt-img-sub'; sub.textContent='上传到MT论坛图床 或 手动输入链接'; var body=document.createElement('div'); body.id='mt-img-body'; var url=document.createElement('input'); url.id='mt-img-url'; url.type='text'; url.placeholder='也可直接粘贴图片链接'; var msg=document.createElement('div'); msg.id='mt-img-msg'; var actions=document.createElement('div'); actions.id='mt-img-actions'; var fbtn=document.createElement('label'); fbtn.className='mb'; fbtn.textContent='上传图片'; var finp=document.createElement('input'); finp.id='mt-img-file'; finp.type='file'; finp.accept='image/*'; fbtn.appendChild(finp); // 自定义点击跳转链接(可选) var link=document.createElement('input'); link.id='mt-img-link'; link.type='text'; link.placeholder='点击图片打开链接(可选,留空不添加)'; var ibtn=document.createElement('div'); ibtn.className='mb primary'; ibtn.textContent='插入'; var cbtn=document.createElement('div'); cbtn.className='mb'; cbtn.textContent='取消'; actions.appendChild(fbtn); actions.appendChild(ibtn); actions.appendChild(cbtn); body.appendChild(url); body.appendChild(link); box.appendChild(title); box.appendChild(sub); box.appendChild(body); box.appendChild(msg); box.appendChild(actions); mask.appendChild(box); document.body.appendChild(mask); function close(){ try{ mask.parentNode.removeChild(mask); }catch(e){} } mask.addEventListener('click',function(e){ if(e.target===mask)close(); }); cbtn.addEventListener('click',close); ibtn.addEventListener('click',function(){ var v=(url.value||'').trim(); if(v){ var linkUrl=(link.value||'').trim(); if(linkUrl){ mtImageInsertLink2(v,linkUrl); } else{ mtImageInsert(v); } close(); } else{ msg.textContent='请先上传图片或输入链接'; } }); finp.addEventListener('change',function(){ var f=finp.files&&finp.files[0]; if(!f)return; msg.textContent='上传中...'; if(!/^image\//i.test(f.type)){ msg.textContent='请选择图片文件'; return; } mtImageUpload(f,function(ok,res){ if(ok&&res){ url.value=res; msg.textContent='上传成功,点击「插入」写入输入框'; } else{ msg.textContent=res||'上传失败,请重试'; } }); }); } // ========== 图片上传(优先:论坛同源 swfupload;降级:MT论坛图床 img.binmt.cc)========== // 主入口:先走论坛同源上传(无跨域,Via 可用),失败再降级到 img.binmt.cc 图床 // 图片大小上限(字节)。0 表示不限制。论坛附件一般建议 ≤ 2MB。 var MT_IMG_MAX_SIZE = 0; // 对图片做归一化:webp 等论坛 swfupload 不支持的格式,用 Canvas 转成 png 再上传。 // 返回 Promise(可原样返回或用转换后的 File 替换)。 function mtImageNormalize(file){ return new Promise(function(resolve){ var ext = (file.name||'').split('.').pop().toLowerCase(); // 论坛 swfupload 图片白名单(Discuz 默认):jpg/jpeg/gif/png/bmp var okExt = {jpg:1, jpeg:1, gif:1, png:1, bmp:1}; if(okExt[ext]){ resolve(file); return; } // 支持格式:原样直传 // 不支持格式(webp/heic/avif 等):尝试用 Canvas 转 png var url = URL.createObjectURL(file); var img = new Image(); img.onload = function(){ try{ var c = document.createElement('canvas'); c.width = img.naturalWidth; c.height = img.naturalHeight; var ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0); URL.revokeObjectURL(url); c.toBlob(function(blob){ if(blob){ var newName = (file.name||'image').replace(/\.[^.]+$/,'') + '.png'; var f = new File([blob], newName, {type:'image/png'}); resolve(f); } else { resolve(file); } // 转失败:原样返回,让后续逻辑提示 }, 'image/png'); }catch(e){ URL.revokeObjectURL(url); resolve(file); } }; img.onerror = function(){ URL.revokeObjectURL(url); resolve(file); }; img.src = url; }); } function mtImageUpload(file,cb){ var done=false; function finish(ok,res){ if(done)return; done=true; cb(ok,res); } mtImageNormalize(file).then(function(f){ // 大小限制(默认关闭 MT_IMG_MAX_SIZE=0;如需开启设 >0 字节) if(MT_IMG_MAX_SIZE && f && f.size && f.size > MT_IMG_MAX_SIZE){ finish(false,'图片超过 ' + Math.round(MT_IMG_MAX_SIZE/1024/1024) + 'MB,请压缩后重试'); return; } mtImageUploadForum(f,function(ok,res){ if(ok){ finish(true,res); return; } // 论坛同源上传失败 -> 降级到 img.binmt.cc 图床 mtImageUploadBed(f,finish); }); }).catch(function(){ finish(false,'图片处理失败'); }); } // 路径一:论坛同源附件上传(Discuz misc.php?mod=swfupload),无跨域限制,Via 环境 100% 可用 function mtImageUploadForum(file,cb){ var done=false; function finish(ok,res){ if(done)return; done=true; cb(ok,res); } var credCache=null; // 从当前页面 DOM 提取 uid + hash(附件上传 token,内联在编辑器 uploadformdata 里) function extractUploadCred(){ if(credCache&&credCache.uid&&credCache.hash)return credCache; var html=document.documentElement.innerHTML; var uid=null,hash=null; var mu=html.match(/uploadformdata:\s*\{[^}]*uid:\s*["']?(\d+)["']?/i); var mh=html.match(/hash:\s*["']([0-9a-f]{32})["']/i); if(mu)uid=mu[1]; if(mh)hash=mh[1]; // 兜底:也尝试从独立字段 / 变量读取 if(!uid && typeof window.discuz_uid!=='undefined'&&window.discuz_uid)uid=window.discuz_uid; if(!hash){ var m2=html.match(/uploadformdata\s*[:=]\s*\{[^}]*hash\s*[:=]\s*["']([^"']+)["']/i); if(m2)hash=m2[1]; } credCache={uid:uid,hash:hash}; return credCache; } var cred=extractUploadCred(); if(!cred.uid||!cred.hash){ finish(false,'不在回帖/发帖页面,无法使用论坛图床'); return; } var fd=new FormData(); fd.append('uid',cred.uid); fd.append('hash',cred.hash); fd.append('Filedata',file,file.name||'image.png'); var url='misc.php?mod=swfupload&operation=upload&type=image&inajax=yes&infloat=yes&simple=2'; fetch(url,{method:'POST',credentials:'include',body:fd}) .then(function(r){ return r.text(); }) .then(function(txt){ var raw=String(txt); var a=raw.split('|'); if(a[0]==='DISCUZUPLOAD' && a[2]==='0'){ var relPath=a[5]; if(relPath){ finish(true,(location.origin||'https://bbs.binmt.cc').replace(/\/$/,'')+'/data/attachment/forum/'+String(relPath).replace(/^\/+/,'')); } else{ finish(false,'上传成功但未获取到图片路径'); } } else { var code=a[2]; var STATUSMSG={'1':'不支持该扩展名','2':'服务器限制无法上传那么大的附件','3':'用户组限制无法上传那么大的附件','5':'文件类型限制无法上传该格式','6':'今日已无法上传更多文件','7':'请选择图片文件','8':'附件文件无法保存','9':'没有合法的文件被上传','10':'非法操作','11':'今日已无法上传更大的附件','-1':'内部服务器错误'}; var reason=STATUSMSG[code]||('错误码 '+code); finish(false,'论坛不支持该图片:'+reason); } }) .catch(function(){ finish(false,'论坛图床上传请求失败'); }); } // 路径二:降级 - 原 img.binmt.cc 图床两步上传(GET 首页取 csrf-token + cookie 会话 -> POST /upload) function mtImageUploadBed(file,cb){ var BASE='https://img.binmt.cc'; var useGM=(typeof GM_xmlhttpRequest==='function'); function failWhy(){ return useGM ? '图床上传失败(请检查脚本跨域授权 @connect)' : '图床上传失败(当前环境缺少 GM_xmlhttpRequest,跨域受限,请尝试手动输入链接)'; } // 从 set-cookie 响应头解析 cookie 键值,提取关键会话字段 // 兼容多种 responseHeaders 格式:字符串(换行分隔) / 对象{header:value} / 数组 function addCookie(jar, line){ var seg=String(line).replace(/^set-cookie\s*:\s*/i,'').split(';')[0]; var idx=seg.indexOf('='); if(idx>0){ var k=seg.slice(0,idx).trim(); var v=seg.slice(idx+1).trim(); if(k&&v) jar[k]=v; } } function parseCookies(headersStr){ var jar={}; if(!headersStr)return jar; if(typeof headersStr==='string'){ var lines=headersStr.split(/[\r\n]+/); lines.forEach(function(ln){ if(/^set-cookie\s*:/i.test(ln.trim())) addCookie(jar, ln); }); } else if(typeof headersStr==='object'){ // 对象形式:可能是 {header:value} 或数组 var arr=(Array.isArray(headersStr))?headersStr:[headersStr]; arr.forEach(function(o){ for(var k in o){ if(o.hasOwnProperty(k) && /set-cookie/i.test(String(k))) addCookie(jar, String(k)+': '+o[k]); } }); } return jar; } function cookieHeader(jar){ var parts=[]; for(var k in jar){ if(jar.hasOwnProperty(k)&&jar[k]) parts.push(k+'='+jar[k]); } return parts.join('; '); } function extractCsrf(txt){ var m=(txt||'').match(/csrf-token"\s*content="([^"]+)"/i)||(txt||'').match(/name="csrf-token"\s*content="([^"]+)"/i); return m?m[1]:''; } function doUpload(token,cookieJar){ var done=false; function finish(ok,res){ if(done)return; done=true; cb(ok,res); } if(useGM){ var fd=new FormData(); fd.append('file',file,file.name||'image.png'); var hdr={ 'Referer':BASE+'/', 'x-csrf-token':token }; var ck=cookieHeader(cookieJar); if(ck) hdr['Cookie']=ck; // 注意:不要手动设置 Content-Type,GM/浏览器会为 FormData 自动补 boundary GM_xmlhttpRequest({ method:'POST', url:BASE+'/upload', headers:hdr, data:fd, timeout:30000, onload:function(r){ try{ var txt=r.responseText||r.response||''; var j; if(typeof txt==='object') j=txt; else { try{ j=JSON.parse(txt); }catch(e){ j=null; } } if(j&&j.status&&j.data&&j.data.links&&j.data.links.url){ finish(true,j.data.links.url); } else{ finish(false,(j&&j.message)||('图床返回异常:'+String(txt).slice(0,60))); } }catch(e){ finish(false,'上传响应解析失败'); } }, onerror:function(){ finish(false,failWhy()); }, ontimeout:function(){ finish(false,'上传超时'); } }); return; } // 降级:页面 fetch(可能因 CORS 失败) var fd2=new FormData(); fd2.append('file',file,file.name||'image.png'); fetch(BASE+'/upload',{method:'POST',credentials:'include',headers:{'Referer':BASE+'/','x-csrf-token':token},body:fd2}) .then(function(r){ return r.json(); }) .then(function(j){ if(j&&j.status&&j.data&&j.data.links&&j.data.links.url)finish(true,j.data.links.url); else finish(false,(j&&j.message)||'上传失败'); }) .catch(function(){ finish(false,failWhy()); }); } function getToken(){ function onPage(txt,headersStr){ var t=extractCsrf(txt); var jar=headersStr?parseCookies(headersStr):{}; if(t){ doUpload(t,jar); } else { cb(false,'获取图床会话失败'); } } if(useGM){ // 关键:用 responseHeaders 抓 set-cookie,实现手动会话管理 GM_xmlhttpRequest({ method:'GET', url:BASE+'/', timeout:20000, responseHeaders:true, onload:function(r){ onPage(r.responseText, r.responseHeaders); }, onerror:function(){ cb(false,'无法访问图床'); } }); } else { fetch(BASE+'/',{credentials:'include'}).then(function(r){return r.text();}).then(function(t){onPage(t,'');}).catch(function(){ cb(false,'无法访问图床'); }); } } getToken(); } function ubbInjectStyle(){ if(document.getElementById('mt-ubb-style'))return; var st=document.createElement('style'); st.id='mt-ubb-style'; st.textContent= // 工具栏容器:改为可横向滚动(触屏原生滚动),解除 swiper 对 transform 的锁定 '#comiis_mh_sub{overflow-x:auto!important;overflow-y:hidden!important;touch-action:pan-x!important;-webkit-overflow-scrolling:touch;scrollbar-width:none;}'+ '#comiis_mh_sub::-webkit-scrollbar{display:none;}'+ '.swiper-wrapper.comiis_post_ico{display:flex!important;flex-wrap:nowrap!important;align-items:center;gap:6px;padding:6px 10px;width:max-content!important;transform:none!important;transition:none!important;}'+ // 快速回复框工具栏(无 swiper,直接是 flex 容器),同样设为可横向滚动 '.comiis_post_ico.comiis_minipost_icot{display:flex!important;flex-wrap:nowrap!important;align-items:center;gap:6px;padding:6px 10px;overflow-x:auto!important;overflow-y:hidden!important;touch-action:pan-x!important;-webkit-overflow-scrolling:touch;scrollbar-width:none;}'+ '.comiis_post_ico.comiis_minipost_icot::-webkit-scrollbar{display:none;}'+ // 胶囊按钮(原生按钮统一外观),覆盖 swiper 版与快速回复框版两种容器 '.swiper-wrapper.comiis_post_ico a,'+ '.comiis_post_ico.comiis_minipost_icot a{display:inline-flex!important;align-items:center;justify-content:center;height:28px;padding:0 11px!important;border:1px solid #e0e4e8;border-radius:14px;background:#fff;color:#444;font-size:12.5px;flex:none!important;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;box-sizing:border-box!important;margin:0!important;line-height:1;white-space:nowrap!important;}'+ // 隐藏原生按钮里的 comiis_font 图标字体(只保留文字) '.swiper-wrapper.comiis_post_ico a i.comiis_font::before,.comiis_post_ico.comiis_minipost_icot a i.comiis_font::before{display:none!important;}'+ // 原生按钮文字颜色/字体统一 '.swiper-wrapper.comiis_post_ico a i.comiis_font,.swiper-wrapper.comiis_post_ico a em,'+ '.comiis_post_ico.comiis_minipost_icot a i.comiis_font,.comiis_post_ico.comiis_minipost_icot a em{color:#444!important;font-style:normal!important;font-size:inherit!important;font-weight:400!important;}'+ '.swiper-wrapper.comiis_post_ico a:active,'+ '.comiis_post_ico.comiis_minipost_icot a:active{background:#3a76f0;color:#fff;border-color:#3a76f0;}'+ // UBB 快捷输入栏:独立成一行,横排在原生工具栏下方,可横向滚动 '.mt-ubb-row{display:flex!important;flex-wrap:nowrap!important;align-items:center;gap:6px;padding:6px 10px;overflow-x:auto!important;overflow-y:hidden!important;touch-action:pan-x!important;-webkit-overflow-scrolling:touch;scrollbar-width:none;background:#fff;border-bottom:1px solid #eef1f4;overscroll-behavior-x:contain!important;overscroll-behavior-y:contain!important;}'+ '.mt-ubb-row::-webkit-scrollbar{display:none;}'+ '.mt-ubb-row .ub{display:inline-flex!important;align-items:center;justify-content:center;height:28px;padding:0 11px!important;border:1px solid #e0e4e8;border-radius:14px;background:#fff;color:#444;font-size:12.5px;flex:none!important;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;box-sizing:border-box!important;margin:0!important;line-height:1;white-space:nowrap!important;}'+ '.mt-ubb-row .ub:active{background:#3a76f0;color:#fff;border-color:#3a76f0;}'+ // 快速回复框(非 swiper)里 UBB 按钮是 span.ub(非 a),需单独补胶囊样式 '.comiis_post_ico.comiis_minipost_icot .ub{display:inline-flex!important;align-items:center;justify-content:center;height:28px;padding:0 11px!important;border:1px solid #e0e4e8;border-radius:14px;background:#fff;color:#444;font-size:12.5px;flex:none!important;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;box-sizing:border-box!important;margin:0!important;line-height:1;white-space:nowrap!important;}'+ '.comiis_post_ico.comiis_minipost_icot .ub:active{background:#3a76f0;color:#fff;border-color:#3a76f0;}'+ // 快速回复框「回复」按钮:摘出后独立一行,重写为通栏主按钮 '.mt-reply-submit-row{display:block!important;float:none!important;margin:8px 10px 4px!important;}'+ '.mt-reply-submit-row input#fastpostsubmit{display:block!important;width:100%!important;height:38px!important;line-height:38px!important;border:none!important;border-radius:19px!important;background:#3a76f0!important;color:#fff!important;font-size:15px!important;font-weight:600!important;cursor:pointer;-webkit-tap-highlight-color:transparent;}'+ '.mt-reply-submit-row input#fastpostsubmit:active{background:#2f63cf!important;}'; (document.head||document.documentElement).appendChild(st); } function ubbBuildBar(){ var ta=document.getElementById('needmessage'); if(!ta)return null; if(window.__mtUbbDone)return window.__mtUbbWrap||null; // 优先 swiper 结构(发帖页/独立回复页),否则快速回复框的普通工具栏 var wrap=document.querySelector('.swiper-wrapper.comiis_post_ico'); var isSwiper=!!wrap; if(!wrap){ wrap=document.querySelector('.comiis_post_ico.comiis_minipost_icot'); } if(!wrap)return null; ubbInjectStyle(); var as=wrap.querySelectorAll('a'); // 去掉原生按钮里的 comiis_font 图标字体(只删 PUA 图标字符文本节点,保留 i/em 结构,点击行为不受影响) for(var k=0;k=0;n--){ var node=nodes[n]; if(node.nodeType===3){ var txt=node.nodeValue||''; var clean=txt.replace(/[\ue000-\uf8ff]/g,'').trim(); if(clean){ node.nodeValue=clean; } else { ic.removeChild(node); } } } })(ics[x]); } })(as[k]); } // 阻止 swiper 库接管横向手势(仅 swiper 结构存在 comiis_mh_sub) if(isSwiper){ var sub=document.getElementById('comiis_mh_sub'); if(sub && !sub.__mtTouchStop){ sub.__mtTouchStop=true; ['touchstart','touchmove','touchend'].forEach(function(ev){ sub.addEventListener(ev,function(e){ e.stopPropagation(); },true); }); } } // 快速回复框:把「回复」提交按钮从横向滚动工具栏中摘出,独立成一行,重写 UI if(!isSwiper){ var submitLine=document.getElementById('fastpostsubmitline'); if(submitLine && submitLine.parentNode===wrap){ wrap.after(submitLine); // 移到工具栏容器之后,成为独立一行 submitLine.className+=' mt-reply-submit-row'; } } // UBB 快捷输入栏: // - swiper 结构(发帖页/独立回复页):独立成一行,不混入原生工具栏,避免污染 $(this).index()。 // - 快速回复框(非 swiper,原生是「表情/图片/...」):保持一栏设计,UBB 插在同一栏;并移除原生「图片」按钮 // (UBB 栏里的「网络图片」功能更好,原生图片按钮多余)。 var bar=document.createElement('div'); bar.className='mt-ubb-row'; var list=ubbList(); for(var i=0;i=0){ a.parentNode.removeChild(a); } })(as[p]); } } if(isSwiper){ // swiper:UBB 独立成一行,挂到最外层工具栏容器之后 var outer=wrap.closest('.comiis_post_ico.comiis_minipost_icot')||wrap.parentNode; if(outer && outer.parentNode){ outer.parentNode.insertBefore(bar, outer.nextSibling); } else { wrap.after(bar); } } else { // 快速回复框:保持一栏,把 UBB 按钮逐个插到原工具栏末尾 var tip=wrap.querySelector('span.ub'); if(tip){ tip.parentNode.removeChild(tip); } // 防御:避免重复 while(bar.children.length){ wrap.appendChild(bar.children[0]); } // 依次移入,避免动态长度问题 bar=wrap; // 之后的手势拦截/返回值都指向 wrap(实际容器) } // 阻止 UBB 栏横向滑动时把 touch 手势冒泡到页面,误触网站「打开侧边栏设定」逻辑。 // 只阻止冒泡(stopPropagation),不影响容器自身的原生横向滚动。 var touchTarget=bar; if(!touchTarget.__mtTouchStop){ touchTarget.__mtTouchStop=true; ['touchstart','touchmove','touchend'].forEach(function(ev){ touchTarget.addEventListener(ev,function(e){ e.stopPropagation(); },true); }); } window.__mtUbbDone=true; window.__mtUbbWrap=touchTarget; return touchTarget; } // 父页侧:向小窗 iframe 主动注入「自包含启动脚本」。 // 背景(决定性根因):Via 浏览器只往顶层页面注入用户脚本、不往同源 iframe 注入, // 所以 iframe 内运行的仍是原始论坛页(无 UBB 栏、无自动回复)。旧的「触发型」方案 // (调用 iframe.contentWindow.__mtUbbRetry() / postMessage 通知)全部无效——因为 iframe 里 // 根本没有脚本可以响应这些触发。 // // 方案 X:父页在 iframe load 后,把 autoReply + 整套 UBB 函数链的源码(经 // Function.prototype.toString() 提取)+ 极简运行时头(S/FEATS/defOf/on)拼接成一份 // 自包含脚本,作为