はてブコメントに日時を書くと hCalendar を生成する Greasemonkey スクリプト - memo88
https://memo88.hatenablog.com/entry/20101120/1290219501
Created
June 18, 2020 11:52
-
-
Save sonota88/3187c5f60f67c3eafd31fbf0b457035f to your computer and use it in GitHub Desktop.
bhatena-hcalendar.user.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name bhatena-hcalendar | |
// @namespace anbt | |
// @include * | |
// ==/UserScript== | |
/* | |
dt: DateTime | |
*/ | |
function puts(){ console.log(arguments); } | |
function strip(str){ | |
return str.replace( /^[\s\t\n\r\n]+/, "" ).replace( /[\s\t\r\n]+$/, "" ); | |
} | |
function xid(){ | |
return document.getElementById(arguments[0]); | |
} | |
function include(array, target){ | |
var result = []; | |
for(var a=0; a<array.length; a++){ | |
if( target === array[a] ){ | |
return true; | |
} | |
} | |
return false; | |
} | |
function hasClass(elem, className){ | |
return elem.getAttribute("class") | |
&& include( elem.getAttribute("class").split(" "), className); | |
} | |
function xclass( root, className ){ | |
var elems = root.getElementsByTagName("*"); | |
var result = []; | |
for(var a in elems){ | |
if( elems[a].getAttribute && hasClass(elems[a], className) | |
){ | |
result.push(elems[a]); | |
} | |
} | |
return result; | |
} | |
function createElement(parent, tagName, attributes, styles, innerHTML){ | |
var e = document.createElement(tagName); | |
if(attributes){ | |
for(var key in attributes){ | |
e.setAttribute(key, attributes[key]); }} | |
if(styles){ | |
for(var key in styles){ | |
e.style[key] = styles[key]; }} | |
if(innerHTML){ | |
e.innerHTML = innerHTML; } | |
if(parent){ | |
parent.appendChild(e); } | |
return e; | |
} | |
//////////////////////////////// | |
var doc = document; | |
var DefaultDiff = 1000 * 60 * 60; // 1h | |
var veventStyle = { | |
border: "solid 1px #ddd" | |
, backgroundColor: "#f8f8f8" | |
, padding: "1ex" | |
, fontSize: "80%" | |
, marginLeft: "8ex" | |
}; | |
//////////////////////////////// | |
function zeroPadding(num, digit){ | |
var str = num.toString(); | |
while(str.length < digit){ | |
str = "0" + str; | |
} | |
return str; | |
} | |
function formatDate(date){ | |
return "" | |
+ date.getFullYear() | |
+ "-" + zeroPadding( date.getMonth() + 1, 2 ) | |
+ "-" + zeroPadding( date.getDate(), 2 ); | |
} | |
function formatDateTime(dt){ | |
if( ! dt | |
|| dt === YMDError | |
|| dt === HMError ) | |
{ | |
throw "invalid"; | |
} | |
return formatDate(dt) | |
+ " " + dig2(dt.getHours()) | |
+ ":" + dig2(dt.getMinutes()); | |
} | |
function formatDateTimeRange(parsed){ | |
return formatDateTime(parsed.start) + "--" + formatDateTime(parsed.end); | |
} | |
function parseYMD(str){ | |
var year, month, date; | |
if( str.match(/^(\d{4})[\.\-/]([01]?\d)[\.\-/]([0-3]?\d)$/) | |
|| str.match(/^(\d{4})([01]\d)([0-3]\d)$/) | |
) | |
{ | |
year = RegExp.$1; | |
month = RegExp.$2; | |
date = RegExp.$3; | |
}else if( str.match(/^([01]?\d)[\.\-/]([0-3]?\d)$/) ){ | |
year = (new Date()).getFullYear(); | |
month = RegExp.$1; | |
date = RegExp.$2; | |
}else{ | |
throw "invalid YMD"; | |
} | |
return { | |
year: year | |
, month: month | |
, date: date | |
}; | |
} | |
function parseHM(hm, dtStart){ | |
var hours, minutes; | |
if( ! hm ){ | |
if(dtStart){ | |
hours = 23; | |
minutes = 59; | |
}else{ | |
hours = 0; | |
minutes = 0; | |
} | |
}else if( hm.match(/^([0-2]?\d):([0-5]?\d)$/) | |
|| hm.match(/^([0-2]\d)([0-5]\d)$/) | |
) | |
{ | |
hours = RegExp.$1; | |
minutes = RegExp.$2; | |
}else{ | |
throw "must not happen"; | |
} | |
return { | |
hours: hours | |
, minutes: minutes | |
}; | |
} | |
function parseDateTime(str, dtStart){ | |
var baseDT; | |
if(dtStart){ | |
baseDT = dtStart; | |
}else{ | |
baseDT = new Date(); | |
} | |
var ymd, hm; | |
var year, month, date, hours, minutes; | |
if( str.match(/^(.+?) (.+)$/) ){ // date and time | |
ymd = RegExp.$1; | |
hm = RegExp.$2; | |
}else{ // date or time | |
if( str.match(/^([0-2]?\d):?([0-5]?\d)$/) ){ // time | |
ymd = formatDate(baseDT); | |
hm = str; | |
}else{ // date | |
ymd = str; | |
hm = null; | |
} | |
} | |
try{ | |
var _ymd = parseYMD(ymd); | |
year = _ymd.year; | |
month = _ymd.month; | |
date = _ymd.date; | |
}catch(e){ | |
if(dtStart){ | |
throw "end:date"; | |
}else{ | |
throw "start:date"; | |
}; | |
} | |
try{ | |
var _hm = parseHM(hm, dtStart); | |
hours = _hm.hours; | |
minutes = _hm.minutes; | |
}catch(e){ | |
if(dtStart){ | |
throw "end:time"; | |
}else{ | |
throw "start:time"; | |
}; | |
} | |
return new Date(year, month-1, date, hours, minutes); | |
} | |
function parseDateTimeRange(str){ | |
var strStart, strEnd; | |
var dtStart, dtEnd; | |
if( str.match(/^(.+?)\+(.+)$/) ){ | |
strStart = RegExp.$1; | |
var tempDiff = RegExp.$2; | |
var unit; | |
if( tempDiff.match( /^(\d+)([dhm])$/ ) ){ | |
tempDiff = parseInt(RegExp.$1); | |
unit = RegExp.$2; | |
}else{ | |
throw "invalid diff"; | |
} | |
var diff; | |
switch(unit){ | |
case "d": | |
diff = tempDiff * 24 * 60 * 60 * 1000; break; | |
case "h": | |
diff = tempDiff * 60 * 60 * 1000; break; | |
case "m": | |
diff = tempDiff * 60 * 1000; break; | |
default: | |
throw "must not happen"; | |
} | |
dtStart = parseDateTime(strStart); | |
dtEnd = new Date( dtStart.getTime() + diff ); | |
}else if( str.match(/^(.+?)--(.+)$/) ){ | |
strStart = RegExp.$1; | |
strEnd = RegExp.$2; | |
dtStart = parseDateTime(strStart); | |
dtEnd = parseDateTime(strEnd, dtStart); | |
}else{ | |
if( str.match(/^(.+)--$/) ){ | |
str = RegExp.$1; | |
} | |
dtStart = parseDateTime(str); | |
if( str.match(/^(.+) (.+)$/) | |
|| str.match(/^(\d?\d):?(\d?\d)$/) ){ | |
dtEnd = new Date( dtStart.getTime() + DefaultDiff ); | |
}else{ | |
dtEnd = parseDateTime(str, dtStart); | |
} | |
} | |
return { | |
start: dtStart | |
, end: dtEnd | |
}; | |
} | |
//////////////////////////////// | |
function findUserMetaData(bm, key){ | |
var re = new RegExp( "<" + key + ":(.+?)>"); | |
if( bm.comment.match(re) ){ | |
return strip( RegExp.$1 ); | |
}else{ | |
return null; | |
} | |
} | |
//////////////////////////////// | |
function getDateTime(bm){ | |
var dateText = findUserMetaData(bm, "dt"); | |
return parseDateTimeRange(dateText); | |
} | |
function getSummary(bm){ | |
return xid( "head-entry-link" ).innerHTML; | |
} | |
// todo: #entry-extract-content が取れない時があるので対処すべき | |
function getDescription(bm){ | |
var description; | |
if( xid( "entry-extract-content" ) ){ | |
description = xid( "entry-extract-content" ).textContent.substr(0, 140); | |
}else{ | |
description = "description not found"; | |
} | |
return description; | |
} | |
function getLocation(bm){ | |
var value = findUserMetaData(bm, "at"); | |
if( value ){ | |
return value; | |
}else{ | |
return null; | |
} | |
} | |
function getURL(){ | |
return xid("head-entry-link").href; | |
} | |
//////////////////////////////// | |
function date2rfc3339(date){ | |
var result = ""; | |
result = date.getFullYear() | |
+ "-" + zeroPadding( date.getMonth()+1, 2 ) | |
+ "-" + zeroPadding( date.getDate(), 2 ); | |
result += "T"; | |
result += zeroPadding( date.getHours(), 2); | |
result += ":" + zeroPadding( date.getMinutes(), 2); | |
result += ":00+09:00"; | |
return result; | |
} | |
function shortDateTime(date){ | |
var result = ""; | |
if( date.getFullYear() !== (new Date()).getFullYear() ){ | |
result += date.getFullYear() + "-"; | |
} | |
result += zeroPadding( date.getMonth()+1, 2 ) | |
+ "-" + zeroPadding( date.getDate(), 2 ); | |
result += " "; | |
result += zeroPadding( date.getHours(), 2); | |
result += ":" + zeroPadding( date.getMinutes(), 2); | |
return result; | |
} | |
//////////////////////////////// | |
function getInnerHTML(bm){ | |
var summary = getSummary(bm); | |
var description = getDescription(bm); | |
var location = getLocation(bm); | |
var url = getURL(); | |
var html = ""; | |
html += "<p>"; | |
html += "<span class='summary' style='font-weight: bold; display: none;'>" | |
+ summary + "</span>"; | |
if(location){ | |
html += " (at <span class='location'>" | |
+ location + "</span>)"; | |
} | |
html += "</p>"; | |
html += '<p>'; | |
try{ | |
var dt = getDateTime(bm); | |
var sDateText = date2rfc3339(dt.start); | |
var eDateText = date2rfc3339(dt.end); | |
html += "<span class='dtstart' style='font-family: monospace; background-color: #ffa;' title='"+ sDateText +"'>" | |
+ shortDateTime(dt.start) + "</span>"; | |
html += "--<span class='dtend' style='font-family: monospace; background-color: #ffa;' title='"+ eDateText +"'>" | |
+ shortDateTime(dt.end) + "</span>"; | |
}catch(e){ | |
html += "(" + e + ") "; | |
} | |
html += "</p>"; | |
html += "<p class='description' style='padding-left: 2ex; display: none;'>" | |
+ description + "</p>"; | |
html += "<a class='url' style='display: none;' href='"+url+"'>url</a>"; | |
return html; | |
} | |
function procBookmark(bm){ | |
if( findUserMetaData( bm, "dt" ) ){ | |
var hCalendar = createElement( | |
bm, "div", { "class": "vcalendar" } | |
); | |
createElement( | |
hCalendar | |
, "div" | |
, { "class": "vevent" } | |
, veventStyle | |
, getInnerHTML(bm) | |
); | |
} | |
} | |
//////////////////////////////// | |
// for my bookmark | |
function getSelfBM(){ | |
var temp_ul = xid("bookmarked_user"); | |
var selfBookmark = xclass( temp_ul, "self" )[0]; | |
if(selfBookmark){ | |
selfBookmark.comment = xclass(selfBookmark, "comment")[0].innerHTML | |
.replace(/</g, "<") | |
.replace(/>/g, ">"); | |
return selfBookmark; | |
}else{ | |
return null; | |
} | |
} | |
function refreshVEvent(bm, comment){ | |
if(comment){ bm.comment = comment; } | |
var vevent = xclass(bm, "vevent")[0]; | |
if( ! findUserMetaData( bm, "dt" ) ){ | |
vevent.innerHTML = ""; | |
vevent.style.display = "none"; | |
return; | |
} | |
vevent.innerHTML = getInnerHTML(bm); | |
vevent.style.display = "block"; | |
} | |
var selfbm = getSelfBM(); | |
if(selfbm){ | |
var hCalendar = createElement( | |
selfbm, "div", { "class": "vcalendar" } | |
); | |
var vevent = createElement( | |
hCalendar | |
, "div" | |
, { "class": "vevent" } | |
, veventStyle | |
, getInnerHTML(selfbm) | |
); | |
if( ! findUserMetaData( selfbm, "dt" ) ){ | |
vevent.style.display = "none"; | |
} | |
selfbm.addEventListener( | |
"change" | |
, function(e){ | |
refreshVEvent(selfbm, e.target.value); | |
} | |
, false | |
); | |
} | |
//////////////////////////////// | |
// for other bookmarks | |
function getOtherBookmarks(){ | |
var temp = xid("bookmarked_user").childNodes; | |
var bookmarks = []; | |
for(var a=0; a<temp.length; a++){ | |
if(temp[a].tagName === "LI" | |
&& ! hasClass(temp[a], "self") ) | |
{ | |
bookmarks.push(temp[a]); | |
} | |
} | |
return bookmarks; | |
} | |
var others = getOtherBookmarks(); | |
var bm; | |
for(var a=0; a<others.length; a++){ | |
bm = others[a]; | |
// if(a==0){ // for debug | |
// xclass(bm, "comment")[0].style.display = "inline"; | |
// xclass(bm, "comment")[0].style.visibility = "visible"; | |
// xclass(bm, "comment")[0].innerHTML = "<dt: 20010102 1234>"; | |
// } | |
bm.comment = xclass(bm, "comment")[0].innerHTML | |
.replace(/</g, "<") | |
.replace(/>/g, ">"); | |
if(bm.comment){ procBookmark(bm); } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment