转跳到内容

所有动态

此流会自动更新

  1. 过去一个小时
  2. 欢迎新人 现在还有玩黄油吗 提醒一下,新手村不能自占二楼
  3. 第一眼杯子,进来看还真是杯子。礼物嘛,心意到就好了。
  4. 躺在床上玩逆转裁判,玩着玩着就睡着了
  5. 好耶~!!!我选择等我的富婆对象养我
  6. 發現很多新人其實都是建了帳號之後在新手村待著,某天突然想到才突然開始解任務。
  7. 什...?! 是劲爆小作文!
  8. chen1091接下了周常任务:勇者杀敌——水怪的养成 任务链接:第一日回复https://sstm.moe/topic/378342-大家会因为某部动画热度极高而放弃追番吗/page/2/#findComment-18888875 任务链接:第二日回复https://sstm.moe/topic/378886-深夜对红烧肉的疑惑,遂发帖/#findComment-18890125 任务链接:第三日回复https://sstm.moe/topic/378930-机翻润色了几本健康老师官网的小说,请各位看看有没有什么问题?/#findComment-18891306 任务链接:第四日回复https://sstm.moe/topic/378931-迟来的新人报道/#findComment-18892514 任务链接:第五日回复https://sstm.moe/topic/378992-大概是新人报道?/#findComment-18893666
  9. 欢迎新人
  10. 感谢提供脚本,我试了一下虽然显示删除成功但配额占用还是100%,刷新一下页面后原本删除的附件又出现在列表里了,我也不想折腾了,以后直接用图床了。
  11. 今天
  12. 啊这,好惨的说。对了,那么一周五天在同一个帖子下回复五次算不算完成任务呢🤔
  13. 太赞同了,我也是这样。但是我觉得或许这也是晕3d的其中一个原因。
  14. 嗯嗯好滴好滴。咦,这是什么表情包呀,我怎么都没在论坛自带的表情包里找到这个呢?
  15. 电脑端,使用tampermonkey插件,试试看把这个脚本拷贝进去,如果是你有修改权限的帖子会删除附件,如果你没修改权限的话会报404,至少我实验是有效的 // ==UserScript== // @name SSTM 附件页删除按钮(区分主题/回复) // @namespace https://example.com/ // @version 0.2.0 // @author sstm脚本组 // @description 在 https://sstm.moe/attachments/ 页面为每个附件块插入删除按钮,并根据来源链接自动判断编辑主题还是编辑回复 // @match https://sstm.moe/attachments/* // @match https://sstm.moe/attachments/ // @grant none // ==/UserScript== (function () { 'use strict'; const CONFIG = { itemSelector: 'div.ipsDataItem.ipsAttach', attachmentLinkSelector: 'a[href*="/applications/core/interface/file/attachment.php?id="]', sourceLinkSelector: '.ipsDataItem_generic.ipsDataItem_size9 a[href*="/topic/"]', buttonClass: 'sstm-delete-attachment-btn', confirmBeforeDelete: true, debug: true }; function log(...args) { if (CONFIG.debug) { console.log('[SSTM Attachment Delete]', ...args); } } function absoluteUrl(url) { return new URL(url, location.origin).toString(); } function getCsrfKey() { const fromMeta = document.querySelector('meta[name="csrfKey"]')?.content; if (fromMeta) return fromMeta; const fromWindow1 = window.ips?.getSetting?.('csrfKey'); if (fromWindow1) return fromWindow1; const fromWindow2 = window.ipsSettings?.csrfKey; if (fromWindow2) return fromWindow2; const m = document.cookie.match(/(?:^|;\s*)csrfKey=([^;]+)/); if (m) return decodeURIComponent(m[1]); return null; } function extractAttachmentId(item) { const link = item.querySelector(CONFIG.attachmentLinkSelector); if (!link) return null; try { const url = new URL(link.href, location.origin); return url.searchParams.get('id'); } catch (e) { return null; } } function extractFileName(item) { const el = item.querySelector('.ipsAttach_title a, .ipsDataItem_title a'); return el ? el.textContent.trim() : '未知附件'; } function extractSourceInfo(item) { const sourceLink = item.querySelector(CONFIG.sourceLinkSelector); if (!sourceLink) return null; const href = absoluteUrl(sourceLink.href); const url = new URL(href); const commentId = url.searchParams.get('comment'); const doValue = url.searchParams.get('do'); const isReply = doValue === 'findComment' && !!commentId; if (isReply) { url.searchParams.delete('do'); url.searchParams.delete('comment'); return { type: 'reply', sourceUrl: href, baseTopicUrl: url.toString(), commentId }; } url.searchParams.delete('do'); url.searchParams.delete('comment'); return { type: 'topic', sourceUrl: href, baseTopicUrl: url.toString(), commentId: null }; } function buildEditUrl(sourceInfo, csrfKey) { const url = new URL(sourceInfo.baseTopicUrl, location.origin); if (sourceInfo.type === 'reply') { url.searchParams.set('do', 'editComment'); url.searchParams.set('comment', sourceInfo.commentId); } else { url.searchParams.set('do', 'edit'); } if (csrfKey) { url.searchParams.set('csrfKey', csrfKey); } return url.toString(); } async function fetchText(url, options = {}) { const resp = await fetch(url, { credentials: 'include', cache: 'no-cache', ...options }); if (!resp.ok) { throw new Error(`请求失败 ${resp.status} ${resp.statusText}`); } return await resp.text(); } function parseHTML(html) { return new DOMParser().parseFromString(html, 'text/html'); } function extractPostKeyFromEditHtml(html) { const doc = new DOMParser().parseFromString(html, 'text/html'); // 1. 优先从 IPS 编辑器容器读取 const editorEl = doc.querySelector('[data-ipseditor-postkey]'); if (editorEl) { const postKey = editorEl.getAttribute('data-ipseditor-postkey'); if (postKey) return postKey; } // 2. 兜底:兼容可能存在的 input const input = doc.querySelector('input[name="postKey"]'); if (input?.value) return input.value; // 3. 再兜底:正则扫原始 HTML let m = html.match(/data-ipseditor-postkey=["']([^"']+)["']/i); if (m) return m[1]; m = html.match(/name=["']postKey["'][^>]*value=["']([^"']+)["']/i); if (m) return m[1]; m = html.match(/["']postKey["']\s*:\s*["']([^"']+)["']/i); if (m) return m[1]; return null; } async function deleteAttachment(editUrl, attachmentId, postKey, csrfKey) { const url = new URL(editUrl, location.origin); url.searchParams.set('postKey', postKey); url.searchParams.set('deleteFile', attachmentId); if (csrfKey) { url.searchParams.set('csrfKey', csrfKey); } log('删除请求 URL:', url.toString()); const resp = await fetch(url.toString(), { method: 'GET', credentials: 'include', mode: 'cors', headers: { 'accept': '*/*', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'x-requested-with': 'XMLHttpRequest' }, referrer: editUrl }); if (!resp.ok) { throw new Error(`删除请求失败 ${resp.status} ${resp.statusText}`); } return await resp.text(); } function setBtnState(btn, state, text) { btn.disabled = state === 'loading'; btn.textContent = text; if (state === 'loading') { btn.style.opacity = '0.7'; btn.style.cursor = 'wait'; } else { btn.style.opacity = '1'; btn.style.cursor = 'pointer'; } } function removeItem(item) { item.style.transition = 'opacity .25s ease, transform .25s ease'; item.style.opacity = '0'; item.style.transform = 'scale(0.98)'; setTimeout(() => item.remove(), 260); } async function handleDelete(item, btn) { const attachmentId = extractAttachmentId(item); const fileName = extractFileName(item); const sourceInfo = extractSourceInfo(item); const csrfKey = getCsrfKey(); if (!attachmentId) { alert('未找到附件 ID'); return; } if (!sourceInfo) { alert('未找到该附件对应的主题/回复链接'); return; } if (!csrfKey) { alert('未找到 csrfKey'); return; } const targetText = sourceInfo.type === 'reply' ? `回复 #${sourceInfo.commentId}` : '主题首楼'; if (CONFIG.confirmBeforeDelete) { const ok = confirm( `确定删除附件吗?\n\n文件名:${fileName}\n附件ID:${attachmentId}\n挂载位置:${targetText}` ); if (!ok) return; } try { setBtnState(btn, 'loading', '读取编辑页...'); const editUrl = buildEditUrl(sourceInfo, csrfKey); log('编辑页 URL:', editUrl); log('来源类型:', sourceInfo.type, sourceInfo); const editHtml = await fetchText(editUrl, { headers: { 'accept': 'text/html,application/xhtml+xml' } }); const postKey = extractPostKeyFromEditHtml(editHtml); if (!postKey) { throw new Error('未能从编辑页中解析出 postKey'); } setBtnState(btn, 'loading', '提交删除...'); const result = await deleteAttachment(editUrl, attachmentId, postKey, csrfKey); log('删除响应:', result); setBtnState(btn, 'success', '已删除'); removeItem(item); } catch (err) { console.error(err); setBtnState(btn, 'error', '删除失败'); alert(`删除失败:${err.message}`); setTimeout(() => { setBtnState(btn, 'idle', '删除附件'); }, 1200); } } function injectButton(item) { if (item.querySelector(`.${CONFIG.buttonClass}`)) return; const attachmentId = extractAttachmentId(item); const sourceInfo = extractSourceInfo(item); if (!attachmentId || !sourceInfo) return; const btn = document.createElement('button'); btn.type = 'button'; btn.className = CONFIG.buttonClass; btn.textContent = '删除附件'; btn.style.marginTop = '8px'; btn.style.padding = '6px 10px'; btn.style.border = '1px solid #cc3333'; btn.style.borderRadius = '4px'; btn.style.background = '#fff5f5'; btn.style.color = '#b30000'; btn.style.fontSize = '12px'; btn.style.lineHeight = '1.2'; btn.style.cursor = 'pointer'; btn.addEventListener('mouseenter', () => { if (!btn.disabled) btn.style.background = '#ffeaea'; }); btn.addEventListener('mouseleave', () => { if (!btn.disabled) btn.style.background = '#fff5f5'; }); btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); handleDelete(item, btn); }); const target = item.querySelector('.ipsDataItem_main') || item; target.appendChild(btn); } function processAll() { document.querySelectorAll(CONFIG.itemSelector).forEach(injectButton); } function observe() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (!(node instanceof Element)) continue; if (node.matches?.(CONFIG.itemSelector)) { injectButton(node); } node.querySelectorAll?.(CONFIG.itemSelector).forEach(injectButton); } } }); observer.observe(document.body, { childList: true, subtree: true }); } processAll(); observe(); })(); 然后之后如果想上传图片/视频的话,可以试试看这个https://sstm.moe/topic/378868-【油猴脚本】便捷文件上传自定义表情包/?do=findComment&comment=18887760
  16. 不能图床啥的么。。。
  17. shenm接下了主线任务1:新人报道 任务链接:https://sstm.moe/topic/378992-大概是新人报道?/
  18. 给我的错字捉一下虫(毕竟本人打字很慢所以一直用的是语音输入法)“论台”应当是“论坛”
  19. 嗯。。怎么说呢,ss同盟,我个人算是久仰大名了(毕竟好几年前就在玩ss同盟相关汉化的游戏了——没错,就是勇战)其实还挺怀念那段日子的,不过现在不是怀旧的时候,毕竟无论如何,我当年没有加入这个可以说是伟大的论坛是既成事实,如今我现在也只是个小资历,实话实说,我向往着这个论坛庞大的资源和知识,但之前由于种种原因都没有注册,哪怕是在三年前注册好了之后也没有想出新手村什么的(这么一说我在新手村待了快三年了)如今我现在终于有了一定的空闲,因此我决定好好融入这个论坛,毕竟,我从好几年前就对这件事有一定向往了,现在也算是如愿了(都快让我有点自我感动了哈哈 )嗯,就像先前网上传过的那个段子一样,汉化组是伟大的(就是公子哥拿饭店的食材给乞丐做饭的那个)因此我对论台上的做着汉化的各位充满了敬仰(不敢有向往毕竟我外语确实不好XD)总而言之,这是一个迟到了的新人报道,我现在终于真正成为了ss同盟的一份子,我的内心难以言表,因此,只能祝所有在看这个帖子的人好运
  20. 攸薩神教是真的☆~ 新人好奇银行在哪里
  21. 这两个月在做什么呢
  22. 啃生肉啊......或者说我能过n1就是啃生肉啃的( 现在过了依旧啃生肉
  23. 几个月不见~你都变成初级会员了,最近在做什么呢
  24. 恭喜啊 ,虽然有点迟了 那你现在用日语的地方多吗
  1. 加载更多动态
×
×
  • 新建...

重要消息

为使您更好地使用该站点,请仔细阅读以下内容: 使用条款