// ==UserScript==
// @name TOKEN取得
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 文字通り、Discordでログイン中のTOKENを取得します。
// @author Freeze
// @match https://discord.com/*
// @grant unsafeWindow
// @license You can modify as long as you credit me
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = 'authorizationExtractorSettings';
/**
* 設定を localStorage から読み込む
* 戻り値例:
* {
* top: '10px',
* left: '10px',
* width: '320px',
* height: '220px',
* showToken: false,
* isVisible: false
* }
*/
function loadSettings() {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
return JSON.parse(raw);
} catch (e) {
console.warn('Failed to load settings:', e);
return null;
}
}
/**
* 設定を localStorage に保存
*/
function saveSettings(settings) {
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
} catch (e) {
console.warn('Failed to save settings:', e);
}
}
/**
* トークンをマスク表示する関数
*/
function maskToken(token) {
return '*'.repeat(token.length);
}
/**
* localStorage からトークンを取得して返す
*/
function getToken() {
const storage = (typeof unsafeWindow !== 'undefined' && unsafeWindow.localStorage)
? unsafeWindow.localStorage
: window.localStorage;
let raw = null;
try {
raw = storage.getItem('token');
} catch (e) {
console.warn('Unable to read from storage:', e);
}
if (raw) {
return raw.slice(1, -1);
}
return null;
}
/**
* 要素をドラッグ可能にする関数
* ヘッダー部分を掴んでドラッグし、mouseup時に saveCallback() を呼ぶ
*/
function makeElementDraggable(el, saveCallback) {
el.addEventListener('mousedown', function(event) {
if (event.target.closest('.header-bar')) {
event.preventDefault();
let shiftX = event.clientX - el.getBoundingClientRect().left;
let shiftY = event.clientY - el.getBoundingClientRect().top;
function moveAt(pageX, pageY) {
const newLeft = pageX - shiftX;
const newTop = pageY - shiftY;
el.style.left = Math.min(Math.max(0, newLeft), window.innerWidth - el.offsetWidth) + 'px';
el.style.top = Math.min(Math.max(0, newTop), window.innerHeight - el.offsetHeight) + 'px';
}
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
document.addEventListener('mousemove', onMouseMove);
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
saveCallback();
}
document.addEventListener('mouseup', onMouseUp);
}
});
el.ondragstart = function() {
return false;
};
}
/**
* UI を作成し、container 要素を返す
*/
function createUI() {
const defaultWidth = '320px';
const defaultHeight = '220px';
const settings = loadSettings() || {};
// コンテナ
const container = document.createElement('div');
container.id = 'tokenContainer';
container.style.position = 'fixed';
container.style.top = settings.top || '10px';
container.style.left = settings.left || '10px';
container.style.width = settings.width || defaultWidth;
container.style.height = settings.height || defaultHeight;
container.style.backgroundColor= 'rgba(30, 30, 30, 0.95)';
container.style.color = '#ffffff';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.6)';
container.style.resize = 'both';
container.style.overflow = 'auto';
container.style.zIndex = '10000';
container.style.fontFamily = 'Arial, sans-serif';
// 初期表示は loadSettings().isVisible に従う。未定義なら非表示
container.style.display = settings.isVisible ? 'block' : 'none';
document.body.appendChild(container);
// ヘッダー(ドラッグ用バー)
const headerBar = document.createElement('div');
headerBar.classList.add('header-bar');
headerBar.style.width = '100%';
headerBar.style.height = '30px';
headerBar.style.cursor = 'move';
headerBar.style.backgroundColor = '#2c2f33';
headerBar.style.borderTopLeftRadius = '8px';
headerBar.style.borderTopRightRadius = '8px';
headerBar.style.display = 'flex';
headerBar.style.alignItems = 'center';
headerBar.style.justifyContent = 'space-between';
headerBar.style.padding = '0 10px';
headerBar.style.boxSizing = 'border-box';
container.appendChild(headerBar);
// タイトル
const title = document.createElement('span');
title.textContent = 'Freeze - TOKEN取得';
title.style.fontSize = '14px';
title.style.fontWeight = '600';
title.style.userSelect = 'none';
headerBar.appendChild(title);
// 閉じるボタン
const closeBtn = document.createElement('span');
closeBtn.textContent = '✕';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontSize = '14px';
closeBtn.style.padding = '2px 6px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.userSelect = 'none';
closeBtn.style.transition = 'background-color 0.2s';
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.backgroundColor = '#ff4d4d';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.backgroundColor = 'transparent';
});
closeBtn.addEventListener('click', () => {
container.style.display = 'none';
persistSettings();
});
headerBar.appendChild(closeBtn);
// 本文エリア
const bodyWrapper = document.createElement('div');
bodyWrapper.style.padding = '12px';
bodyWrapper.style.boxSizing = 'border-box';
container.appendChild(bodyWrapper);
// トークン表示
const tokenDisplay = document.createElement('code');
tokenDisplay.style.whiteSpace = 'pre-wrap';
tokenDisplay.style.wordBreak = 'break-word';
tokenDisplay.style.display = 'block';
tokenDisplay.style.marginBottom = '12px';
tokenDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
tokenDisplay.style.color = '#00FF00';
tokenDisplay.style.padding = '10px';
tokenDisplay.style.borderRadius = '4px';
tokenDisplay.style.fontSize = '13px';
tokenDisplay.textContent = 'Failed acquisition. Press “Refresh” to try again.';
bodyWrapper.appendChild(tokenDisplay);
// ボタンエリア
const buttonWrapper = document.createElement('div');
buttonWrapper.style.display = 'flex';
buttonWrapper.style.justifyContent = 'space-between';
buttonWrapper.style.alignItems = 'center';
buttonWrapper.style.gap = '10px';
bodyWrapper.appendChild(buttonWrapper);
// 左ボタン群
const leftButtons = document.createElement('div');
leftButtons.style.display = 'flex';
leftButtons.style.gap = '8px';
buttonWrapper.appendChild(leftButtons);
// Copy ボタン
const copyButton = document.createElement('button');
copyButton.textContent = 'Copy';
copyButton.style.padding = '6px 14px';
copyButton.style.backgroundColor = '#7289da';
copyButton.style.color = '#ffffff';
copyButton.style.border = 'none';
copyButton.style.borderRadius = '4px';
copyButton.style.cursor = 'pointer';
copyButton.style.transition = 'background-color 0.2s, transform 0.1s';
copyButton.addEventListener('mouseenter', () => {
copyButton.style.backgroundColor = '#667bc4';
});
copyButton.addEventListener('mouseleave', () => {
copyButton.style.backgroundColor = '#7289da';
});
copyButton.addEventListener('mousedown', () => {
copyButton.style.transform = 'scale(0.95)';
});
copyButton.addEventListener('mouseup', () => {
copyButton.style.transform = 'scale(1)';
});
leftButtons.appendChild(copyButton);
copyButton.addEventListener('click', function() {
const dummy = document.createElement('textarea');
document.body.appendChild(dummy);
dummy.value = tokenDisplay.getAttribute('data-token') || '';
dummy.select();
document.execCommand('copy');
document.body.removeChild(dummy);
copyButton.textContent = 'Copied!';
setTimeout(() => { copyButton.textContent = 'Copy'; }, 1000);
});
// Refresh ボタン
const refreshButton = document.createElement('button');
refreshButton.textContent = 'Refresh';
refreshButton.style.padding = '6px 14px';
refreshButton.style.backgroundColor = '#ffa500';
refreshButton.style.color = '#ffffff';
refreshButton.style.border = 'none';
refreshButton.style.borderRadius = '4px';
refreshButton.style.cursor = 'pointer';
refreshButton.style.transition = 'background-color 0.2s, transform 0.1s';
refreshButton.addEventListener('mouseenter', () => {
refreshButton.style.backgroundColor = '#e59400';
});
refreshButton.addEventListener('mouseleave', () => {
refreshButton.style.backgroundColor = '#ffa500';
});
refreshButton.addEventListener('mousedown', () => {
refreshButton.style.transform = 'scale(0.95)';
});
refreshButton.addEventListener('mouseup', () => {
refreshButton.style.transform = 'scale(1)';
});
leftButtons.appendChild(refreshButton);
// Show/Hide ボタン
const toggleButton = document.createElement('button');
toggleButton.textContent = settings.showToken ? 'Hide' : 'Show';
toggleButton.style.padding = '6px 14px';
toggleButton.style.backgroundColor = settings.showToken ? '#f04747' : '#43b581';
toggleButton.style.color = '#ffffff';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '4px';
toggleButton.style.cursor = 'pointer';
toggleButton.style.transition = 'opacity 0.2s';
toggleButton.addEventListener('mouseenter', () => {
toggleButton.style.opacity = '0.8';
});
toggleButton.addEventListener('mouseleave', () => {
toggleButton.style.opacity = '1';
});
buttonWrapper.appendChild(toggleButton);
/**
* 設定を保存する
*/
function persistSettings() {
const current = {
top: container.style.top,
left: container.style.left,
width: container.style.width,
height: container.style.height,
showToken: toggleButton.textContent === 'Hide',
isVisible: container.style.display === 'block'
};
saveSettings(current);
}
// 初回表示:保存された showToken に従いトークンを表示
const initialRaw = getToken();
if (initialRaw) {
tokenDisplay.setAttribute('data-token', initialRaw);
if (settings.showToken) {
tokenDisplay.textContent = initialRaw;
} else {
tokenDisplay.textContent = maskToken(initialRaw);
}
}
// Refresh ボタン押下時
refreshButton.addEventListener('click', () => {
refreshButton.disabled = true;
refreshButton.textContent = 'Refreshing…';
setTimeout(() => {
const raw = getToken();
if (raw) {
tokenDisplay.setAttribute('data-token', raw);
if (toggleButton.textContent === 'Hide') {
tokenDisplay.textContent = raw;
} else {
tokenDisplay.textContent = maskToken(raw);
}
} else {
tokenDisplay.textContent = 'Failed acquisition. Press “Refresh”.';
tokenDisplay.removeAttribute('data-token');
}
refreshButton.textContent = 'Refresh';
refreshButton.disabled = false;
}, 100);
});
// Show/Hide トグル押下時
toggleButton.addEventListener('click', () => {
const raw = tokenDisplay.getAttribute('data-token');
if (!raw) return;
if (toggleButton.textContent === 'Show') {
tokenDisplay.textContent = raw;
toggleButton.textContent = 'Hide';
toggleButton.style.backgroundColor = '#f04747';
} else {
tokenDisplay.textContent = maskToken(raw);
toggleButton.textContent = 'Show';
toggleButton.style.backgroundColor = '#43b581';
}
persistSettings();
});
// ドラッグ可能化(ヘッダーのみ)
makeElementDraggable(container, persistSettings);
// リサイズ終了後に設定保存
container.addEventListener('mouseup', () => {
persistSettings();
});
// container に保存用メソッドをアタッチ(外部から呼べるように)
container.persistSettings = persistSettings;
return { container };
}
/**
* 「TOKEN取得」ボタンを作成し、クリックで UI の表示・非表示を切り替える
*/
function createFetchButton(container) {
const fetchBtn = document.createElement('button');
fetchBtn.textContent = 'TOKEN取得';
fetchBtn.style.position = 'fixed';
fetchBtn.style.bottom = '20px';
fetchBtn.style.right = '20px';
fetchBtn.style.padding = '10px 20px';
fetchBtn.style.backgroundColor= '#5865f2';
fetchBtn.style.color = '#ffffff';
fetchBtn.style.border = 'none';
fetchBtn.style.borderRadius = '6px';
fetchBtn.style.cursor = 'pointer';
fetchBtn.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
fetchBtn.style.fontSize = '14px';
fetchBtn.style.fontWeight = '600';
fetchBtn.style.zIndex = '10001';
fetchBtn.style.transition = 'background-color 0.2s, transform 0.1s';
fetchBtn.addEventListener('mouseenter', () => {
fetchBtn.style.backgroundColor = '#4953c8';
});
fetchBtn.addEventListener('mouseleave', () => {
fetchBtn.style.backgroundColor = '#5865f2';
fetchBtn.style.transform = 'scale(1)';
});
fetchBtn.addEventListener('mousedown', () => {
fetchBtn.style.transform = 'scale(0.95)';
});
fetchBtn.addEventListener('mouseup', () => {
fetchBtn.style.transform = 'scale(1)';
});
document.body.appendChild(fetchBtn);
fetchBtn.addEventListener('click', () => {
const isHidden = container.style.display === 'none';
container.style.display = isHidden ? 'block' : 'none';
// 表示状態が変わったので保存
container.persistSettings();
});
}
// ────────────────────────────────────────────────────
// スクリプト本体
// ────────────────────────────────────────────────────
// UI 作成
const { container } = createUI();
// 「TOKEN取得」ボタン作成
createFetchButton(container);
// 保存設定があれば表示状態を復元(初期 loadSettings() 内で適用済)
// 特に何もしなくてOK
})();