Last active
December 31, 2022 04:44
-
-
Save fqj1994/87ddd40413d70fa99cdbfafda9732d34 to your computer and use it in GitHub Desktop.
Eventernote Helper Userscript
This file contains 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 Eventernote Helper | |
// @namespace eventernotehelper | |
// @version 0.46 | |
// @description eventernote helper to manage schedule easier. | |
// @author fqj1994 | |
// @match *://www.eventernote.com/* | |
// @grant GM_xmlhttpRequest | |
// @updateURL https://gist.github.com/fqj1994/87ddd40413d70fa99cdbfafda9732d34/raw/eventernote.user.js | |
// @downloadURL https://gist.github.com/fqj1994/87ddd40413d70fa99cdbfafda9732d34/raw/eventernote.user.js | |
// ==/UserScript== | |
let myAttendedEvents = undefined; | |
let oshi = []; | |
function initTools(done) { | |
loadIcs(function() { | |
loadOshi(done); | |
}); | |
} | |
function loadIcs(done) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
url: "/users/ical", | |
onload: function(response) { | |
let startPos = response.responseText.indexOf('https://www.eventernote.com/users/calendar'); | |
let urlStart = response.responseText.substr(startPos); | |
let endPos = urlStart.indexOf('"'); | |
let url = urlStart.substr(0, endPos); | |
GM_xmlhttpRequest({ | |
method: "GET", | |
url: url, | |
onload: function(response) { | |
myAttendedEvents = response.responseText.split("\BEGIN:VEVENT"); | |
done(); | |
} | |
}); | |
}, | |
}); | |
} | |
function loadOshi(done) { | |
GM_xmlhttpRequest({ | |
method: "GET", | |
url: "/users/", | |
onload: function(response) { | |
let startPos = response.responseText.indexOf('gb_actors_list') | |
if (startPos < 0) return done(); | |
let strip = response.responseText.substr(startPos); | |
let divPos = strip.indexOf('</div>'); | |
strip = strip.substr(0, divPos); | |
while (true) { | |
let pos = strip.indexOf('<a href="/actors/'); | |
if (pos < 0) break; | |
strip = strip.substr(pos); | |
pos = strip.indexOf('</a>'); | |
oshi.push(strip.substr(0, pos + 4)); | |
strip = strip.substr(pos + 4); | |
} | |
done(); | |
}, | |
}); | |
} | |
function dist(s1, s2) { | |
let f = []; | |
for(let i = 0; i <= s2.length; i++){ | |
f[i] = [i]; | |
if(i === 0) continue; | |
for(let j = 0; j <= s1.length; j++){ | |
f[0][j] = j; | |
if(j === 0) continue; | |
f[i][j] = s2[i-1] == s1[j-1] ? f[i - 1][j - 1] : Math.min( | |
f[i-1][j-1] + 1, | |
f[i][j-1] + 1, | |
f[i-1][j] + 1 | |
); | |
} | |
} | |
return f[s2.length][s1.length]; | |
} | |
function icsToEventName(idx, html) { | |
const st = html ? "\nDESCRIPTION:" : "\nSUMMARY:"; | |
const summaryPos = myAttendedEvents[idx].indexOf(st); | |
const summaryStr = myAttendedEvents[idx].substr(summaryPos + st.length); | |
const nlPos = summaryStr.indexOf("\n"); | |
return summaryStr.substr(0, nlPos).replace("<a href=", "<a style=\"color: DeepPink\" target=\"_blank\" href="); | |
} | |
function icsToDate(idx) { | |
const search = ["\nDTSTART;VALUE=DATE:", "\nDTSTART;TZID=Asia/Tokyo:"]; | |
for (let i = 0; i < search.length; i++) { | |
const searchStr = search[i]; | |
const pos = myAttendedEvents[idx].indexOf(searchStr); | |
if (pos >= 0) { | |
const sub = myAttendedEvents[idx].substr(pos + searchStr.length); | |
const yr = sub.substr(0, 4), mon = sub.substr(4, 2), d = sub.substr(6, 2); | |
return yr + "-" + mon + "-" + d; | |
} | |
} | |
return ""; | |
} | |
function icsToDateTime(idx) { | |
const search = ["\nDTSTART;VALUE=DATE:", "\nDTSTART;TZID=Asia/Tokyo:"]; | |
for (let i = 0; i < search.length; i++) { | |
const searchStr = search[i]; | |
const pos = myAttendedEvents[idx].indexOf(searchStr); | |
if (pos >= 0) { | |
const sub = myAttendedEvents[idx].substr(pos + searchStr.length); | |
const nl = sub.indexOf("\n"); | |
return sub.substr(0, nl); | |
} | |
} | |
return ""; | |
} | |
function doIAttend(eventId, yes, no) { | |
for (let i = 0; i < myAttendedEvents.length; i++) { | |
if (myAttendedEvents[i].indexOf("\nURL;VALUE=URI:https://www.eventernote.com/events/" + eventId + "\r") >= 0) { | |
yes(); | |
return; | |
} | |
} | |
no(); | |
} | |
function doIConflict(Y, M, D, skipEventId, allowSimilar, yes, no) { | |
const pad = function(num, size){ return ('000000000' + num).substr(-size); } | |
const eventName = function(idx) { | |
return icsToEventName(idx, true); | |
} | |
const ymd = pad(Y, 4) + pad(M, 2) + pad(D, 2); | |
let conflict = [] | |
let curEventIdx = -1; | |
for (let i = 0; i < myAttendedEvents.length; i++) { | |
if (myAttendedEvents[i].indexOf("\nURL;VALUE=URI:https://www.eventernote.com/events/" + skipEventId + "\r") >= 0) { | |
curEventIdx = i; | |
} | |
} | |
for (let i = 0; i < myAttendedEvents.length; i++) { | |
if (myAttendedEvents[i].indexOf("\nURL;VALUE=URI:https://www.eventernote.com/events/" + skipEventId + "\r") >= 0) { | |
continue; | |
} | |
if (myAttendedEvents[i].indexOf("\nDTSTART;VALUE=DATE:" + ymd) >= 0) { | |
if (allowSimilar && curEventIdx >= 0 && dist(icsToEventName(i, false), icsToEventName(curEventIdx, false)) <= 5) { | |
continue; | |
} | |
conflict.push(eventName(i)); | |
} | |
if (myAttendedEvents[i].indexOf("\nDTSTART;TZID=Asia/Tokyo:" + ymd + "T") >= 0) { | |
if (allowSimilar && curEventIdx >= 0 && dist(icsToEventName(i, false), icsToEventName(curEventIdx, false)) <= 5) { | |
continue; | |
} | |
conflict.push(eventName(i)); | |
} | |
} | |
conflict.sort(); | |
if (conflict.length > 0) yes(conflict); | |
else no(); | |
} | |
function exportItemToText(future_only) { | |
let events = [] | |
let now = new Date().toISOString(); | |
for (let idx = 0; idx < myAttendedEvents.length; idx++) { | |
if (icsToDate(idx) != "" && (!future_only || icsToDate(idx) >= now)) { | |
events.push(icsToDateTime(idx) + "\t" + icsToDate(idx) + "\t" + icsToEventName(idx, false)); | |
} | |
} | |
events.sort(); | |
for (let idx = 0; idx < events.length; idx++) { | |
events[idx] = events[idx].split("\t").slice(1).join("\t"); | |
} | |
const dataURL = 'data:text/plain,' + encodeURIComponent(events.join("\n")); | |
const pageHeight = Math.max( document.body.scrollHeight, document.body.offsetHeight, | |
document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); | |
document.body.innerHTML += '<div id="calData" style="width: 100%; height: ' + pageHeight + 'px; position: absolute; left: 0px; top: 0px; background-color: white;"></div>' | |
document.getElementById('calData').innerHTML = "<pre id=\"pressesc\">Escを押すと、Eventernoteに戻ります。了解を押すと、この部分が消えます。<input type=\"button\" value=\"了解\" onclick=\"document.getElementById('pressesc').style='display: none;';\"/></pre>" + | |
"<pre>" + events.join("\n") + "</pre>"; | |
document.onkeydown = function(evt) { | |
evt = evt || window.event; | |
let isEscape = false; | |
if ("key" in evt) { | |
isEscape = (evt.key == "Escape" || evt.key == "Esc"); | |
} else { | |
isEscape = (evt.keyCode == 27); | |
} | |
if (isEscape) { | |
document.onkeydown = undefined; | |
document.getElementById('calData').outerHTML = ""; | |
document.getElementById("exportToText").addEventListener("click", exportToText, false); | |
document.getElementById("exportAllToText").addEventListener("click", exportAllToText, false); | |
} | |
} | |
} | |
function exportToText() { | |
exportItemToText(true); | |
} | |
function exportAllToText() { | |
exportItemToText(false); | |
} | |
function markEventStatus(eventNode, eventId, Y, M, D) { | |
let colorNode = eventNode.getElementsByClassName('event')[0]; | |
let dataNode = eventNode.getElementsByClassName('event')[0]; | |
if (eventNode.parentNode.tagName == 'A') { | |
colorNode = eventNode.parentNode; | |
dataNode = eventNode.parentNode; | |
} | |
colorNode.style = 'background-color: LemonChiffon'; | |
doIAttend( | |
eventId, | |
function() { | |
colorNode.style = 'background-color: Aqua'; | |
doIConflict( | |
Y, M, D, eventId, true, | |
function(conflictEvents) { | |
colorNode.style = 'background-color: Orange'; | |
dataNode.innerHTML += ("<div>" + conflictEvents.join("<br/>") + "</div>"); | |
}, | |
function() {}); | |
}, | |
function() { | |
doIConflict( | |
Y, M, D, eventId, false, | |
function(conflictEvents) { | |
colorNode.style = 'background-color: LightPink'; | |
dataNode.innerHTML += ("<div>" + conflictEvents.join("<br/>") + "</div>"); | |
}, | |
function() { | |
colorNode.style = ''; | |
}); | |
} | |
); | |
} | |
function handleOshi(node) { | |
for (let oshiIdx = 0; oshiIdx < oshi.length; oshiIdx++) { | |
node.innerHTML = node.innerHTML.replace(oshi[oshiIdx], oshi[oshiIdx].replace('<a ', '<a style="font-size: 20px; color: red" ')); | |
} | |
} | |
(function() { | |
'use strict'; | |
initTools(function() { | |
if (document.getElementsByClassName('dropdown-menu').length > 0) { | |
document.getElementsByClassName('dropdown-menu')[0].innerHTML += '<li><a id="exportToText">これからの予定(文字のみ)</a></li>' | |
document.getElementsByClassName('dropdown-menu')[0].innerHTML += '<li><a id="exportAllToText">全てのイベント(文字のみ)</a></li>' | |
document.getElementById("exportToText").addEventListener("click", exportToText, false); | |
document.getElementById("exportAllToText").addEventListener("click", exportAllToText, false); | |
} | |
[].forEach.call(document.getElementsByClassName('gb_listview'), handleOshi); | |
let path = document.location.pathname; | |
let pathSplited = path.split('/'); | |
let lastPart = pathSplited[pathSplited.length - 1]; | |
if (pathSplited[pathSplited.length - 2] == 'events' && path.indexOf('/events/') == 0 && parseInt(lastPart).toString() == lastPart) { | |
let going = document.firstElementChild.innerHTML.indexOf('<p class="center note-edit-area">') >=0 || document.firstElementChild.innerHTML.indexOf('ノートを編集</a></p>') >= 0; | |
let eventDateNode = undefined; | |
let dataNode = undefined; | |
let mobile = false; | |
if (document.getElementsByClassName('gb_events_info_table')[0]) { | |
dataNode = document.getElementsByClassName('gb_events_info_table')[0].firstElementChild.firstElementChild; | |
eventDateNode = document.getElementsByClassName('gb_events_info_table')[0].firstElementChild.firstElementChild.firstElementChild.lastElementChild; | |
} else { | |
dataNode = document.getElementsByClassName('mod_page')[0].childNodes[7] | |
eventDateNode = document.getElementsByClassName('mod_page')[0].childNodes[7]; | |
mobile = true; | |
} | |
let eventDate = eventDateNode.innerText; | |
let dateArray = eventDate.split(' ')[0].split('-'); | |
let dateY = parseInt(dateArray[0]), dateM = parseInt(dateArray[1]), dateD = parseInt(dateArray[2]); | |
doIConflict( | |
dateY, dateM, dateD, lastPart, false, | |
function(conflictEvents) { | |
if (!going) { | |
eventDateNode.innerHTML += | |
"❌"; | |
} | |
dataNode.innerHTML = | |
(mobile ? "" : "<tr><td class=\"span2\">その日に" + (going ? "<strong>も</strong>" : "") + "参加するイベント</td><td>") + | |
conflictEvents.join("<br/>") + (mobile ? "<br/>" : "</td></tr>") + | |
dataNode.innerHTML; | |
}, | |
function() { | |
document.getElementsByClassName('gb_events_info_table')[0].firstElementChild.firstElementChild.firstElementChild.lastElementChild.innerHTML += | |
"✅"; | |
} | |
); | |
return; | |
} | |
let events = []; | |
let eventListDiv = document.getElementsByClassName('gb_event_list'); | |
for (let idx = 0; idx < eventListDiv.length; idx++) { | |
let clearFixBlock = eventListDiv[idx].getElementsByClassName('clearfix'); | |
for (let idx2 = 0; idx2 < clearFixBlock.length; idx2++) { | |
events.push(clearFixBlock[idx2]); | |
} | |
} | |
let mobileEventListDiv = document.getElementsByClassName('gb_listevent'); | |
for (let idx = 0; idx < mobileEventListDiv.length; idx++) { | |
let boxBlock = mobileEventListDiv[idx].getElementsByClassName('box'); | |
for (let idx2 = 0; idx2 < boxBlock.length; idx2++) { | |
events.push(boxBlock[idx2]); | |
} | |
} | |
for (let idx = 0; idx < events.length; idx++) { | |
let event = events[idx]; | |
handleOshi(event); | |
let date = event.getElementsByClassName('date')[0]; | |
let dateStr = date.firstElementChild.innerText; | |
let dateArray = dateStr.split(' ')[0].split('-'); | |
let dateY = parseInt(dateArray[0]), dateM = parseInt(dateArray[1]), dateD = parseInt(dateArray[2]); | |
let eventUrl = undefined; | |
if (event.getElementsByClassName('event')[0].firstElementChild.firstElementChild) | |
eventUrl = event.getElementsByClassName('event')[0].firstElementChild.firstElementChild.href; | |
else | |
eventUrl = event.parentNode.href; | |
let eventUrlSplit = eventUrl.split('/'); | |
let eventId = parseInt(eventUrlSplit[eventUrlSplit.length - 1]); | |
markEventStatus(event, eventId, dateY, dateM, dateD); | |
let noteCount = event.getElementsByClassName('note_count'); | |
if (noteCount.length > 0) { | |
let noteCountEle = noteCount[0]; | |
if (noteCountEle.firstElementChild.title == '参加者数') { | |
noteCountEle.addEventListener('click', function() { | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: eventUrl, | |
onload: function(response) { | |
let text = response.responseText; | |
let start = text.indexOf('このイベントに参加のフレンズ'); | |
let end = text.indexOf('このイベントに参加のイベンター'); | |
if (start == -1 || end == -1) return; | |
let con = text.substr(start, end - start + 1); | |
let people = []; | |
while (true) { | |
let start = con.indexOf('<p class="name pre">'); | |
if (start < 0) break; | |
con = con.substr(start); | |
let end = con.indexOf('</p>'); | |
people.push(con.substr('<p class="name pre">'.length, end - '<p class="name pre">'.length)); | |
con = con.substr(end + 1); | |
} | |
event.getElementsByClassName('event')[0].innerHTML += '<div>このイベントに参加のフレンズ<br/>' + people.join('\n') + '</div>'; | |
} | |
}); | |
}); | |
} | |
} | |
} | |
let actors = document.getElementsByClassName('gb_actors_list')[0].getElementsByTagName("li"); | |
for (let idx = 0; idx < actors.length; idx++) { | |
let style_str = actors[idx].getAttribute('class'); | |
if (!style_str) continue; | |
let styles = style_str.split(" "); | |
let cnt = undefined; | |
for (let tmp = 0; tmp < styles.length; tmp++) { | |
if (styles[tmp].match(/^c/)) cnt = parseInt(styles[tmp].substr(1)); | |
} | |
actors[idx].innerHTML += '<span style="font-size: 14px"> (' + cnt + ')</span>'; | |
} | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment