fork(13) download
  1. //【登録場所】 "V2C\script\system\threadld.js"
  2. //【内容】threadld.jsのまとめ
  3. //【備考】threadUpdatedとpanelCreatedの{}内で、行頭のコメント「//」を、利用する行ごとに解除することで、有効になります。
  4. //【更新日】2013/11/24 guroNGの追加
  5. //【スクリプト】
  6. // ----- 次の行から -----
  7. /* スレッドの更新が完了した時に実行するスクリプト */
  8. function threadUpdated(th, cx) {
  9. /* 機能を有効にしたい場合、下の各行頭//を削除してください */
  10. // egnoreNewNGRes(th, cx); //新着がNGのみの場合、未読状態を解除
  11. redirectNewsThread(th, cx); //速報headlineスレを開くと本スレにリダイレクト ※パーミッションにSを追加してください ※速報headlineスレのログは削除されません。
  12. redirectLiveThread(th, cx); //テレビ番組欄のスレを開くと一番勢いのあるスレッド(恐らく本スレ)にリダイレクト ※パーミッションにSを追加してください ※テレビ番組欄スレのログは削除されません。
  13. idConditionalNG(th, cx); //閾値以上書き込みIDをNG判定(デフォルトはニュー速と嫌儲で有効)
  14. // checkNewTanpatsuNG(th, cx); //新着レスを対象に単発IDのレスを非表示にする。(デフォルトは実況カテゴリで有効)
  15. guroNG(th, cx); //グロアンカーのついたレスが画像リンクを持っていた場合透明NGに指定
  16. }
  17.  
  18. /* レス表示タブを作成した時に実行するスクリプト */
  19. function panelCreated(th) {
  20. /* 機能を有効にしたい場合、下の各行頭//を削除してください */
  21. // openWritePanel(th); //開いたスレが2chの場合、書き込みパネルを自動で開く
  22. // openFixedColumn(th); //常に特定のカラムで開く(デフォルトは0カラム)
  23. // groupThreadPanel(th); //2カラムの状態で、特定の板(デフォルトは実況カテゴリとTwitter)を特定のカラム(デフォルトは0カラム)で開く
  24. }
  25.  
  26. /* 関数名を添えてスクリプトコンソール出力
  27. 使用例 printFunc(arguments.callee, "要素1は「{0}」です。", 100); */
  28. function printFunc(func, format /*, ...*/)
  29. {
  30. var args = arguments;
  31. var message = format.replace(/\{(\d)\}/g, function(m, c) { return args[parseInt(c) + 2]; });
  32.  
  33. if (func && (typeof func == 'function')) {
  34. v2c.println("[threadld.js : " + func.name + "()] " + message);
  35. } else {
  36. v2c.println("[threadld.js]" + message);
  37. }
  38. }
  39.  
  40. /* ここから各機能 */
  41. function egnoreNewNGRes(th, cx) {
  42. var newResi = th.localResCount - cx.numNewRes;
  43. for (var i = newResi; i < th.localResCount; i++) {
  44. var res = th.getRes(i);
  45. if (res && !res.ng) {
  46. return;
  47. }
  48. }
  49. th.resetUnread();
  50. }
  51.  
  52. function redirectLiveThread(th, cx) {
  53. // ―┤設定項目ここから├―――――――――――――――――――――――――――――――――――――――――――
  54. // [設定項目] スレタイから番組名を除外した部分を対象に以下の正規表現でマッチした場合除外する。
  55. var DENY_WORDS = new RegExp("(マターリ|マターリ|酒|sage)", "i");
  56. // [設定項目] スレと一緒に板一覧を開く場合はture開かない場合はfalse
  57. var BOARDVIEW = true;
  58. // [設定項目/BOARDVIEW=trueの時のみ有効] スレのDAT落ち扱いを回避するためにスレ取得開始まで一定時間(ミリ秒単位)待つ (スレ一欄と同期が取れない為。スレ一欄が更新完了するまでの時間を入力)
  59. var THUPDATE_WAIT = 500;
  60. // [設定項目] スクリプトコンソールに比較文字列を出力する(一致具合検証用) 出力する場合はtrueしない場合はfalse
  61. var OUTPUT_COMPARE = false;
  62. // [設定項目] 比較関数の閾値。この値以上のスコアになったスレは除外する
  63. var threshold = 16;
  64. // [設定項目] 番組名とスレタイに比較前に補正を掛けて一致しやすくする
  65. var PRE_FUNC = (function(name) {
  66. var ret = { 'value' : '', 'deny' : '' };
  67. //カッコを削除(DEBY_WORDSの場合は残す
  68. var tname = name.replace(/[\[\(\<【({<[《〔][^\]\)\>】)}」』>]》〕]+[\]\)\>】)}>]》〕]/g, ''); // カッコの種類の「『 はタイトルに使われる場合があるので除外
  69. if (tname.length < name.length && DENY_WORDS.test(name)) {
  70. ret.deny = RegExp.$1;
  71. }
  72. name = tname;
  73. //全角英数字を半角に
  74. name = name.replace(/[!-~]/g, function(s) {
  75. return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
  76. });
  77. //半角英数字をと日本語以外は削除。小文字を大文字に
  78. name = name.replace(/[^一-龠ぁ-んァ-ヴa-zA-Z0-9\r\n]/ig, '').toUpperCase();
  79.  
  80. var hanKana = ['ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'パ', 'ビ', 'ピ', 'ブ', 'プ', 'ベ', 'ペ', 'ボ', 'ポ', 'ヴ',
  81. 'ァ', 'ア', 'ィ', 'イ', 'ゥ', 'ウ', 'ェ', 'エ', 'ォ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ッ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ',
  82. 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ャ', 'ヤ', 'ュ', 'ユ', 'ョ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ワ', 'ヲ', 'ン', '。', '「', '」', '、', '・', 'ー', '゙', '゚'];
  83. var zenKana = 'ガギグゲゴザジズゼゾダヂヅデドバパビピブプベペボポヴァアィイゥウェエォオカキクケコサシスセソタチッツテトナニヌネノハヒフヘホマミムメモャヤュユョヨラリルレロワワヲン。「」、・ー゛゜';
  84. var zenHira = 'がぎぐげござじずぜぞだぢづでどばぱびぴぶぷべぺぼぽゔぁあぃいぅうぇえぉおかきくけこさしすせそたちっつてとなにぬねのはひふへほまみむめもゃやゅゆょよらりるれろわわをん。「」、・ー゛゜';
  85. //全角カタカナひらがなを半角カタカナに
  86. name = name.replace(/[ぁ-んァ-ヴ「」、・ー゛゜]/g, function(s) {
  87. var n = zenKana.indexOf(s);
  88. if (n != -1) { return hanKana[n]; }
  89. n = zenHira.indexOf(s);
  90. if (n != -1) { return hanKana[n]; }
  91. return '';
  92. });
  93. ret.value = name;
  94. return ret;
  95. });
  96. // ――――――――――――――――――――――――――――――――――――――――――――――――――――――
  97.  
  98. if (!cx.error && th.board.key.startsWith('tv2chwiki')) {
  99. var tmp = th.getRes(0);
  100. var bd = v2c.getBoard(tmp.links[0]);
  101. if (!bd) { printFunc(arguments.callee, "{0}の取得に失敗", bd); return false; }
  102. tmp = String(tmp.message).split('\n');
  103. if ((!tmp) || tmp.length <= 0) { printFunc(arguments.callee, "{0}の>>1の本文の取得に失敗", th); return false; }
  104. // ヒット率を上げるために全角スペースで区切ってループ判定(その分誤判定も増える。元のロジックに戻した場合は見つからなかったで終わり)
  105. var rawtitle = tmp[1];
  106. var ss = v2c.readURL(bd.url + 'subject.txt');
  107. if (v2c.interrupted) { return false; }
  108. if (!ss) { printFunc(arguments.callee, "{0}の取得に失敗", bd); return false; }
  109. var lines = ss.split('\n');
  110. var now = Math.floor((new Date()).getTime() / 1000);
  111. var maxmatches = [];
  112. printFunc(arguments.callee, "【リダイレクト先の検索開始】番組名:{0}", rawtitle);
  113. var title = PRE_FUNC(rawtitle);
  114. for (var i = 0; i < lines.length; i++) {
  115. if (/^(\d+)\.dat<>(.+) \((\d+)\)/.test(lines[i])) {
  116. var current = RegExp.$1;
  117. var name = RegExp.$2;
  118. var resnum = RegExp.$3;
  119. var speed = parseInt(resnum) / ((now - parseInt(current)) / 86400)
  120. tmp = PRE_FUNC(name);
  121. if (OUTPUT_COMPARE) {
  122. v2c.println(title.value + '\t=\t' + tmp.value);
  123. }
  124. if (tmp.deny) {
  125. printFunc(arguments.callee, "DENY_WORDS「{1}」にヒットしたので「{0}」を除外します。", tmp.deny, name);
  126. continue;
  127. }
  128. var score = levenshtein(title.value, tmp.value);
  129. if (score >= threshold) {
  130. maxmatches.push({'speed': speed, key: current, 'title': name, 'resnum': parseInt(resnum), 'score' : score});
  131. }
  132. }
  133. }
  134. if (maxmatches.length > 0) {
  135. // 1001に達していないなるべく古いスレを優先する。見つからない場合は1001しかない=新スレが立ってないってことで最も新しいスレを返す
  136. // スレ立て時間昇順
  137. maxmatches.sort(function(a, b) {
  138. if (a.score === b.score) { return parseInt(a.key) - parseInt(b.key); }
  139. else return a.score - b.score;
  140. });
  141. var max = maxmatches[maxmatches.length - 1]; // 最後に立ったスレを入れとく
  142. for (var i = 0; i < maxmatches.length; i++) {
  143. if (maxmatches[i].resnum == 1001) { continue; }
  144. max = maxmatches[i];
  145. break;
  146. }
  147. printFunc(arguments.callee, "【検索終了。スレッドの取得を試みます】スレタイ名:{0} ", max.title);
  148. var u = new java.net.URL('http://' + bd.url.getHost() + '/test/read.cgi/' + bd.key + '/' + max.key + '/');
  149. if (BOARDVIEW) {
  150. v2c.openURL(bd.url);
  151. java.lang.Thread.sleep(THUPDATE_WAIT);
  152. v2c.openURL(u, true, false, false);
  153. return true;
  154. } else {
  155. var th2 = bd.getThread(max.key, bd.url, max.title, max.resnum);
  156. if (th2) {
  157. th2.open(true, false, false);
  158. return true;
  159. } else {
  160. printFunc(arguments.callee, "【スレッドの取得に失敗したのでスレ一覧を開きます】番組名 : {0}", rawtitle);
  161. v2c.openURL(bd.url);
  162. }
  163. }
  164. } else {
  165. printFunc(arguments.callee, "【スレッドを見つけられなかったのでスレ一覧を開きます】番組名 : {0}", rawtitle);
  166. v2c.openURL(bd.url);
  167. }
  168. }
  169. }
  170. // 文字列の類似比較用距離関数
  171. function levenshtein(s1, s2) {
  172. // http://k...content-available-to-author-only...d.net
  173. // + original by: Carlos R. L. Rodrigues (http://w...content-available-to-author-only...l.com)
  174. // + bugfixed by: Onno Marsman
  175. // + revised by: Andrea Giammarchi (http://w...content-available-to-author-only...t.com)
  176. // + reimplemented by: Brett Zamir (http://b...content-available-to-author-only...r.me)
  177. // + reimplemented by: Alexander M Beedie
  178. // * example 1: levenshtein('Kevin van Zonneveld', 'Kevin van Sommeveld');
  179. // * returns 1: 3
  180.  
  181. if (s1 == s2) {
  182. return 0;
  183. }
  184.  
  185. var s1_len = s1.length;
  186. var s2_len = s2.length;
  187. if (s1_len === 0) {
  188. return s2_len;
  189. }
  190. if (s2_len === 0) {
  191. return s1_len;
  192. }
  193.  
  194. // BEGIN STATIC
  195. var split = false;
  196. try{
  197. split=!('0')[0];
  198. } catch (e){
  199. split=true; // Earlier IE may not support access by string index
  200. }
  201. // END STATIC
  202. if (split){
  203. s1 = s1.split('');
  204. s2 = s2.split('');
  205. }
  206.  
  207. var v0 = new Array(s1_len+1);
  208. var v1 = new Array(s1_len+1);
  209.  
  210. var s1_idx=0, s2_idx=0, cost=0;
  211. for (s1_idx=0; s1_idx<s1_len+1; s1_idx++) {
  212. v0[s1_idx] = s1_idx;
  213. }
  214. var char_s1='', char_s2='';
  215. for (s2_idx=1; s2_idx<=s2_len; s2_idx++) {
  216. v1[0] = s2_idx;
  217. char_s2 = s2[s2_idx - 1];
  218.  
  219. for (s1_idx=0; s1_idx<s1_len;s1_idx++) {
  220. char_s1 = s1[s1_idx];
  221. cost = (char_s1 == char_s2) ? 0 : 1;
  222. var m_min = v0[s1_idx+1] + 1;
  223. var b = v1[s1_idx] + 1;
  224. var c = v0[s1_idx] + cost;
  225. if (b < m_min) {
  226. m_min = b; }
  227. if (c < m_min) {
  228. m_min = c; }
  229. v1[s1_idx+1] = m_min;
  230. }
  231. var v_tmp = v0;
  232. v0 = v1;
  233. v1 = v_tmp;
  234. }
  235. return v0[s1_len];
  236. }
  237. function redirectNewsThread(th, cx) {
  238. if (!cx.error && th.board.key.match(/(bbynews|bbylive|bbynamazu|bbylunch)/)) {
  239. var u = th.getRes(1).links[0];
  240. v2c.openURL(u,true,false,false);
  241. }
  242. }
  243. function openWritePanel(th) {
  244. if (
  245. th.bbs.is2ch
  246. /* 2ch以外でも有効にしたい場合、下の各行頭//を削除してください */
  247. // || th.bbs.is2cheq //BBSが2ch互換板の場合
  248. // || th.bbs.shitaraba //BBSがしたらばの場合
  249. // || th.bbs.machi //BBSがまちBBSの場合
  250. // || th.bbs.twitter //BBSがTwitterの場合
  251. ) {
  252. th.mayOpenWritePanel();
  253. }
  254. }
  255. // 対象文字列のチェック
  256. function checkThStr(str, target) {
  257. var tlen, i;
  258. if ((tlen = target.length) > 0) {
  259. for (i=0; i<tlen; i++) {
  260. if (str.indexOf(target[i]) > -1) {
  261. return true;
  262. }
  263. }
  264. }
  265. return false;
  266. }
  267.  
  268. // IDのNG判定
  269. function idConditionalNG(th, cx) {
  270. /* 設定 */
  271. // idCountがこの値を超えたらそのIDをNG 初期値20
  272. var threshold = 20;
  273. // trueなら透明NGID、falseなら通常NGID
  274. var set_t = false;
  275. // NG登録後全てのレス表示タブのスレッドでも再チェックさせるならtrue,そうでないならfalse
  276. // 重いようならfalseにしてそのスレだけで再チェック
  277. var rechk_rp = false;
  278. // IDの長さがこの値を超えたらチェック対象にする
  279. var inflen = 7;
  280. // 実行する対象URL、スレURLに含まれるか(th.url.toString().indexOf() > 0)を
  281. // チェックするので ".2ch.net/test/read.cgi/news/" という風に
  282. // 複数あるときは , で区切る
  283. // 2chすべての板を対象にするなら ".2ch.net/test/read.cgi/" など
  284. // すべてを対象にするなら "." など
  285. // ↓は既にニュー速と嫌儲を登録、必要ないなら削除
  286. var target = [
  287. ".2ch.net/test/read.cgi/news/",
  288. ".2ch.net/test/read.cgi/poverty/",
  289. ];
  290. // プラットフォームごとの闘値機能を有効にするならtrue、そうでないならfalse
  291. var xtargetThresholdsEnabled = true;
  292. // プラットフォームごとの闘値 初期値20
  293. var xtargetThresholds = {
  294. 'O' : 20, // 携帯
  295. 'o' : 20, // WiLLCOMの一部機種
  296. 'I' : 20, // iPhone
  297. 'i' : 20, // iPhone
  298. 'P' : 20, // p2.2ch.netからの投稿
  299. '0' : 20, // PC, wifi経由等
  300. };
  301. /* 設定終わり */
  302. if(!cx.error && cx.numNewRes > 0 && checkThStr(th.url.toString(), target)) {
  303. var srtnum = th.localResCount - cx.numNewRes;
  304. var res, i;
  305. var rflag = false;
  306. for (i=srtnum; i<th.localResCount; i++) {
  307. res = th.getRes(i);
  308. if (res != null && !res.ng && res.id && res.id.length() > inflen) {
  309. if (xtargetThresholdsEnabled && res.id.length() == 9) {
  310. var thresTmp = xtargetThresholds[String(res.id).charAt(8)];
  311. if (thresTmp != undefined && res.idCount > thresTmp) {
  312. res.addNGID(set_t);
  313. rflag = true;
  314. }
  315. continue;
  316. }
  317. if (res.idCount > threshold) {
  318. res.addNGID(set_t);
  319. rflag = true;
  320. }
  321. }
  322. }
  323. // 追加があったら再チェック
  324. if (rflag) {
  325. v2c.resPane.checkNG(th);// ←重いようならth.boardをthにしてそのスレだけで再チェック
  326. }
  327. }
  328. }
  329. // 新着レスを対象に単発IDのレスを非表示にする。
  330. // 既にNGとなっている場合はNGラベルの見分けがつくように何もしない。
  331. // [through]
  332. // 単発でもスルーするレスの範囲(through=50で>>1-50のレスを飛ばす。)
  333. function setNewTanpatsuNG(th, cx, through) {
  334. var lrc = th.localResCount
  335. var format = '非表示の単発IDスカウター: '
  336. var scouter = 0
  337.  
  338. for ( var i = lrc - cx.numNewRes; i < lrc; i++ ) {
  339. if ( i < through ) continue;
  340.  
  341. var res = th.getRes( i )
  342. if ( res && res.idCount == 1 && !res.ng ) {
  343. res.setNGRes()
  344. scouter++
  345. v2c.setStatus( format + scouter )
  346. }
  347. }
  348. }
  349. function checkNewTanpatsuNG( th, cx ) {
  350. var target = [ //実況カテゴリ
  351. 'live',
  352. 'endless',
  353. 'weekly',
  354. 'kokkai',
  355. 'dome',
  356. 'oonna',
  357. 'ootoko',
  358. 'dancesite',
  359. 'festival',
  360. 'jasmine',
  361. ];
  362. var targetTanpatsu = /Firefox|Google/i
  363.  
  364. if ( cx.numNewRes > 0 ) {
  365. if ( th.bbs.is2ch && checkThStr(th.board.key, target) ) {
  366. setNewTanpatsuNG( th, cx, 75 );
  367. } else if ( targetTanpatsu.test( th.title ) ) {
  368. setNewTanpatsuNG( th, cx, 1 );
  369. }
  370. egnoreNewNGRes( th, cx )
  371. }
  372. }
  373.  
  374. function openFixedColumn(th) {
  375. /* 設定 */
  376. var index = 0;//スレッド開くときに常に使用するカラムIndex
  377. /* 設定ここまで */
  378.  
  379. if (th.columnIndex>index) {
  380. th.movePanelTo(index, -1);
  381. th.open(false, false, false);//移動した後表示
  382. }
  383. }
  384. function groupThreadPanel(th) {
  385. /* 設定 */
  386. var target = [ //実況カテゴリ
  387. 'live',
  388. 'endless',
  389. 'weekly',
  390. 'kokkai',
  391. 'dome',
  392. 'oonna',
  393. 'ootoko',
  394. 'dancesite',
  395. 'festival',
  396. 'jasmine',
  397. ];
  398. var targetIndex = 0;//target1の場合に開くColumnIndex
  399. var otherIndex = 1;//target以外で開くColumnIndex
  400. /* 設定ここまで */
  401.  
  402. if (th.bbs.is2ch && checkThStr(th.board.key, target) || th.bbs.twitter) {
  403. if (th.columnIndex != targetIndex) {
  404. th.movePanelTo(targetIndex, -1);
  405. th.open(false, false, false);//移動した後表示
  406. }
  407. } else {
  408. if (th.columnIndex != otherIndex) {
  409. th.movePanelTo(otherIndex, -1);
  410. th.open(false, false, false);//移動した後表示
  411. }
  412. }
  413.  
  414. }
  415.  
  416. function guroNG(th, cx)
  417. {
  418. for (var i = (th.localResCount - cx.numNewRes); i < th.localResCount; i++) {
  419. var res = th.getRes(i);
  420. var decision = false;
  421. if (res.refResIndex && res.refResIndex.length > 0 && res.links.length > 0) {
  422.  
  423. for (var j = 0; j < res.refResIndex.length; j++) {
  424. decision = (th.getRes(res.refResIndex[j]).message.indexOf('グロ') >= 0) ? true : decision;
  425. }
  426. }
  427. if (decision) v2c.println(res.message);
  428. if (decision) res.setNGRes(true);
  429. }
  430. }
  431.  
Not running #stdin #stdout 0s 0KB
stdin
Standard input is empty
stdout
Standard output is empty