您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to YouTube watch pages to easily tweet a comment with the video link.
- // ==UserScript==
- // @name YouTube Comment to Twitter
- // @namespace http://tampermonkey.net/
- // @version 1.3
- // @description Adds a button to YouTube watch pages to easily tweet a comment with the video link.
- // @author torch
- // @match *://www.youtube.com/watch*
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @run-at document-end
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use_strict';
- // --- Configuration ---
- const BUTTON_TEXT = "🐦 Твитнуть комментарий";
- const POPUP_TITLE = "Написать твит о видео";
- const TWITTER_MAX_LENGTH = 280; // Standard Twitter limit
- const TWITTER_URL_LENGTH = 23; // Standard length consumed by a t.co URL
- // --- Styles ---
- GM_addStyle(`
- #yt-comment-to-twitter-btn {
- background-color: #1DA1F2;
- color: white;
- border: none;
- padding: 8px 12px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 14px;
- margin: 4px 2px;
- cursor: pointer;
- border-radius: 20px;
- font-weight: bold;
- }
- #yt-comment-to-twitter-btn:hover {
- background-color: #0c85d0;
- }
- .twitter-popup-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 9998;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .twitter-popup-content {
- background-color: #1e1e1e; /* Darker theme for YouTube dark mode */
- color: #e0e0e0;
- padding: 20px;
- border-radius: 8px;
- box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
- width: 400px;
- max-width: 90%;
- z-index: 9999;
- }
- .twitter-popup-content h3 {
- margin-top: 0;
- color: #1DA1F2;
- }
- .twitter-popup-content textarea {
- width: calc(100% - 20px);
- height: 100px;
- margin-bottom: 10px;
- padding: 10px;
- border: 1px solid #555;
- border-radius: 4px;
- background-color: #2a2a2a;
- color: #e0e0e0;
- resize: vertical;
- }
- .twitter-popup-content .char-counter {
- text-align: right;
- font-size: 0.9em;
- color: #aaa;
- margin-bottom: 10px;
- }
- .twitter-popup-content button {
- padding: 10px 15px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: bold;
- margin-right: 10px;
- }
- .twitter-popup-content .tweet-btn {
- background-color: #1DA1F2;
- color: white;
- }
- .twitter-popup-content .tweet-btn:hover {
- background-color: #0c85d0;
- }
- .twitter-popup-content .cancel-btn {
- background-color: #555;
- color: white;
- }
- .twitter-popup-content .cancel-btn:hover {
- background-color: #777;
- }
- `);
- let popupOverlay = null;
- // getVideoTitle is not needed for the tweet content anymore, but kept in case it's useful for other features later.
- // function getVideoTitle() {
- // const titleElement = document.querySelector('h1.ytd-video-primary-info-renderer yt-formatted-string, h1.title.ytd-video-primary-info-renderer');
- // return titleElement ? titleElement.textContent.trim() : "YouTube Video";
- // }
- function getVideoUrl() {
- return window.location.href;
- }
- function createPopup() {
- if (popupOverlay) {
- popupOverlay.style.display = 'flex'; // Show if already created
- if (popupOverlay.querySelector('textarea')) {
- popupOverlay.querySelector('textarea').focus();
- }
- return;
- }
- popupOverlay = document.createElement('div');
- popupOverlay.className = 'twitter-popup-overlay';
- popupOverlay.onclick = function(e) {
- if (e.target === popupOverlay) {
- closePopup();
- }
- };
- const popupContent = document.createElement('div');
- popupContent.className = 'twitter-popup-content';
- const title = document.createElement('h3');
- title.textContent = POPUP_TITLE;
- const textarea = document.createElement('textarea');
- textarea.placeholder = "Ваш комментарий...";
- const charCounter = document.createElement('div');
- charCounter.className = 'char-counter';
- const updateCharCounter = () => {
- // The tweet will consist of the comment, a space, and the URL.
- // Twitter uses t.co to shorten URLs, which takes up a fixed number of characters.
- const lengthOfComment = textarea.value.length;
- const lengthOfSpaceAndUrl = (lengthOfComment > 0 ? 1 : 0) + TWITTER_URL_LENGTH; // Add space only if comment exists
- const remaining = TWITTER_MAX_LENGTH - lengthOfComment - lengthOfSpaceAndUrl;
- charCounter.textContent = `${remaining} символов осталось`;
- charCounter.style.color = remaining < 0 ? 'red' : '#aaa';
- };
- textarea.addEventListener('input', updateCharCounter);
- const tweetButton = document.createElement('button');
- tweetButton.textContent = "Твитнуть";
- tweetButton.className = 'tweet-btn';
- tweetButton.onclick = function() {
- const comment = textarea.value.trim();
- // Comment can be empty, in which case only the URL is tweeted via the 'url' parameter.
- // Twitter usually pre-fills the text field with the URL if the text parameter is empty.
- const videoUrl = getVideoUrl();
- // Construct tweet text: only the comment.
- // The videoUrl will be passed in the 'url' parameter of the Twitter intent.
- // Twitter will append the URL to the comment text.
- let tweetText = comment;
- const twitterIntentUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(videoUrl)}`;
- window.open(twitterIntentUrl, '_blank');
- closePopup();
- };
- const cancelButton = document.createElement('button');
- cancelButton.textContent = "Отмена";
- cancelButton.className = 'cancel-btn';
- cancelButton.onclick = closePopup;
- popupContent.appendChild(title);
- popupContent.appendChild(textarea);
- popupContent.appendChild(charCounter);
- popupContent.appendChild(tweetButton);
- popupContent.appendChild(cancelButton);
- popupOverlay.appendChild(popupContent);
- document.body.appendChild(popupOverlay);
- // Initialize counter
- updateCharCounter();
- textarea.focus();
- }
- function closePopup() {
- if (popupOverlay) {
- // Clear textarea for next time
- const textarea = popupOverlay.querySelector('textarea');
- if (textarea) {
- textarea.value = '';
- }
- popupOverlay.style.display = 'none';
- }
- }
- function addButton() {
- // More robust selectors for YouTube's dynamic layout
- const commonActionSelectors = [
- '#actions-inner #menu', // Older layout under video
- '#menu-container.ytd-watch-metadata', // Older layout alternative
- 'ytd-video-actions #actions', // Newer layout for like/dislike etc.
- '#actions.ytd-watch-flexy' // Common actions row
- ];
- const fallbackSelectors = [
- '#info-contents #top-row.ytd-watch-info-text',
- '#meta-contents #info-contents',
- '#meta-contents #info',
- '#owner #subscribe-button' // As a last resort, place it near subscribe
- ];
- let actionsContainer = null;
- for (const selector of commonActionSelectors) {
- actionsContainer = document.querySelector(selector);
- if (actionsContainer) break;
- }
- if (!actionsContainer) {
- for (const selector of fallbackSelectors) {
- actionsContainer = document.querySelector(selector);
- if (actionsContainer) break;
- }
- }
- if (actionsContainer) {
- if (actionsContainer.querySelector('#yt-comment-to-twitter-btn')) {
- return; // Button already exists
- }
- const twitterButton = document.createElement('button');
- twitterButton.id = 'yt-comment-to-twitter-btn';
- twitterButton.textContent = BUTTON_TEXT;
- twitterButton.onclick = createPopup;
- // Attempt to insert it in a reasonable place
- if (actionsContainer.id === 'actions' && actionsContainer.parentElement?.tagName === 'YTD-VIDEO-ACTIONS') {
- // Preferred: Add next to like/share buttons
- actionsContainer.insertBefore(twitterButton, actionsContainer.children[Math.min(2, actionsContainer.children.length)]);
- } else if (actionsContainer.firstChild) {
- actionsContainer.insertBefore(twitterButton, actionsContainer.firstChild.nextSibling);
- } else {
- actionsContainer.appendChild(twitterButton);
- }
- // console.log("YouTube Comment to Twitter button added to:", actionsContainer);
- } else {
- // console.warn("Could not find a suitable container for the Twitter button after multiple attempts.");
- }
- }
- // YouTube uses dynamic loading, so we need to observe DOM changes
- function observeDOM() {
- const targetNode = document.body;
- const config = { childList: true, subtree: true };
- let lastPathname = window.location.pathname;
- let debounceTimer;
- const handleMutation = () => {
- // Try to add the button if it's not there
- if (!document.querySelector('#yt-comment-to-twitter-btn')) {
- addButton();
- }
- };
- const callback = function(mutationsList, observer) {
- // Check if navigation has happened to a new watch page
- if (window.location.pathname !== lastPathname && window.location.pathname.includes("/watch")) {
- lastPathname = window.location.pathname;
- // Wait a bit for the new page to load elements
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(handleMutation, 1000);
- return;
- }
- // General check for dynamic content loading on the current page
- for (const mutation of mutationsList) {
- if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
- // Check if a potential container is now available
- if (document.querySelector('#actions-inner #menu, #menu-container.ytd-watch-metadata, ytd-video-actions #actions, #actions.ytd-watch-flexy') && !document.querySelector('#yt-comment-to-twitter-btn')) {
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(handleMutation, 300); // Debounce to avoid multiple rapid adds
- break;
- }
- }
- }
- };
- const observer = new MutationObserver(callback);
- observer.observe(targetNode, config);
- // Initial attempt in case the element is already there
- if (window.location.pathname.includes("/watch")) {
- setTimeout(addButton, 1000); // Initial delay for page load
- }
- }
- // Make sure the script runs after the page is mostly loaded
- if (document.readyState === "complete" || document.readyState === "interactive") {
- observeDOM();
- } else {
- window.addEventListener('DOMContentLoaded', observeDOM);
- }
- })();