// ==UserScript==
// @name Twitter/X 搜索日期快捷按钮
// @namespace http://tampermonkey.net/
// @version 2025-06-25
// @description 在 Twitter/X 搜索框下方添加日期筛选快捷按钮
// @author Kai([email protected])
// @license MIT
// @match https://x.com/explore
// @match https://x.com/search*
// @icon https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 调试开关
const DEBUG = false;
const log = (...args) => DEBUG && console.log(...args);
log('[日期快捷按钮] 脚本已加载');
// 获取格式化日期
function getFormattedDate(daysAgo = 0) {
const date = new Date();
date.setDate(date.getDate() - daysAgo);
return date.toISOString().split('T')[0];
}
// 获取上周的起止日期
function getLastWeekDates() {
const today = new Date();
const dayOfWeek = today.getDay();
const lastSunday = new Date(today);
lastSunday.setDate(today.getDate() - dayOfWeek - 7);
const lastSaturday = new Date(lastSunday);
lastSaturday.setDate(lastSunday.getDate() + 6);
return {
since: lastSunday.toISOString().split('T')[0],
until: lastSaturday.toISOString().split('T')[0]
};
}
// 获取上个月的起止日期
function getLastMonthDates() {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1);
const lastDay = new Date(today.getFullYear(), today.getMonth(), 0);
return {
since: firstDay.toISOString().split('T')[0],
until: lastDay.toISOString().split('T')[0]
};
}
// 获取去年的起止日期
function getLastYearDates() {
const today = new Date();
const firstDay = new Date(today.getFullYear() - 1, 0, 1);
const lastDay = new Date(today.getFullYear() - 1, 11, 31);
return {
since: firstDay.toISOString().split('T')[0],
until: lastDay.toISOString().split('T')[0]
};
}
// 创建快捷按钮容器
function createShortcutButtons() {
log('[日期快捷按钮] 创建按钮容器');
const container = document.createElement('div');
container.style.cssText = `
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid rgb(239, 243, 244);
background: white;
`;
container.id = 'date-shortcuts-container';
// 按钮配置
const buttons = [
{ text: '今天', days: 0 },
{ text: '7天内', days: 7 },
{ text: '30天内', days: 30 },
{ text: '上周', type: 'lastWeek' },
{ text: '上个月', type: 'lastMonth' },
{ text: '去年', type: 'lastYear' }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.textContent = btn.text;
button.style.cssText = `
padding: 6px 12px;
border-radius: 9999px;
border: 1px solid rgb(207, 217, 222);
background: white;
color: rgb(15, 20, 25);
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
`;
// 悬停效果
button.onmouseover = () => {
button.style.background = 'rgb(247, 249, 249)';
};
button.onmouseout = () => {
button.style.background = 'white';
};
// 点击事件
button.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const searchInput = document.querySelector('[data-testid="SearchBox_Search_Input"]');
if (!searchInput) {
log('[日期快捷按钮] 未找到搜索输入框');
return;
}
const currentValue = searchInput.value.trim();
log(`[日期快捷按钮] 输入框当前值: "${currentValue}"`);
// 移除已有的 since: 和 until: 参数
let cleanedValue = currentValue.replace(/\s*since:\S+/g, '').replace(/\s*until:\S+/g, '').trim();
let newQuery;
if (btn.days !== undefined) {
// 原有的按钮逻辑(只有 since)
const dateStr = getFormattedDate(btn.days);
newQuery = cleanedValue + ` since:${dateStr}`;
} else if (btn.type) {
// 新按钮逻辑(有 since 和 until)
let dates;
switch (btn.type) {
case 'lastWeek':
dates = getLastWeekDates();
break;
case 'lastMonth':
dates = getLastMonthDates();
break;
case 'lastYear':
dates = getLastYearDates();
break;
}
newQuery = cleanedValue + ` since:${dates.since} until:${dates.until}`;
}
// 更新 URL 参数
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('q', newQuery.trim());
// 构建新 URL
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
log(`[日期快捷按钮] 导航到: ${newUrl}`);
// 导航到新 URL
window.location.href = newUrl;
};
container.appendChild(button);
});
return container;
}
// 插入快捷按钮到 dropdown
function insertShortcuts() {
// 查找 dropdown
const dropdown = document.querySelector('[id^="typeaheadDropdown"]');
if (!dropdown) {
log('[日期快捷按钮] 未找到 dropdown');
return;
}
// 检查是否已插入
if (dropdown.querySelector('#date-shortcuts-container')) {
log('[日期快捷按钮] 快捷按钮已存在');
return;
}
log('[日期快捷按钮] 找到 dropdown,准备插入按钮');
// 创建并插入按钮容器到顶部
const shortcuts = createShortcutButtons();
dropdown.prepend(shortcuts);
log('[日期快捷按钮] 按钮插入成功');
}
// 监听搜索框点击
function setupSearchBoxListener() {
const searchInput = document.querySelector('[data-testid="SearchBox_Search_Input"]');
if (!searchInput) {
log('[日期快捷按钮] 未找到搜索框,稍后重试');
setTimeout(setupSearchBoxListener, 1000);
return;
}
log('[日期快捷按钮] 找到搜索框,添加监听器');
// 移除旧的监听器(如果存在)
searchInput.removeEventListener('click', handleSearchClick);
searchInput.removeEventListener('focus', handleSearchClick);
searchInput.removeEventListener('keydown', handleSearchSubmit);
// 添加新的监听器
searchInput.addEventListener('click', handleSearchClick);
searchInput.addEventListener('focus', handleSearchClick);
// 监听回车键,确保 URL 参数与输入框内容同步
searchInput.addEventListener('keydown', handleSearchSubmit);
// 监听 DOM 变化,以便在 dropdown 出现时插入按钮
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.id && node.id.includes('typeaheadDropdown')) {
log('[日期快捷按钮] 检测到 dropdown 创建');
setTimeout(insertShortcuts, 50);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 处理搜索框点击事件
function handleSearchClick() {
log('[日期快捷按钮] 搜索框被点击');
// 延迟执行以等待 dropdown 创建
setTimeout(insertShortcuts, 100);
}
// 处理搜索框回车提交
function handleSearchSubmit(e) {
if (e.key === 'Enter') {
log('[日期快捷按钮] 检测到回车键');
const searchInput = e.target;
const currentValue = searchInput.value.trim();
// 如果输入框为空,导航到 explore 页面
if (!currentValue) {
e.preventDefault();
window.location.href = '/explore';
return;
}
// 阻止默认行为,手动处理导航
e.preventDefault();
// 更新 URL 参数
const urlParams = new URLSearchParams();
urlParams.set('q', currentValue);
urlParams.set('src', 'typed_query');
// 构建新 URL
const newUrl = `/search?${urlParams.toString()}`;
log(`[日期快捷按钮] 手动导航到: ${newUrl}`);
// 导航到新 URL
window.location.href = newUrl;
}
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupSearchBoxListener);
} else {
setupSearchBoxListener();
}
// 处理页面导航(Twitter 是 SPA)
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
log('[日期快捷按钮] 页面导航,重新初始化');
setTimeout(setupSearchBoxListener, 500);
}
}).observe(document, { subtree: true, childList: true });
})();