Skip to content

Instantly share code, notes, and snippets.

@syusui-s
Last active April 21, 2023 02:55
Show Gist options
  • Save syusui-s/3592392294cb8408915a to your computer and use it in GitHub Desktop.
Save syusui-s/3592392294cb8408915a to your computer and use it in GitHub Desktop.
ブックマークレット集

Bookmarklet

これまでちまちま作ってきたブックマークレット(Bookmarklet)を公開します。地味に便利系が多いです。 これらのブックマークレットは全てCC0でライセンスで提供されます。

もはやブックマークレットじゃなくて、Webアプリみたいなものですが、艦これ専用のタイマーアプリです。

TweetDeckやカレンダー、ブラウザゲーム(例えば艦これ)など、ページの遷移が起こってほしくない、常駐させておきたいページで実行しておくと、遷移するときに確認画面を出してくれるスクリプトです。強制的に防止することはできません、あくまでも緩和策です。

ES6以上必須。検索結果で探すときに使えます。「〇〇 000users入り」とかのがいいかも。 取りこぼしがないと言う点では強い。

ES6以上必須。めっちゃ長いです。

画像や動画の貼ってあるツイートページでこのブックマークレットを使うと、
ユーザ名_ツイートID_画像ファイル名.拡張子の名前でメディアを保存します。 複数画像にも対応しています。

ES6以上必須。リンクをScrapbox形式でコピーします。

javascript: (async () => {
const style = {
display: 'block',
margin: 0,
padding: 0,
boxSizing: 'content-box',
lineHeight: '28px',
fontSize: '16px',
fontFamily: 'sans-serif',
fontWeight: 'bold',
textAlign: 'center',
};
const showPopup = text => {
const elem = document.createElement('div');
Object.assign(elem.style, {
...style,
background: '#333',
border: '2px solid #fff',
borderTop: 'none',
borderRadius: '0 0 5px 5px',
zIndex: 2 ** 31 - 1,
position: 'fixed',
top: 0,
left: '50%',
right: '50%',
height: '28px',
width: '100px',
fontSize: '18px',
color: '#fff',
});
elem.textContent = text;
document.body.appendChild(elem);
setTimeout(() => elem.remove(), 500);
};
const shouldUseClipboard = async () => {
if (!navigator.permissions) {
return false;
}
return await [() => navigator.permissions.query({
name: 'clipboard-write'
}), () => navigator.permissions.query({
name: 'clipboard'
})].reduce((promise, fallback) => promise.catch(fallback), Promise.reject()).then(permissionStatus => permissionStatus.state === 'granted').catch(e => {
if (!(e instanceof TypeError)) throw e;
return false;
});
};
const clipboardCopy = async (text) => {
if (await shouldUseClipboard()) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (e) {
console.error("Failed to copy: ", e);
}
}
};
const execCommandCopy = (text) => {
if (!document.execCommand) {
return false;
}
const copyButton = document.createElement('button');
copyButton.type = 'button';
Object.assign(copyButton.style, {
...style,
background: '#fff',
border: '2px solid #222',
borderRadius: '5px',
position: 'fixed',
zIndex: 2 ** 31 - 1,
top: 0,
left: '50vw',
height: '28px',
width: '150px',
fontSize: '18px',
color: '#222',
outline: 'none',
animation: 'none',
transition: 'none',
});
copyButton.textContent = 'Click to Copy';
const handleMouseMove = (ev) => {
copyButton.style.top = `${ev.clientY - 14}px`;
copyButton.style.left = `${ev.clientX - 75}px`;
};
document.addEventListener('mousemove', handleMouseMove);
const saveRanges = () => {
if (!(window.getSelection && Selection)) return [];
const s = window.getSelection();
const ranges = [];
for (let i = 0; i < s.rangeCount; i++) {
ranges.push(s.getRangeAt(i));
}
return ranges;
};
const restoreRange = (savedRanges) => {
if (!(window.getSelection && Selection)) return;
const s = window.getSelection();
s.removeAllRanges();
savedRanges.forEach((r) => s.addRange(r));
};
const cleanup = () => {
document.removeEventListener('mousemove', handleMouseMove);
copyButton.remove();
};
const execCopy = () => {
const ranges = saveRanges();
const input = document.createElement('input');
input.value = text;
document.body.appendChild(input);
input.select();
try {
document.execCommand('copy');
return true;
} catch (e) {
console.error(e);
return false;
} finally {
cleanup();
input.remove();
restoreRange(ranges);
}
};
return new Promise((resolve) => {
copyButton.addEventListener('keydown', (ev) => {
ev.stopPropagation();
ev.preventDefault();
if (ev.key === 'Enter' || ev.key === 'Space') {
resolve(execCopy());
}
});
copyButton.addEventListener('click', (ev) => {
ev.stopPropagation();
ev.preventDefault();
resolve(execCopy());
}, false);
copyButton.addEventListener('contextmenu', (ev) => {
ev.preventDefault();
ev.stopPropagation();
cleanup();
resolve(true);
}, false);
document.body.appendChild(copyButton);
copyButton.focus();
});
};
const windowPromptCopy = (text) => {
if (!window.prompt) {
return false;
}
window.prompt('copy it!', text);
return true;
};
const pad02 = x => x.toString().padStart(2, "0");
const videoElement = document.body.querySelector('video');
const x = videoElement?.getCurrentTime?.() || videoElement?.currentTime;
if (x == undefined) {
window.alert('video not found or failed to get current time');
}
const h = Math.floor(x / 3600);
const m = Math.floor(x % 3600 / 60);
const s = Math.floor(x % 60);
const hs = h > 0 ? `${h}:` : "";
const ms = h > 0 ? pad02(m) : m;
const ss = pad02(s);
const text = `${hs}${ms}:${ss}`;
const copySucceed = await [clipboardCopy, execCommandCopy, windowPromptCopy].reduce((prev, curr) => prev.then(p => p || curr(text)), Promise.resolve());
let result = copySucceed ? 'Copied!' : 'Failed to copy';
showPopup(result);
undefined;
})();
javascript:window.addEventListener('beforeunload', function(e){
e.returnValue='%E9%81%B7%E7%A7%BB%E9%98%B2%E6%AD%A2%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%88%E3%82%8A%E3%80%81%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E9%81%B7%E7%A7%BB%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%97%E3%81%BE%E3%81%99%E3%80%82';
});
javascript:(function(){
const ar = document.querySelectorAll('li.image-item');
const n = window.prompt('指定数', '100');
Array.prototype.forEach.call(ar, e => {
const a = e.querySelector('a.bookmark-count');
if(+a.textContent >= +n) e.style.backgroundColor = '#FF0';
}); })();
javascript: {
const obtainTitle = () => {
const documentTitle = document.title;
if (documentTitle.length > 0)
return documentTitle;
const h1 = document.querySelector('h1');
if (h1 && h1.textContent.length > 0)
return h1.textContent;
const h2 = document.querySelector('h2');
if (h2 && h2.textContent.length > 0)
return h2.textContent;
return "";
};
const title = obtainTitle();
const url = location.href;
const text = `${title} ${url}`;
const tweetUrl = new URL('https://twitter.com/intent/tweet');
tweetUrl.searchParams.append('text', title);
tweetUrl.searchParams.append('url', url);
window.open(tweetUrl, 'twitter', 'height=400,width=300');
undefined;
}
javascript: (async function() {
'use strict';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const download = (url, filename) => new Promise((resolve, reject) => {
const link = document.createElement('a');
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.overrideMimeType('octet/stream');
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
link.href = URL.createObjectURL(xhr.response);
link.download = filename;
link.click();
resolve();
} else {
reject(`Failed to Download "${filename}". Status code was not 200.`);
}
});
xhr.addEventListener('error', reject);
xhr.send();
});
const highlight = img => {
const rect = img.parentNode.parentNode.getClientRects()[0];
const cover = document.createElement('div');
cover.classList.add('highlight-image');
Object.assign(cover.style, {
display: 'block',
position: 'absolute',
zIndex: '999999',
top: `${rect.top + window.scrollY}px`,
left: `${rect.left}px`,
height: `${rect.height}px`,
width: `${rect.width}px`,
boxSizing: 'border-box',
border: '10px solid #1DA1F2',
});
document.body.appendChild(cover);
cover.scrollIntoView(false);
};
const unhighlight = () => {
const covers = document.querySelectorAll('.highlight-image');
Array.prototype.forEach.call(covers, cover => cover.remove());
};
async function askToDonload(img) {
highlight(img);
await sleep(100);
const result = confirm('Download this image?');
unhighlight(img);
return result;
}
function reorderImages(images) {
return images;
}
async function exec() {
const tweetMatch = location.href.match(/\/([^/]+)\/status\/([0-9]+)/);
if (!tweetMatch) {
throw 'Invalid twitter URL';
}
const [_1, screenName, tweetId] = tweetMatch;
const selector = `a[href*="status/${tweetId}/photo/"] img[src^="https://pbs.twimg.com/media/"]`;
console.log(selector);
const images = reorderImages([...document.body.querySelectorAll(selector)]);
if (!(images && images.length > 0)) {
alert('This page has no media content!!');
return;
}
const filteredImages = [];
if (images.length === 1) {
filteredImages.push(images[0]);
} else {
for (const img of images) {
const shouldDownload = await askToDonload(img);
if (shouldDownload) {
filteredImages.push(img);
}
}
}
if (filteredImages.length === 0)
return;
const tasks = filteredImages.map(async (img, i) => {
const imgurl = new URL(img.src);
imgurl.searchParams.set('name', 'orig');
const ext = imgurl.searchParams.get('format');
const mediaIdMatch = imgurl.pathname.match(/media\/([\w-]+)/);
if (!(mediaIdMatch && ext)) {
alert('Failed to download. URL does not match to a format.');
return;
}
const index = images.length > 1 ? `_${i+1}` : '';
const filename = `${screenName}_${tweetId}${index}_${mediaIdMatch[1]}.${ext}`;
await download(imgurl, filename);
});
await Promise.all(tasks);
}
try {
await exec();
} catch (e) {
alert(e);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment