AI Chat Timestamper

Silently prepends a timestamp to every message you send on arena.ai, so the AI knows the current date and time without exposing your timezone.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         AI Chat Timestamper
// @namespace    https://greasyfork.org/
// @version      1.0
// @description  Silently prepends a timestamp to every message you send on arena.ai, so the AI knows the current date and time without exposing your timezone.
// @author       NoName
// @license      CC0-1.0
// @match        *://arena.ai/*
// @match        *://*.arena.ai/*
// @match        *://lmarena.ai/*
// @match        *://*.lmarena.ai/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    function buildTimestampParts() {
        const now = new Date();
        const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
        const pad = n => String(n).padStart(2, '0');

        const year = now.getFullYear();
        const month = pad(now.getMonth() + 1);
        const day = pad(now.getDate());
        const dow = days[now.getDay()];

        let hour = now.getHours();
        const min = pad(now.getMinutes());
        const ampm = hour >= 12 ? 'PM' : 'AM';

        hour = hour % 12;
        if (hour === 0) hour = 12;

        const plain = `${year}-${month}-${day} ${dow} ${hour}:${min} ${ampm}`;
        const full = `[${plain}] (Auto-generated)`;

        return { plain, full };
    }

    let toastEl = null;
    let toastTimer = null;

    function showToast(message) {
        function render() {
            if (!toastEl) {
                toastEl = document.createElement('div');
                toastEl.id = 'ai-ts-toast';
                toastEl.style.cssText = `
                    position: fixed;
                    left: 12px;
                    bottom: 12px;
                    z-index: 999999;
                    max-width: 82vw;
                    padding: 9px 12px;
                    border-left: 4px solid #22c55e;
                    border-radius: 10px;
                    background: #161616;
                    color: #f5f5f5;
                    font-size: 12px;
                    font-family: monospace;
                    line-height: 1.35;
                    word-break: break-word;
                    opacity: 0;
                    transform: translateY(6px);
                    transition: opacity 0.14s ease, transform 0.14s ease;
                    pointer-events: none;
                `;
                document.body.appendChild(toastEl);
            }

            toastEl.textContent = `🕐 ${message}`;
            toastEl.style.opacity = '1';
            toastEl.style.transform = 'translateY(0)';

            clearTimeout(toastTimer);
            toastTimer = setTimeout(() => {
                if (!toastEl) return;
                toastEl.style.opacity = '0';
                toastEl.style.transform = 'translateY(6px)';
            }, 2200);
        }

        if (document.body) {
            render();
        } else {
            document.addEventListener('DOMContentLoaded', render, { once: true });
        }
    }

    function alreadyStamped(text) {
        return /^\[\d{4}-\d{2}-\d{2}\s(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s.+?\]\s\(Auto-generated\)/.test(text);
    }

    function tryStampPayload(parsed, stampText) {
        let changed = false;

        // Known current shape
        if (
            parsed &&
            parsed.userMessage &&
            typeof parsed.userMessage === 'object' &&
            typeof parsed.userMessage.content === 'string' &&
            !alreadyStamped(parsed.userMessage.content)
        ) {
            parsed.userMessage.content = stampText + '\n\n' + parsed.userMessage.content;
            return true;
        }

        // Common alternatives
        const candidates = [
            ['message'],
            ['prompt'],
            ['content'],
            ['text'],
            ['input']
        ];

        for (const path of candidates) {
            let obj = parsed;
            for (let i = 0; i < path.length - 1; i++) {
                if (!obj || typeof obj !== 'object') {
                    obj = null;
                    break;
                }
                obj = obj[path[i]];
            }

            if (!obj || typeof obj !== 'object') continue;

            const key = path[path.length - 1];
            if (typeof obj[key] === 'string' && !alreadyStamped(obj[key])) {
                obj[key] = stampText + '\n\n' + obj[key];
                return true;
            }
        }

        // Search nested objects shallowly for "content" string fields
        function scan(obj, depth = 0) {
            if (!obj || typeof obj !== 'object' || depth > 4 || changed) return;

            if (
                typeof obj.content === 'string' &&
                obj.content.trim() &&
                !alreadyStamped(obj.content)
            ) {
                obj.content = stampText + '\n\n' + obj.content;
                changed = true;
                return;
            }

            for (const key in obj) {
                if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
                const value = obj[key];
                if (value && typeof value === 'object') {
                    scan(value, depth + 1);
                    if (changed) return;
                }
            }
        }

        scan(parsed);
        return changed;
    }

    const originalFetch = window.fetch;

    window.fetch = async function (...args) {
        let [resource, config] = args;

        const url = typeof resource === 'string' ? resource : resource.url;
        const method = (config && config.method) ? config.method.toUpperCase() : 'GET';

        if (
            method === 'POST' &&
            config &&
            config.body &&
            typeof config.body === 'string'
        ) {
            try {
                const parsed = JSON.parse(config.body);
                const ts = buildTimestampParts();

                const changed = tryStampPayload(parsed, ts.full);

                if (changed) {
                    config = {
                        ...config,
                        body: JSON.stringify(parsed)
                    };

                    showToast(`Timestamp: ${ts.plain}`);
                }
            } catch (e) {
                // Silently skip if parsing fails
            }
        }

        return originalFetch.apply(this, [resource, config]);
    };
})();