Last active
December 15, 2020 10:01
-
-
Save mohno007/b426a347237d2b5454e6467d9eb512a1 to your computer and use it in GitHub Desktop.
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 Search Siita with Google | |
// @namespace https://gist.github.com/mohno007/b426a347237d2b5454e6467d9eb512a1/ | |
// @version 0.1 | |
// @description Googleの検索結果にSiitaの検索結果も出してくれます | |
// @author You | |
// @match https://www.google.co.jp/search?* | |
// @match https://www.google.com/search?* | |
// @grant GM.xmlHttpRequest | |
// @connect siita.atware.co.jp | |
// @updateURL https://gist.github.com/mohno007/b426a347237d2b5454e6467d9eb512a1/raw/search_siita_with_google.user.js | |
// @downloadURL https://gist.github.com/mohno007/b426a347237d2b5454e6467d9eb512a1/raw/search_siita_with_google.user.js | |
// ==/UserScript== | |
{ | |
const main = async () => { | |
const sanitizeMap = { | |
'<': '<', | |
'>': '>', | |
'&': '&', | |
"'": ''', | |
'`': '`', | |
'"': '"', | |
}; | |
const html = (callSites, ...substitutions) => { | |
const escapedSubstitutions = substitutions.map(value => | |
value.toString().replace(/[<>&\\`'"]/g, match => sanitizeMap[match]) | |
); | |
const htmlString = String.raw(callSites, ...escapedSubstitutions); | |
const template = document.createElement('template'); | |
template.innerHTML = htmlString; | |
return template.content; | |
}; | |
// resource == Request not supported | |
const gmFetch = (resource, init = {}) => { | |
const url = new URL(resource.toString()); | |
const anonymous = ! ( init.credentials === 'include' || (init.credentials === 'same-origin' && window.location.origin === url.origin) ); | |
const details = { | |
url: url.toString(), | |
method: init.method ?? 'GET', | |
// init.headers, | |
// init.body, | |
// init.mode, | |
// init.credentials, | |
anonymous, | |
// init.cache, | |
// init.redirect, | |
// init.referrer, | |
// init.referrerPolicy, | |
// init.integrity, | |
// init.keepalive, | |
}; | |
return new Promise((resolve, reject) => { | |
const abortable = GM.xmlHttpRequest({ | |
...details, | |
responseType: 'blob', | |
onload(r) { | |
const res = new Response( | |
r.response, | |
{ | |
status: r.status, | |
statusText: r.statusText, | |
// headers: responseHeaders, | |
} | |
); | |
resolve(res); | |
}, | |
onerror(...err) { console.error(err); reject(new DOMException('', 'Error')); }, | |
ontimeout() { reject(new DOMException('', 'TimeoutError')); }, | |
onabort() { reject(new DOMException('', 'AbortError')); }, | |
}); | |
// Abortion | |
if (init.signal !== undefined) { | |
init.signal.addEventListener('abort', () => abortable.abort()); | |
} | |
}); | |
}; | |
class SearchResult { | |
constructor(url, title) { Object.assign(this, { title, url }); } | |
} | |
class SearchResults { | |
constructor(url, count, results) { Object.assign(this, { url, count, results }); } | |
} | |
const domParser = new DOMParser(); | |
const siita = async (queries) => { | |
const buildUrl = (query) => { | |
const url = new URL('https://siita.atware.co.jp/search'); | |
url.searchParams.set('q', query); | |
return url.toString(); | |
}; | |
const url = buildUrl(queries[0]); | |
const res = await gmFetch(url, { credentials: 'include' }); | |
if (!res.ok) { | |
return new SearchResults(0, []); | |
} | |
const body = await res.text(); | |
const doc = domParser.parseFromString(body, 'text/html'); | |
const count = parseInt(doc.querySelector('.container > div > p').textContent) || 0; | |
const items = [...doc.querySelectorAll('.container .feed-item h1 a')] | |
.map(link => new SearchResult('https://siita.atware.co.jp' + new URL(link.href).pathname, link.textContent)); | |
return new SearchResults(url, count, items); | |
}; | |
const searchQueries = new URL(window.location).searchParams.get('q').split(/\s+/); | |
const result = await siita(searchQueries); | |
if (result.count > 0) { | |
const resultView = html` | |
<div style="position: fixed; bottom: 0; right: 0; width: 30vw; max-height: 50vh; padding: 10px; border: 1px solid #222; overflow-y: scroll; background: white; color: black; z-index: 1;"> | |
<button class="close" style="position: absolute; top: 0; right: 0; background: black; color: white;">X</button> | |
<div style="font-weight: bold;">Siitaの検索結果: 「${searchQueries[0]}」(${result.count}件) <a href="${result.url}">➡</a></div> | |
<ul style="margin: 1em; padding: 0;"> | |
</ul> | |
</div> | |
`; | |
result.results.forEach(result => { | |
resultView.querySelector('ul').appendChild(html`<li><a href="${result.url}">${result.title}</a></li>`); | |
}); | |
resultView.querySelector('.close').addEventListener('click', () => resultView.remove()); | |
document.body.appendChild(resultView); | |
const handlePopState = () => { | |
window.removeEventListener('popstate', handlePopState); | |
resultView.remove(); | |
main(); | |
}; | |
window.addEventListener('popstate', handlePopState); | |
} | |
}; | |
main(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment