(仮)Wizball の詳細ページを保存用に、ちょっといじる

  • /*
     * @title (仮)Wizball の詳細ページを保存用に、ちょっといじる
     * @description (仮)Wizball の詳細ページを保存用に、ちょっといじる
     * @include https://m.wizball.io/questions/*
     * @license MIT License http://opensource.org/licenses/MIT
     * @javascript_url *
     */
    /*
    ・コメントを全て展開して、全文表示されるようにした
    ・コメントにアイコンをつけた
    ・Voter のアイコンを表示するようにした
    ・投稿時刻を日時にした
    ・下部の「人気の質問」を消した
    ・表示幅を、最大 640px にした
    
    ■免責事項
    この Bookmarklet を使うことによって生じた、いかなる損害、不利益等に関しても a-kuma3 は一切の責任を負いません。
    それを承諾した方だけ、お使いください。
    
    history
    2019-07-28  バッククォートを外して、Hatena::Let へ
    2019-08-01  Wizball で告知
    2019-08-02  Chrome : デフォルトアイコンだと名前が出ちゃう
    2019-08-02  Chrome : コメントが複数行になってない
    2019-08-02  コメントにポリシー違反があったときの対応
    
    */
    (async _ => {
    	if (location.hostname != "m.wizball.io") {
    		alert("Please use at m.wizball.io !");
    		return;
    	}
    	const d_ = document;
    	const ts = Date.now();
    	const msg = d_.querySelector(".question_logo");
    	async function display_comments() {
    //		console.log("!!! load comments !!!");
    		const list = d_.querySelectorAll(".comment_list");
    		for (let i = 0 ; i < list.length ; ++i) {
    			const first_item = list[i].querySelector(".comment_txt");
    			if (! first_item) {		// 質問はコメントがなくても UL 要素がある
    				continue;
    			}
    			const orig_url = first_item.href;
    			const url_base = orig_url.replace(/questions/, "apis/v1/questions");
    			const comments = [];
    			let page = 1;
    			while (true) {
    				const url = url_base + "?page=" + page + "&size=50&sort=LATEST";
    				const resp = await fetch(url);
    				const json = await resp.json();
    				if (json.length > 0) {
    					comments.push(... json);
    					page += 1;
    				} else {
    					break;
    				}
    			}
    //			console.log(comments);
    			list[i].innerHTML = "";
    			comments.forEach(c => {
    				let img;
    				if (c.status == "PENALTY") {
    					const item = Object.assign(d_.createElement("li"), {
    						className: "comment_item",
    						innerHTML: '<div class="message_area"><div class="message_area_inner"><p class="message">サービスポリシー違反により非表示になりました。</p></div></div>',
    					});
    					list[i].appendChild(item);
    					return;
    				}
    				if (c.author.image) {
    					img = [
    						'<img src="https://obs.line-scdn.net/',
    						c.author.image.hash,
    						'/s150" class="user_20" title="',
    						c.author.nickname,
    						'">'
    					].join("");
    				} else {
    					img = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=" class="user_20" title="' + c.author.nickname + '">';
    				}
    				const item = Object.assign(d_.createElement("li"), {
    					className: "comment_item",
    					innerHTML: [
    						'<div class="comment_item_inner"><div class="profile_img comment_icon">',
    						img,
    						'</div><a href="/users/',
    						c.author.id,
    						'" class="user_id">',
    						c.author.nickname,
    						'</a> <div class="comment_txt type_space">',
    						c.text,
    						'</a></div>',
    					].join(""),
    				});
    				list[i].appendChild(item);
    			});
    			const comment_value = list[i].parentNode.querySelector(".comment_list ~ .comment_value");
    //			console.log(comment_value);
    			list[i].parentNode.insertBefore(comment_value, list[i]);
    		}
    	}
    
    	async function load_q(qid) {
    		const resp = await fetch("/apis/v1/questions/" + qid);
    		const json = await resp.json();
    		return json;
    	}
    	async function load_a(qid) {
    		const map = {};
    		for (let p = 1 ; p <= 300 ; ++p) {
    			const resp = await fetch("/apis/v1/questions/" + qid + "/answers?page=" + p + "&size=50&sort=POPULAR");
    			const json = await resp.json();
    			if (json.length == 0) {
    				break;
    			}
    			json.forEach(a => {
    				map[a.id] = a;
    			});
    		}
    		return map;
    	}
    	async function load_v(a) {
    		let ub;
    		if (a.questionId) {		// answer
    			ub = "/apis/v1/questions/" + a.questionId + "/answers/" + a.id + "/voters?size=50&sort=LATEST&";
    		} else { // question
    			ub = "/apis/v1/questions/" + a.id + "/votes?size=50&sort=LATEST&";
    		}
    		const list = [];
    		for (let p = 1 ; p <= 300 ; ++p) {
    			const resp = await fetch(ub + "page=" + p);
    			const json = await resp.json();
    			if (json.length == 0) {
    				break;
    			}
    			list.push(... json);
    		}
    		return list;
    	}
    
    	function modify_time(e, q) {
    		const qt = e.querySelector(".user_info_detail.date");
    		qt.innerHTML = (new Date(q.createdAt)).toLocaleString();
    	}
    
    	async function display_voters(e, a) {
    		const eopt = e.querySelector(".qna_option_area");
    		const ea = d_.createElement("div");
    		ea.className = "vote_area";
    		eopt.parentNode.insertBefore(ea, eopt);
    //		eopt.parentNode.insertBefore(eul, eopt);
    		const v = await load_v(a);
    		ea.innerHTML = '<div class="vote_value">Vote : ' + v.length + '</div>';
    		const eul = d_.createElement("ul");
    		ea.appendChild(eul);
    		eul.className = "voter_list";
    		v.forEach(v => {
    			const li = eul.appendChild(d_.createElement("li"));
    			li.className = "voter_item profile_img";
    //			li.innerHTML = v.author.nickname + ", ";
    			if (v.author.image) {
    				li.innerHTML = '<img src="https://obs.line-scdn.net/' + v.author.image.hash + '/s150" class="user_20" title="' + v.author.nickname + '">';
    			} else {
    				li.innerHTML = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=" class="user_20" title="' + v.author.nickname + '">';
    			}
    		});
    	}
    
    	async function modify_data() {
    		msg.innerHTML = "質問データを取得中...";
    		const m = /\/questions\/(\d+)/.exec(location.href);
    		const qid = m[1];
    		const q = await load_q(qid);
    		const qe = d_.querySelector(".qna_area.question");
    		modify_time(qe, q);
    		msg.innerHTML = "質問のVoterを取得中...";
    //		const qv = await load_v(q);
    		await display_voters(qe, q);
    
    		msg.innerHTML = "回答データを取得中...";
    		const a = await load_a(qid);
    		const ae = d_.querySelectorAll(".qna_area.answer .answer_item");
    //		console.log(ae);
    		msg.innerHTML = "回答のVoterを取得中...";
    		for (let i = 0 ; i < ae.length ; ++i) {
    			const bc = ae[i].querySelector(".btn_comment");
    			if (bc) {
    				const m = /answers\/(\d+)/.exec(bc.href);
    				const aid = m[1];
    				modify_time(ae[i], a[aid]);
    	//			const av = await load_v(a);
    				await display_voters(ae[i], a[aid]);
    			}
    			// else PENALTY
    		}
    	}
    
    //	function x() {
    //		const imgs = d_.querySelectorAll("img");
    //		const img = imgs[1];
    //		console.log(img);
    //		let canvas = Object.assign(document.createElement("canvas"), {
    //            width: img.width,
    //            height: img.height,
    //        });
    //        let ctx = canvas.getContext('2d');
    //        ctx.drawImage(img, 0, 0);
    //		const uri = canvas.toDataURL();		// The operation is insecure.
    //		console.log(uri);
    //		img.src = uri;
    //	}
    
    	async function modify_window() {
    		msg.innerHTML = "コメントを読み込み中...";
    		await display_comments();
    		await modify_data();
    //		x();
    		msg.innerHTML = "★完了★";
    		setTimeout(function() {
    			msg.innerHTML = "";
    		}, 3000);
    		console.log("!!! " + ((Date.now() - ts)/1000.0) + " sec");
    	}
    
    	const more_btn = d_.querySelector(".qna_area.answer .btn_more_answer");
    	if (more_btn) {
    		d_.head.appendChild(Object.assign(d_.createElement("style"), {
    			innerHTML: [
    'body {',
    '	max-width: 640px;',
    '}',
    '.wrap.home_end .content .qna_area_inner .comment_area .comment_item_inner .comment_txt {',
    '	max-height: none;',
    '	display: block;',
    '}',
    '.comment_item_inner .comment_icon, .voter_list .voter_item {',
    '	width: 20px;',
    '	height: 20px;',
    '	margin-right: 0.5ex;',
    '}',
    '.comment_item_inner .comment_icon {',
    '	float: left;',
    '}',
    '.user_20 {',
    '	border-radius: 50%;',
    '	width: 100%;',
    '}',
    '.wrap.home_end .content .question_logo {',
    '	font-size: inherit;',
    '	color: inherit;',
    '	font-size: 15px;',
    '	color: #959aa5;',
    '}',
    '.wrap.home_end .content .qna_area_inner .comment_area .comment .comment_value {',
    '	padding-top: 0;',
    '	padding-bottom: 10px;',
    '}',
    '.qna_area.question .vote_area {',
    '	padding-left: 22px;',
    '}',
    '.vote_area {',
    '	padding-top: 14px;',
    '}',
    '.vote_value {',
    '	padding-bottom: 10px;',
    '}',
    '.vote_area {',
    '	color: #959aa5;',
    '	font-size: 15px;',
    '}',
    '.voter_item {',
    '	display: inline-block;',
    '}',
    '.related_question_area {',
    '	display: none;',
    '}',
    '.wrap.home_end .content .qna_area.answer .message_area {',
    '	height: initial;',
    '	background-color: silver;',
    '}',
    '.wrap.home_end .content .message_area .message {',
    '	color: #777;',
    '}',
    		].join(""),
    		}));
    		let n = 0;
    		const answer_list = d_.querySelector(".answer_list");
    		// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
    		const MutationObserver = window.MutationObserver || window.WebkitMutationObserver;
    		const observer = new MutationObserver(async function (records) {
    			n += 1;
    //			console.log(n);
    			if (more_btn.style.display != "none") {
    				more_btn.click();
    				if (n > 10) {
    					observer.disconnect();
    				}
    			} else {
    				await modify_window();
    			}
    		});
    		observer.observe(answer_list, { childList: true, /* subtree: true */ });
    
    		if (more_btn.style.display != "none") {
    			msg.innerHTML = "回答を読み込み中...";
    			more_btn.click();
    		} else {
    			await modify_window();
    		}
    	}
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2019/08/02 16:59:35 - 2019-08-02
  2. 2019/08/02 10:08:34 - 2019-08-02
  3. 2019/08/01 14:59:20 - 2019-08-01
  4. 2019/07/30 15:35:21 - 2019-07-30
  5. 2019/07/30 15:29:14 - 2019-07-30
  6. 2019/07/30 09:38:45 - 2019-07-30
  7. 2019/07/30 09:35:40 - 2019-07-30
  8. 2019/07/28 12:59:44 - 2019-07-28
  9. 2019/07/28 02:26:09 - 2019-07-28
  10. 2019/07/28 02:22:46 - 2019-07-28
  11. 2019/07/28 02:20:34 - 2019-07-28
  12. 2019/07/28 02:17:53 - 2019-07-28
  13. 2019/07/28 02:15:56 - 2019-07-28
  14. 2019/07/28 02:04:34 - 2019-07-28