您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tag and save Snapchat chats seamlessly using localStorage.
- // ==UserScript==
- // @name SnapTagger (Improved)
- // @namespace http://tampermonkey.net/
- // @version 0.3 // Increment version for the change
- // @description Tag and save Snapchat chats seamlessly using localStorage.
- // @author You & Gemini
- // @license MIT // Added MIT License
- // @match https://web.snapchat.com/*
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_deleteValue // Optional: For clearing data if needed
- // ==/UserScript==
- (function () {
- 'use strict';
- // --- Constants ---
- const STORAGE_KEY = 'snapTaggerData'; // Key for localStorage
- // IMPORTANT: These selectors might change if Snapchat updates their website.
- // You may need to inspect the elements on web.snapchat.com and update these.
- const SNAP_ICON_SELECTOR = 'svg[aria-label="Snapchat"]'; // Try targeting the SVG element more specifically
- const CHAT_HEADER_SELECTOR = '[data-testid="conversation-header-title"]'; // Selector for the chat header element containing the friend's name
- // --- State ---
- let uiInjected = false;
- // --- Styling ---
- // Use GM_addStyle for CSS to keep it separate and potentially more maintainable
- GM_addStyle(`
- .snaptagger-container {
- position: relative;
- cursor: pointer;
- display: inline-block; /* Adjust display as needed */
- vertical-align: middle; /* Align with other header items */
- }
- .snaptagger-menu {
- position: absolute;
- top: 100%; /* Position below the icon */
- right: 0;
- background-color: #2f2f2f; /* Slightly lighter dark grey */
- border: 1px solid #555; /* Softer border */
- border-radius: 8px; /* Match Snapchat's rounding */
- padding: 12px;
- z-index: 10000; /* Ensure it's on top */
- display: none; /* Hidden by default */
- color: #ffffff;
- font-size: 14px;
- min-width: 180px; /* Give it some width */
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* Add subtle shadow */
- font-family: "Inter", sans-serif; /* Try to match font */
- }
- .snaptagger-menu.visible {
- display: block;
- }
- .snaptagger-title {
- font-weight: 600;
- margin-bottom: 10px;
- color: #eee; /* Lighter title color */
- border-bottom: 1px solid #444;
- padding-bottom: 6px;
- }
- .snaptagger-button {
- display: block;
- width: 100%;
- background-color: #4a4a4a; /* Button background */
- color: #ffffff;
- border: none;
- padding: 8px 12px;
- margin-bottom: 6px;
- border-radius: 6px;
- cursor: pointer;
- text-align: left;
- font-size: 13px;
- transition: background-color 0.2s ease;
- }
- .snaptagger-button:hover {
- background-color: #5c5c5c; /* Slightly lighter on hover */
- }
- .snaptagger-button:last-child {
- margin-bottom: 0;
- }
- .snaptagger-feedback {
- font-size: 12px;
- color: #999;
- margin-top: 8px;
- text-align: center;
- min-height: 15px; /* Reserve space */
- }
- `);
- // --- Utility Functions ---
- /**
- * Gets the currently tagged data from storage.
- * @returns {Array<Object>} An array of tag objects or an empty array.
- */
- function getStoredTags() {
- // Use GM_getValue for Tampermonkey/Greasemonkey storage (more robust than raw localStorage)
- return GM_getValue(STORAGE_KEY, []); // Default to empty array if nothing stored
- }
- /**
- * Saves the tagged data to storage.
- * @param {Array<Object>} tags - The array of tag objects to save.
- */
- function saveTags(tags) {
- GM_setValue(STORAGE_KEY, tags);
- }
- /**
- * Gets the name of the currently open chat.
- * @returns {string | null} The chat name or null if not found.
- */
- function getCurrentChatName() {
- // This selector might need adjustment based on Snapchat's current structure
- const headerElement = document.querySelector(CHAT_HEADER_SELECTOR);
- return headerElement ? headerElement.textContent.trim() : null;
- }
- /**
- * Shows temporary feedback message in the menu.
- * @param {string} message - The message to display.
- */
- function showFeedback(message) {
- const feedbackEl = document.getElementById('snaptagger-feedback');
- if (feedbackEl) {
- feedbackEl.textContent = message;
- setTimeout(() => {
- if (feedbackEl.textContent === message) { // Only clear if it's the same message
- feedbackEl.textContent = '';
- }
- }, 2500); // Clear after 2.5 seconds
- }
- }
- // --- Core Logic ---
- /**
- * Tags the currently open chat conversation.
- */
- function tagCurrentChat() {
- const chatName = getCurrentChatName();
- if (!chatName) {
- showFeedback("Error: Couldn't find chat name.");
- console.error("SnapTagger: Could not find chat header element with selector:", CHAT_HEADER_SELECTOR);
- return;
- }
- const tag = prompt(`Enter a tag for the chat with "${chatName}":`);
- if (tag === null || tag.trim() === '') {
- showFeedback("Tagging cancelled.");
- return; // User cancelled or entered empty tag
- }
- const newTagEntry = {
- chatName: chatName,
- tag: tag.trim(),
- timestamp: new Date().toISOString(),
- // Future: Add specific messages here if needed
- // messages: getSelectedMessages() // Placeholder for more advanced functionality
- };
- try {
- const currentTags = getStoredTags();
- currentTags.push(newTagEntry);
- saveTags(currentTags);
- console.log("SnapTagger: Chat tagged:", newTagEntry);
- showFeedback(`Tagged "${chatName}" as "${tag.trim()}"`);
- } catch (error) {
- console.error("SnapTagger: Error saving tag:", error);
- showFeedback("Error saving tag.");
- alert("SnapTagger Error: Could not save tag to storage. Storage might be full or disabled.\n\n" + error);
- }
- }
- /**
- * Exports the stored tags as a JSON file.
- */
- function exportTaggedChats() {
- try {
- const tags = getStoredTags();
- if (tags.length === 0) {
- showFeedback("No tags to export.");
- return;
- }
- const jsonData = JSON.stringify(tags, null, 2); // Pretty print JSON
- const blob = new Blob([jsonData], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
- a.download = `snaptagger_export_${timestamp}.json`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url); // Clean up
- showFeedback("Tags exported successfully!");
- console.log("SnapTagger: Exported tags.", tags);
- } catch (error) {
- console.error("SnapTagger: Error exporting tags:", error);
- showFeedback("Error during export.");
- alert("SnapTagger Error: Could not export tags.\n\n" + error);
- }
- }
- /**
- * Injects the SnapTagger UI elements into the page.
- * @param {Element} iconElement - The Snapchat icon element to replace/wrap.
- */
- function injectUI(iconElement) {
- if (uiInjected) return; // Prevent multiple injections
- // --- Create Container ---
- const container = document.createElement('div');
- container.className = 'snaptagger-container';
- // --- Clone Icon ---
- // Cloning helps maintain the original icon's appearance and any associated listeners
- const clonedIcon = iconElement.cloneNode(true);
- container.appendChild(clonedIcon);
- // --- Create Dropdown Menu ---
- const menu = document.createElement('div');
- menu.className = 'snaptagger-menu'; // Use class for styling
- menu.id = 'snaptagger-menu'; // Add ID for easier selection
- menu.innerHTML = `
- <div class="snaptagger-title">📌 SnapTagger</div>
- <button id="snaptagger-tag-chat" class="snaptagger-button">Tag this chat</button>
- <button id="snaptagger-export-chat" class="snaptagger-button">Export tagged chats</button>
- <div id="snaptagger-feedback" class="snaptagger-feedback"></div>
- `;
- container.appendChild(menu);
- // --- Replace Original Icon ---
- // Replace the original icon with our container that includes the icon and menu
- iconElement.parentNode.replaceChild(container, iconElement);
- uiInjected = true; // Mark UI as injected
- console.log("SnapTagger: UI Injected.");
- // --- Add Event Listeners ---
- // Toggle dropdown visibility
- container.addEventListener('click', (event) => {
- // Prevent clicks on buttons inside the menu from closing it immediately
- if (!menu.contains(event.target) || event.target === container || event.target === clonedIcon) {
- menu.classList.toggle('visible');
- }
- });
- // Close dropdown if clicking outside
- document.addEventListener('click', (event) => {
- if (!container.contains(event.target) && menu.classList.contains('visible')) {
- menu.classList.remove('visible');
- }
- });
- // Button actions
- document.getElementById('snaptagger-tag-chat').addEventListener('click', (event) => {
- event.stopPropagation(); // Prevent container click listener from firing
- tagCurrentChat();
- // Optionally close menu after action:
- // menu.classList.remove('visible');
- });
- document.getElementById('snaptagger-export-chat').addEventListener('click', (event) => {
- event.stopPropagation(); // Prevent container click listener from firing
- exportTaggedChats();
- // Optionally close menu after action:
- menu.classList.remove('visible');
- });
- }
- // --- Initialization ---
- // Use MutationObserver for potentially better performance and reliability than setInterval
- const observer = new MutationObserver((mutationsList, obs) => {
- const snapIcon = document.querySelector(SNAP_ICON_SELECTOR);
- if (snapIcon && !uiInjected) {
- console.log("SnapTagger: Snapchat icon found. Injecting UI.");
- // Small delay to ensure surrounding elements are likely stable
- setTimeout(() => injectUI(snapIcon), 500);
- obs.disconnect(); // Stop observing once the icon is found and UI is injected
- }
- // Add a timeout safeguard in case the observer fails or the element never appears
- // setTimeout(() => {
- // if (!uiInjected) {
- // console.warn("SnapTagger: Timed out waiting for icon. Script might not work.");
- // obs.disconnect();
- // }
- // }, 15000); // Stop trying after 15 seconds
- });
- // Start observing the document body for added nodes
- console.log("SnapTagger: Initializing observer...");
- observer.observe(document.body, { childList: true, subtree: true });
- // Fallback using setInterval (less ideal but can work)
- // const checkInterval = 2000; // Check every 2 seconds
- // const maxAttempts = 10; // Try for 20 seconds
- // let attempts = 0;
- // const fallbackInterval = setInterval(() => {
- // if (uiInjected) {
- // clearInterval(fallbackInterval);
- // return;
- // }
- // attempts++;
- // const snapIcon = document.querySelector(SNAP_ICON_SELECTOR);
- // if (snapIcon) {
- // console.log("SnapTagger (Fallback): Snapchat icon found. Injecting UI.");
- // clearInterval(fallbackInterval);
- // injectUI(snapIcon);
- // } else if (attempts >= maxAttempts) {
- // clearInterval(fallbackInterval);
- // console.warn(`SnapTagger (Fallback): Could not find Snapchat icon (${SNAP_ICON_SELECTOR}) after ${maxAttempts} attempts. Script may not work.`);
- // }
- // }, checkInterval);
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址