Sleazy Fork is available in English.

ETTHelper-TagEditer

Help to edit the gallery's tags.

  1. // ==UserScript==
  2. // @name ETTHelper-TagEditer
  3. // @name:zh-CN E绅士标签翻译辅助工具-标签编辑
  4. // @namespace EhTagTranslation
  5. // @description Help to edit the gallery's tags.
  6. // @description:zh-CN 辅助编辑E绅士画廊的标签
  7. // @include *://exhentai.org/g/*
  8. // @include *://e-hentai.org/g/*
  9. // @version 1.4.6
  10. // @author Mapaler <mapaler@163.com>
  11. // @copyright 2019+, Mapaler <mapaler@163.com>
  12. // @homepage https://github.com/EhTagTranslation/UserScripts
  13. // @supportURL https://github.com/EhTagTranslation/UserScripts/issues
  14. // @grant GM_registerMenuCommand
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_deleteValue
  18. // ==/UserScript==
  19.  
  20. const scriptVersion = (defaultVersion=>typeof GM_info != "undefined" ? GM_info.script.version.replace(/(^\s*)|(\s*$)/g, "") : defaultVersion)("unknown"); //本程序的版本
  21. const scriptName = (defaultName=>{
  22. if (typeof(GM_info)!="undefined")
  23. {
  24. if (GM_info.script.name_i18n)
  25. {
  26. const i18n = navigator.language.replace("-","_"); //获取浏览器语言
  27. return GM_info.script.name_i18n[i18n]; //支持Tampermonkey
  28. }
  29. else
  30. {
  31. return GM_info.script.localizedName || //支持Greasemonkey 油猴子 3.x
  32. GM_info.script.name; //支持Violentmonkey 暴力猴
  33. }
  34. }else
  35. {
  36. return defaultName;
  37. }
  38. })("ETTWikiHelper-TagEditer"); //本程序的名称
  39.  
  40. //限定数值最大最小值
  41. function limitMaxAndMin(num,max,min)
  42. {
  43. return Math.max(Math.min(num, max), min);
  44. }
  45.  
  46. //默认CSS内容
  47. const ewh_tag_styleText_Default = `
  48. /* fallback */
  49. @font-face {
  50. font-family: 'Material Icons';
  51. font-style: normal;
  52. font-weight: 400;
  53. src: url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
  54. }
  55.  
  56. .material-icons {
  57. font-family: 'Material Icons';
  58. font-weight: normal;
  59. font-style: normal;
  60. line-height: 1;
  61. letter-spacing: normal;
  62. text-transform: none;
  63. display: inline-block;
  64. white-space: nowrap;
  65. word-wrap: normal;
  66. direction: ltr;
  67. -moz-font-feature-settings: 'liga';
  68. -moz-osx-font-smoothing: grayscale;
  69. }
  70.  
  71. #gd4.ewh-float { /*浮动窗体*/
  72. position: fixed;
  73. top: 10%;
  74. left: 10%;
  75. background-color : inherit;
  76. margin: 0 !important;
  77. padding: 0 !important;
  78. border-style: ridge;
  79. border-width: 3px;
  80. border-color: #eee black black #eee;
  81. opacity: 0.8;
  82. }
  83. .ewh-bar-floatcaption { /*标题栏整体*/
  84. height: 22px;
  85. position: relative;
  86. }
  87. .ewh-cpttext-box { /*标题栏文字框*/
  88. width: 100%;
  89. height: 100%;
  90. float: left;
  91. color: white;
  92. line-height: 22px;
  93. font-size: 14px;
  94. background-image: linear-gradient(to right,#808080,#B7B5BB);
  95. }
  96. .ewh-float .ewh-cpttext-box { /*浮动时的标题颜色*/
  97. background-image: linear-gradient(to right,#000280,#0F80CD);
  98. }
  99. .ewh-cpttext-box::before{ /*标题图标*/
  100. content: "🏷️";
  101. }
  102. .ewh-cpttext-box span { /*标题文字*/
  103. pointer-events:none;
  104. user-select: none;
  105. }
  106. .ewh-cptbtn-box { /*标题按钮框*/
  107. height: 100%;
  108. position: absolute;
  109. top: 0;
  110. right: 8px;
  111. line-height: 22px;
  112. }
  113. .ewh-cpt-btn { /*平时的按钮*/
  114. vertical-align: middle;
  115. padding: 0;
  116. font-size: 14px;
  117. margin-top:-2px;
  118. height: 18px;
  119. width: 20px;
  120. background-color: #c0c0c0;
  121. border-style: outset;
  122. border-width: 2px;
  123. border-color: white black black white;
  124. }
  125. .ewh-cpt-rag { /*平时的范围拖动条*/
  126. vertical-align: middle;
  127. padding: 0;
  128. font-size: 14px;
  129. margin-top:0;
  130. height: 18px;
  131. width: 100px;
  132. }
  133. .ewh-cpt-btn:active { /*按钮按下时的凹陷*/
  134. background-color: #d8d8d8;
  135. padding-left: 1px !important;
  136. padding-top: 1px !important;
  137. border-style: inset;
  138. border-color: black white white black;
  139. }
  140. .ewh-cpt-btn:focus { /*激活后的虚线*/
  141. outline: dotted 1px black;
  142. }
  143. .ewh-btn-closefloat,.ewh-rag-opacity { /*平时隐藏关闭浮动的按钮*/
  144. display: none;
  145. }
  146. .ewh-float .ewh-btn-closefloat,.ewh-float .ewh-rag-opacity { /*浮动时显示关闭浮动的按钮*/
  147. display: unset;
  148. }
  149. .ewh-float .ewh-btn-openfloat{ /*浮动时隐藏开启浮动的按钮*/
  150. display: none;
  151. }
  152. .ewh-bar-tagsearch{
  153. position: relative;
  154. }
  155. .ewh-ipt-tagsearch{
  156. width: 200px;
  157. box-sizing: border-box;
  158. }
  159. .ewh-tagsearchtext,.ewh-tagsearchlink{
  160. font-size: 10pt;
  161. }
  162. .ewh-bar-tagsearch a::before{
  163. font-size: 10pt;
  164. font-weight: bold;
  165. }
  166. .ewh-bar-tagsearch a::after{
  167. font-size: 10pt;
  168. background: #c0c0c0;
  169. color:black;
  170. border-style: ridge;
  171. border-width: 3px;
  172. border-color: #eee black black #eee;
  173. position:absolute;
  174. z-index:999;
  175. padding:8px;
  176. min-width:150px;
  177. max-width:500px;
  178. white-space:pre-wrap;
  179. opacity: 0;
  180. transition: opacity 0.1s;
  181. top:28px;
  182. left:45%;
  183. pointer-events:none;
  184. font-weight: 400;
  185. line-height: 20px;
  186. }
  187. .ewh-bar-tagsearch a:hover::after{
  188. opacity: 1;
  189. }
  190. `;
  191. //获取Tag编辑区
  192. var ewhWindow = document.querySelector("#gd4");
  193.  
  194. //增加浮动窗标题栏
  195. var divCaptionBar = ewhWindow.insertBefore(document.createElement("div"),gd4.firstChild);
  196. divCaptionBar.className = "ewh-bar-floatcaption";
  197.  
  198. //生成辅助器CSS
  199. var ewh_tag_style = divCaptionBar.appendChild(document.createElement("style"));
  200. ewh_tag_style.type = "text/css";
  201. ewh_tag_style.appendChild(document.createTextNode(ewh_tag_styleText_Default));
  202.  
  203. //生成标题栏文字
  204. var divCaption = divCaptionBar.appendChild(document.createElement("div"));
  205. divCaption.className = "ewh-cpttext-box";
  206. divCaption.appendChild(document.createElement("span")).appendChild(document.createTextNode(scriptName));
  207.  
  208. //添加窗体鼠标拖拽移动
  209. var windowPosition = ewhWindow.position = [0, 0]; //[X,Y] 用以储存窗体开始拖动时的鼠标相对窗口坐标差值。
  210. divCaption.addEventListener("mousedown", function(e) { //按下鼠标则添加移动事件
  211. if (!ewhWindow.classList.contains("ewh-float")) return; //如果不是浮动窗体直接结束
  212. var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。
  213. windowPosition[0] = eX - ewhWindow.offsetLeft;
  214. windowPosition[1] = eY - ewhWindow.offsetTop;
  215. var handler_mousemove = function(e) { //移动鼠标则修改窗体坐标
  216. var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。
  217. ewhWindow.style.left = (eX - windowPosition[0]) + 'px';
  218. ewhWindow.style.top = (eY - windowPosition[1]) + 'px';
  219. };
  220. var handler_mouseup = function(e) { //抬起鼠标则取消移动事件
  221. document.removeEventListener("mousemove", handler_mousemove);
  222. if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置
  223. if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置
  224.  
  225. };
  226. document.addEventListener("mousemove", handler_mousemove);
  227. document.addEventListener("mouseup", handler_mouseup, { once: true });
  228. });
  229.  
  230. //生成标题栏按钮
  231. var divButtonBox = divCaptionBar.appendChild(document.createElement("div"));
  232. divButtonBox.className = "ewh-cptbtn-box";
  233.  
  234. //生成修改设置的按钮
  235. var ragOpacity = divButtonBox.appendChild(document.createElement("input"));
  236. ragOpacity.className = "ewh-cpt-rag ewh-rag-opacity";
  237. ragOpacity.type = "range";
  238. ragOpacity.max = 1;
  239. ragOpacity.min = 0.5;
  240. ragOpacity.step = 0.01;
  241. ragOpacity.title = "窗体不透明度";
  242. ragOpacity.onchange = ragOpacity.oninput = function(){
  243. ewhWindow.style.opacity = this.value;
  244. };
  245. ragOpacity.onchange = function(){
  246. ragOpacity.oninput();
  247. if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度
  248. };
  249.  
  250. //生成打开浮动状态的按钮
  251. var btnOpenFloat = divButtonBox.appendChild(document.createElement("button"));
  252. btnOpenFloat.className = "ewh-cpt-btn material-icons ewh-btn-openfloat";
  253. btnOpenFloat.title = "浮动标签编辑框";
  254. btnOpenFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("open_in_new")));
  255. btnOpenFloat.onclick = function(){
  256. //ewhWindow.setAttribute("style",ewhWindow.getAttribute("style_bak"));
  257. //ewhWindow.removeAttribute("style_bak");
  258. ewhWindow.classList.add("ewh-float");
  259. ewhWindow.style.left = GM_getValue("floatwindow-left");
  260. ewhWindow.style.top = GM_getValue("floatwindow-top");
  261. ewhWindow.style.opacity = ragOpacity.value = GM_getValue("floatwindow-opacity") || 0.8;
  262. };
  263. GM_registerMenuCommand("打开浮动标签编辑框", btnOpenFloat.onclick);
  264.  
  265. //生成关闭浮动状态的按钮
  266. var btnCloseFloat = divButtonBox.appendChild(document.createElement("button"));
  267. btnCloseFloat.className = "ewh-cpt-btn material-icons ewh-btn-closefloat";
  268. btnCloseFloat.title = "关闭浮动窗体";
  269. btnCloseFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("close")));
  270. btnCloseFloat.onclick = function(){
  271. //ewhWindow.setAttribute("style_bak",ewhWindow.getAttribute("style"));
  272. if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置
  273. if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置
  274. if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度
  275. ewhWindow.removeAttribute("style");
  276. ewhWindow.classList.remove("ewh-float");
  277. };
  278. GM_registerMenuCommand("重置浮动窗位置与透明度", function(){
  279. btnCloseFloat.onclick(); //先关掉窗体,然后删除设置
  280. GM_deleteValue("floatwindow-left");
  281. GM_deleteValue("floatwindow-top");
  282. GM_deleteValue("floatwindow-opacity");
  283. });
  284.  
  285. var nameSpaceC = {
  286. artist:"艺术家",
  287. female:"女性",
  288. male:"男性",
  289. parody:"原作",
  290. character:"角色",
  291. group:"团队",
  292. language:"语言",
  293. reclass:"重新分类",
  294. misc:"杂项"
  295. };
  296. //获取标签数据列表
  297. var tagdatalist = document.querySelector("#tbs-tags");
  298. var tagData;
  299. //获取真实标签输入框
  300. var newTagText = document.querySelector("#newtagfield");
  301. if (!tagdatalist) //没有ETS,但有ETS扩展版的处理方式
  302. {
  303. var tagDataStr = localStorage.getItem("EhSyringe.tag-list"); //ETS扩展版1.2.1的数据
  304. if (typeof(tagDataStr) == "string")
  305. {
  306. tagData = JSON.parse(tagDataStr);
  307. tagdatalist = document.createElement("datalist");
  308. tagdatalist.id = "tbs-tags";
  309. newTagText.setAttribute("list","tbs-tags");
  310. tagData.forEach(function(tag){
  311. tagdatalist.appendChild(new Option(nameSpaceC[tag.namespace]+":"+tag.name,tag.search));
  312. })
  313. newTagText.insertAdjacentElement('afterend',tagdatalist);
  314. }
  315. }
  316. if (tagdatalist) //如果存在则生成标签搜索框
  317. {
  318. var taglist = tagdatalist.options;
  319. //增加标签搜索框箱子
  320. var divSearchBar = ewhWindow.insertBefore(document.createElement("div"),document.querySelector("#tagmenu_act"));
  321. divSearchBar.className = "ewh-bar-tagsearch";
  322.  
  323. //增加标签搜索框
  324. var iptTagSearch = divSearchBar.appendChild(document.createElement("input"));
  325. iptTagSearch.type = "text";
  326. iptTagSearch.placeholder = "🔍标签搜索:回车附加到下方▼";
  327. iptTagSearch.setAttribute("list","tbs-tags");
  328. iptTagSearch.className="ewh-ipt-tagsearch";
  329. //增加标签搜索提醒文字
  330. var spnTagSearchInfo = divSearchBar.appendChild(document.createElement("span"));
  331. spnTagSearchInfo.className="ewh-tagsearchtext";
  332. //增加标签搜索提醒标签
  333. var aTagSearchInfo = divSearchBar.appendChild(document.createElement("a"));
  334. aTagSearchInfo.className="ewh-tagsearchlink";
  335.  
  336. iptTagSearch.onkeypress = function(e){
  337. if(e.key == "Enter"){ //回车,将内容附加到真实Tag框,并清空搜索框
  338. var _this = this;
  339. if (_this.value.length == 0)
  340. { //如果什么都没输入,相当于提交
  341. spnTagSearchInfo.innerHTML = "";
  342. aTagSearchInfo.removeAttribute("id");
  343. aTagSearchInfo.innerHTML = "";
  344. if (newTagText.value.length > 0)tag_from_field(); //如果输入框有内容点击Tag提交
  345. return;
  346. };
  347.  
  348. var clabel = false, useGuess = false, guess = false;
  349. var tagC;
  350.  
  351. if (tagData) //如果有JSON数据,直接使用
  352. { //扩展版的JSON数据
  353. var searchRes = tagData.filter(function(tag){ //搜索绝对等于的
  354. return tag.search == _this.value;
  355. });
  356. if (searchRes.length<1)
  357. { //猜测式搜索
  358. searchRes = tagData.filter(function(tag){
  359. return tag.name.indexOf(_this.value)>=0 || tag.key.indexOf(_this.value)>=0; //有非tag字符时才搜索中文,其他时候搜索key
  360. });
  361. if (searchRes.length>0)
  362. {
  363. guess = true; //标记为猜的
  364. _this.value = searchRes[0].search; //目前的输入修改为猜的tag
  365. }
  366. }
  367. if (searchRes.length>0)
  368. {
  369. tagC = searchRes[0];
  370. clabel = tagC.name;
  371. }
  372. }else
  373. { //脚本版的数据
  374. if (_this.value.replace(/[\w\:\"\s\-\.\'\$]/,"").length>0) useGuess = true; //如果存在非tag字符,则尝试搜索中文。
  375. for (var ti=0;ti<taglist.length;ti++)
  376. { //循环搜索列表中是否已存在这个Tag
  377. if (taglist[ti].value == _this.value)
  378. {
  379. clabel = taglist[ti].label;
  380. break;
  381. }else if(useGuess && taglist[ti].label.indexOf(_this.value)>0)
  382. {
  383. clabel = taglist[ti].label;
  384. guess = true; //标记为猜的
  385. _this.value = taglist[ti].value; //目前的输入修改为猜的tag
  386. break;
  387. }
  388. }
  389. }
  390. if (clabel)
  391. {
  392. var shortTag;
  393. if (tagData)
  394. { //扩展版的JSON数据
  395. shortTag = (tagC.namespace=="misc"?"":(tagC.namespace.substr(0,1) + ":")) + tagC.key; //缩减Tag长度,以便一次能多提交一些Tag
  396. }else
  397. { //脚本版的数据
  398. var regArr = /^(\w+):"?([\w+\s\-\'\.]+)\$?"?$/ig.exec(_this.value);
  399. shortTag = (regArr[1]=="misc"?"":(regArr[1].substr(0,1) + ":")) + regArr[2]; //缩减Tag长度,以便一次能多提交一些Tag
  400. }
  401. if ((newTagText.value+","+shortTag).length>200)
  402. {
  403. spnTagSearchInfo.innerHTML = "⛔超长(原始标签输入框限定200字符)";
  404. if (!tagData) aTagSearchInfo.removeAttribute("id");
  405. aTagSearchInfo.innerHTML = "";
  406. }else
  407. {
  408. newTagText.value = (newTagText.value.length>0)?(newTagText.value+","+shortTag):shortTag;
  409. spnTagSearchInfo.innerHTML = (guess?"程序猜测你想添加":"你添加了")+" " + (tagData?nameSpaceC[tagC.namespace]:clabel.split(":")[0]) + ":";
  410. if (!tagData) aTagSearchInfo.id = "ta_" + (regArr[1]=="misc"?"":regArr[1]+":") + regArr[2].replace(/\s/igm,"_");
  411. aTagSearchInfo.innerHTML = clabel;
  412. _this.value = "";
  413. }
  414. }else
  415. {
  416. spnTagSearchInfo.innerHTML = "☹️数据库里没有这个标签";
  417. if (!tagData) aTagSearchInfo.removeAttribute("id");
  418. aTagSearchInfo.innerHTML = "";
  419. }
  420. }
  421. };
  422. }