🏠 Home 

评论魔术师

一键修改b站评论样式,从横向文字改为近似的竖向文字,仅是为了有趣。


Install this script?
  1. // ==UserScript==
  2. // @name 评论魔术师
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description 一键修改b站评论样式,从横向文字改为近似的竖向文字,仅是为了有趣。
  6. // @author thunder-sword
  7. // @match https://www.bilibili.com/video/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
  9. // @license MIT
  10. // @grant none
  11. // ==/UserScript==
  12. // 更新日志:
  13. /*
  14. v0.3:使点击回复时出现的评论框也加上魔术变化按钮,此版本会将页面中的所有评论框都加上按钮
  15. v0.2:添加“还原”按钮,还原文本为修改前文本
  16. */
  17. //默认设置
  18. var settings={
  19. "padChar": "_", //填充字符串,半角和空白会用此字符填充
  20. "row": 6, //分组的行数,手机上不展开默认显示6行
  21. "col": 19, //手机上默认显示19列,但为了避免溢出实际显示效果会-1
  22. };
  23. //变量作用:用于输入文本
  24. var event = document.createEvent('HTMLEvents');
  25. event.initEvent("input", true, true);
  26. event.eventType = 'message';
  27. //变量作用:用于检验是否已经转换过
  28. var lastNewStr='';
  29. //变量作用:记录上次原文本,方便还原
  30. var lastRawStr='';
  31. //作用:获取文本实际宽度
  32. function getByteLength(str) {
  33. let byteLength = 0;
  34. for (let i = 0; i < str.length; i++) {
  35. const c = str.charCodeAt(i);
  36. if (c > 255) {
  37. byteLength += 2;
  38. } else {
  39. byteLength += 1;
  40. }
  41. }
  42. return byteLength;
  43. }
  44. function verticalText(str, numRows, padChar='_') {
  45. // 将字符串转换成字符数组,每个元素为一个字符
  46. const chars = str.split('');
  47. const numCols = Math.ceil(chars.length / numRows); // 计算列数
  48. const r###lt = []; // 存储竖直排版后的字符数组
  49. let i, j;
  50. for (i = 0; i < numRows; i++) {
  51. // 初始化每行的字符数组
  52. const row = [];
  53. for (j = 0; j < numCols; j++) {
  54. // 计算字符在原始字符串中的索引
  55. const index = j * numRows + i;
  56. // 将索引越界的字符用填充字符代替
  57. const char = chars[index] || padChar;
  58. // 如果是单字节字符,则在其后面填充一个字符
  59. if (char.charCodeAt(0) < 256) {
  60. row.push(char, padChar);
  61. } else {
  62. row.push(char);
  63. }
  64. }
  65. // 将当前行的字符数组添加到结果数组中
  66. r###lt.push(row);
  67. }
  68. //console.log(r###lt);
  69. // 将竖直排版后的字符数组转换成字符串
  70. return r###lt.map(row => row.join('')).join('\n');
  71. }
  72. //作用:多行结果时将其分为多列来打印
  73. function multColVerticalText(str, numRows, padChar='_') {
  74. const lines = str.split('\n');
  75. const rows = Array.from({length: numRows}, () => []);
  76. for (let i = 0; i < lines.length; i++) {
  77. const chars = lines[i].split('');
  78. const numCols = Math.ceil(chars.length / numRows); // 计算列数
  79. for (let j = 0; j < numRows; j++) {
  80. for (let k = 0; k < numCols; k++) {
  81. // 计算字符在原始字符串中的索引
  82. const index = k * numRows + j;
  83. // 将索引越界的字符用填充字符代替
  84. const char = chars[index] || padChar;
  85. // 如果是单字节字符,则在其后面填充一个字符
  86. if (char.charCodeAt(0) < 256) {
  87. rows[j].push(char, padChar);
  88. } else {
  89. rows[j].push(char);
  90. }
  91. }
  92. }
  93. }
  94. return rows.map(row => row.join('')).join('\n');
  95. }
  96. //作用:规范化,两种想法:
  97. //1.将其6行6行的展示,每6行之间加一个换行,感觉挺规整的
  98. //2.自动计算,将其以最多38B列(19个汉字)的策略自动计算行数,难以实现的同时也不太方便观看,就用第一种吧
  99. //另测试发现当填充符为空格时较小,无法对整,故改为下划线,但其实下划线也无法准确对齐
  100. function normalization(str, padChar='_', row=6, col=19){
  101. //threshold是分组阈值,即每组字符串数,我手机上不展开默认显示的最多字数是6行19列,故采用此方式
  102. //但是测试发现,如果出现半角字符,直接满列有可能超出,即两个半角字符宽度>一个全角字符,为了显示正常,干脆将列数-1,给它留些空余
  103. let threshold=row*(col-1);
  104. let groups=[];
  105. let count=0;
  106. let tmp='';
  107. for(let i = 0; i < str.length; i++){
  108. tmp+=str[i];
  109. if('\n'===str[i]){
  110. let t=count%row;
  111. if(0!==t){
  112. count+=row-t;
  113. }
  114. }else{
  115. count+=1;
  116. }
  117. if(threshold===count){
  118. //console.log(tmp);
  119. //console.log(count);
  120. groups.push(tmp);
  121. tmp='';
  122. count=0;
  123. }
  124. }
  125. if(''!==tmp){
  126. groups.push(tmp);
  127. }
  128. //console.log(groups);
  129. return groups.map(i => multColVerticalText(i, row, padChar)).join('\n\n');
  130. }
  131. //作用:根据settings向评论页面插入选项和按钮
  132. function insertButton(box){
  133. //如果已经有了则不需要再添加
  134. if(box.querySelector('div#ths_magic_convert')){
  135. return;
  136. }
  137. var div=document.createElement('div');
  138. div.id="ths_magic_convert";
  139. div.setAttribute('style',"display: flex; align-items: center; margin-left: 80px; margin-top: 5px;");
  140. //为按钮绑定事件
  141. var button=document.createElement('button');
  142. button.innerText="魔术变化";
  143. button.style.marginRight = "3px";
  144. button.addEventListener('click', function() {
  145. convert_comment(this);
  146. });
  147. div.appendChild(button);
  148. button=document.createElement('button');
  149. button.innerText="还原";
  150. button.style.marginRight = "3px";
  151. button.addEventListener('click', function() {
  152. revert_comment(this);
  153. });
  154. div.appendChild(button);
  155. var label=document.createElement('label');
  156. label.innerText="填充字符:";
  157. div.appendChild(label);
  158. var input=document.createElement('input');
  159. input.id="padChar";
  160. input.value=settings["padChar"];
  161. div.appendChild(input);
  162. label=document.createElement('label');
  163. label.innerText="行数:";
  164. div.appendChild(label);
  165. input=document.createElement('input');
  166. input.id="row";
  167. input.value=settings["row"];
  168. div.appendChild(input);
  169. label=document.createElement('label');
  170. label.innerText="列数:";
  171. div.appendChild(label);
  172. input=document.createElement('input');
  173. input.id="col";
  174. input.value=settings["col"];
  175. div.appendChild(input);
  176. //添加对象到页面中
  177. box.appendChild(div);
  178. }
  179. //作用:点击按钮时处理评论区内文本
  180. //autoCheck:自动检验是否已修改过,修改过则不再更改
  181. function convert_comment(e, autoCheck=true){
  182. //console.log(e);
  183. //console.log('ok');
  184. // 每次点击按钮都更新settings
  185. // 获取按钮的父元素
  186. var div = e.parentNode;
  187. if(!div){
  188. alert("未找到div,执行失败");
  189. throw Error("未找到div,执行失败");
  190. }
  191. var in1=div.querySelector('input#padChar');
  192. //然后获取参数值并修改参数值
  193. if(!in1){
  194. alert("未找到in1!将忽略更改padChar");
  195. }else{
  196. settings["padChar"]=in1.value;
  197. }
  198. var in2=div.querySelector('input#row');
  199. if(!in1){
  200. alert("未找到in2!将忽略更改row");
  201. }else{
  202. settings["row"]=parseInt(in2.value);
  203. }
  204. var in3=div.querySelector('input#col');
  205. if(!in3){
  206. alert("未找到in3!将忽略更改col");
  207. }else{
  208. settings["col"]=parseInt(in3.value);
  209. }
  210. //获取text中文本
  211. //先获取父结点
  212. var box = div.parentNode;
  213. if(!box){
  214. alert("未找到box,执行失败");
  215. throw Error("未找到box,执行失败");
  216. }
  217. var text=box.querySelector("div.box-normal textarea");
  218. if(!text){
  219. alert("未找到text,执行失败");
  220. throw Error("未找到text,执行失败");
  221. }
  222. //执行格式化函数,获取格式化后文本
  223. var rawStr=text.value;
  224. if(autoCheck && rawStr===lastNewStr){
  225. //说明修改过了,不再修改
  226. return;
  227. }
  228. //记录原文本,方便还原
  229. lastRawStr=rawStr;
  230. var newStr=normalization(rawStr, settings["padChar"], settings["row"], settings["col"]);
  231. lastNewStr=newStr;
  232. //修改评论区内字符串
  233. text.value=newStr;
  234. text.dispatchEvent(event);
  235. }
  236. //作用:点击按钮时还原评论区内文本
  237. function revert_comment(e){
  238. //console.log(e);
  239. //console.log('ok');
  240. // 每次点击按钮都更新settings
  241. // 获取按钮的父元素
  242. var div = e.parentNode;
  243. if(!div){
  244. alert("未找到div,执行失败");
  245. throw Error("未找到div,执行失败");
  246. }
  247. //获取text中文本
  248. //先获取父结点
  249. var box = div.parentNode;
  250. if(!box){
  251. alert("未找到box,执行失败");
  252. throw Error("未找到box,执行失败");
  253. }
  254. var text=box.querySelector("div.box-normal textarea");
  255. if(!text){
  256. alert("未找到text,执行失败");
  257. throw Error("未找到text,执行失败");
  258. }
  259. //如果上一次的原文本存在,则赋值
  260. if(lastRawStr){
  261. text.value=lastRawStr;
  262. text.dispatchEvent(event);
  263. }
  264. }
  265. //作用:为每个评论框添加按钮
  266. function insertAll(){
  267. //如果存在浮动评论窗
  268. // var box=document.querySelector('div.fixed-reply-box');
  269. // if(box){
  270. // insertButton(box);
  271. // }
  272. //浮动评论窗也包含其中了,所以不需要额外的判断
  273. document.querySelectorAll('div.reply-box').forEach( (box) => { insertButton(box); });
  274. }
  275. (function() {
  276. 'use strict';
  277. setInterval(insertAll, 500);
  278. })();