您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bypass Telegram's saving content restrictions for media and text; batch download media from selected messages
- // ==UserScript==
- // @name Telegram Web - Allow Saving Content
- // @namespace c0d3r
- // @license MIT
- // @version 0.5
- // @description Bypass Telegram's saving content restrictions for media and text; batch download media from selected messages
- // @author c0d3r
- // @match https://web.telegram.org/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=telegram.org
- // @grant unsafeWindow
- // @grant GM_addStyle
- // ==/UserScript==
- // Extract media from message and download to disc
- function downloadMediaFromMessage(msg) {
- var myMedia;
- if (msg.media) {
- // Extract the media object; simple alternative to getMediaFromMessage
- myMedia = msg.media.document || msg.media.photo;
- }
- if (myMedia) {
- // Download media using the built-in function; auto sets file name and extension
- unsafeWindow.appDownloadManager.downloadToDisc({media: myMedia});
- }
- }
- // Throttle download of multiple medias by 1 second
- function slowDown(secs, msg, btnElm, btnTxt, btnIco) {
- setTimeout(function () {
- btnElm.disabled = true;
- btnElm.style.opacity = 0.6;
- btnTxt.textContent = '..' + (secs + 1) + '..';
- btnIco.textContent = '🕔';
- downloadMediaFromMessage(msg);
- }, secs * 1000);
- }
- // Get message object then download
- async function downloadSingleMedia(pid, mid) {
- // Get the message object based on peer and message ID
- var msg = await unsafeWindow.mtprotoMessagePort.getMessageByPeer(pid, mid);
- downloadMediaFromMessage(msg);
- }
- // Download multiple medias from selected messages
- async function downloadSelectedMedia() {
- var msgs = await unsafeWindow.appImManager.chat.selection.getSelectedMessages();
- var secs = 0;
- var btnElm = document.querySelector('#batch-btn');
- var btnTxt = btnElm.querySelector('.i18n');
- var btnIco = btnElm.querySelector('.mytgico');
- msgs.forEach(function (msg, ind) {
- // Only process messages with media
- if (msg.media && (msg.media.document || msg.media.photo)) {
- slowDown(secs, msg, btnElm, btnTxt, btnIco);
- secs++;
- }
- // Reset the batch button after last download
- if (ind === msgs.length - 1) {
- setTimeout(function () {
- btnElm.disabled = false;
- btnElm.style.opacity = 1;
- btnTxt.textContent = 'D/L';
- btnIco.textContent = '📥';
- }, secs * 1000);
- }
- });
- }
- (function () {
- 'use strict';
- if (window.location.pathname.startsWith('/a/')) {
- // Redirect to the WebK version from the WebA version
- window.location.replace(window.location.href.replace('.org/a/', '.org/k/'));
- } else {
- // The root element used for watching and listening
- var colCenter = document.querySelector('#column-center');
- // Array of class names for media; we only add Download button if these are right clicked
- var clArray = ['photo', 'audio', 'video', 'voice-message', 'media-round', 'grouped-item', 'document-container', 'sticker'];
- // HTML code for the Download button
- var btnHtml = '<div class="btn-menu-item rp-overflow" id="down-btn"><span class="mytgico btn-menu-item-icon" style="font-size: 16px;">📥</span><span class="i18n btn-menu-item-text">Download</span></div>';
- // HTML code for the batch D/L button
- var batchBtnHtml = ' <button class="btn-primary btn-transparent text-bold" id="batch-btn" title="Download Media"><span class="mytgico" style="padding-bottom: 2px;">📥</span> <span class="i18n">D/L</span></button>';
- // A flag for checking if we need to add the Download button
- var needBtn = false;
- // Variables for the current message and peer ID
- var curMid, curPid, observer;
- // Add CSS styles to allow text selection
- GM_addStyle('.no-forwards .bubbles, .bubble, .bubble-content { -webkit-user-select: text!important; -moz-user-select: text!important; user-select: text!important; }');
- // Unlock Ctrl+C to copy selected text
- var origListener = EventTarget.prototype.addEventListener;
- EventTarget.prototype.addEventListener = function(type) {
- if (type !== 'copy') {
- origListener.apply(this, arguments);
- }
- };
- colCenter.addEventListener('mouseup', function (e) {
- // Listen to the right mouse button clicks
- if (e.button === 2) {
- needBtn = false;
- // Test if the current chat has restricted content saving
- if (document.querySelector('.no-forwards')) {
- // Find the closest element containing message and peer IDs
- var closest = e.target.closest('[data-mid]');
- if (closest) {
- // Check if the element actually contains some media classes
- if (clArray.some(function (clName) {
- return closest.classList.contains(clName);
- })) {
- curMid = closest.dataset.mid;
- curPid = closest.dataset.peerId;
- needBtn = true;
- }
- }
- }
- }
- });
- observer = new MutationObserver(function (mutList) {
- mutList.forEach(function (mut) {
- mut.addedNodes.forEach(function (anod) {
- // Check if context menu has been added to the DOM
- if (anod.id === 'bubble-contextmenu' && needBtn) {
- // Add the custom Download button and assign a click event
- anod.querySelector('.btn-menu-item').insertAdjacentHTML('beforebegin', btnHtml);
- anod.querySelector('#down-btn').addEventListener('click', function () {
- downloadSingleMedia(curPid, curMid);
- });
- }
- // Check if selection popup has been added to the DOM
- if (anod.classList && anod.classList.contains('selection-wrapper')) {
- anod.querySelector('.selection-container-left').insertAdjacentHTML('beforeend', batchBtnHtml);
- anod.querySelector('#batch-btn').addEventListener('click', function () {
- downloadSelectedMedia();
- });
- }
- });
- });
- });
- // Observe when context menu is added to the DOM
- observer.observe(colCenter, {
- subtree: true, childList: true
- });
- }
- })();