Skip to content

Instantly share code, notes, and snippets.

@mohno007
Last active December 15, 2020 10:01
Show Gist options
  • Save mohno007/b426a347237d2b5454e6467d9eb512a1 to your computer and use it in GitHub Desktop.
Save mohno007/b426a347237d2b5454e6467d9eb512a1 to your computer and use it in GitHub Desktop.
// ==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 = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
"'": '&#x27;',
'`': '&#x60;',
'"': '&quot;',
};
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