Telegram Web - Allow Saving Content

Bypass Telegram's saving content restrictions for media and text

当前为 2023-10-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Telegram Web - Allow Saving Content
  3. // @namespace c0d3r
  4. // @license MIT
  5. // @version 0.2
  6. // @description Bypass Telegram's saving content restrictions for media and text
  7. // @author c0d3r
  8. // @match https://web.telegram.org/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=telegram.org
  10. // @grant unsafeWindow
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. // Download selected media, uses WebK's built-in functions
  15. async function downloadMedia(pid, mid) {
  16. // Get the message object based on peer and message ID
  17. var msg = await unsafeWindow.mtprotoMessagePort.getMessageByPeer(pid, mid);
  18. var myMedia;
  19.  
  20. if (msg.media) {
  21. // Extract the media object; simple alternative to getMediaFromMessage
  22. myMedia = msg.media.document || msg.media.photo;
  23. }
  24.  
  25. if (myMedia) {
  26. // Download media using the built-in function; auto sets file name and extension
  27. unsafeWindow.appDownloadManager.downloadToDisc({media: myMedia});
  28. }
  29. }
  30.  
  31. (function () {
  32. 'use strict';
  33.  
  34. if (window.location.pathname.startsWith('/a/')) {
  35. // Redirect to the WebK version from the WebA version
  36. window.location.replace(window.location.href.replace('.org/a/', '.org/k/'));
  37. } else {
  38. // The root element used for watching and listening
  39. var colCenter = document.querySelector('#column-center');
  40.  
  41. // Array of class names for media; we only add Download button if these are right clicked
  42. var clArray = ['photo', 'audio', 'video', 'voice-message', 'media-round', 'grouped-item', 'document-container', 'sticker'];
  43.  
  44. // HTML code for the Download button
  45. var btnHtml = '<div class="btn-menu-item rp-overflow" id="down-btn"><span class="tgico btn-menu-item-icon"></span><span class="i18n btn-menu-item-text">Download</span></div>';
  46.  
  47. // A flag for checking if we need to add the Download button
  48. var needBtn = false;
  49.  
  50. // Variables for the current message and peer ID
  51. var curMid, curPid, observer;
  52.  
  53. // Add CSS styles to allow text selection
  54. GM_addStyle('.chat.no-forwards .bubbles, .bubble, .bubble-content { -webkit-user-select: text!important; -moz-user-select: text!important; user-select: text!important; }');
  55.  
  56. colCenter.addEventListener('mouseup', function (e) {
  57. // Listen to the right mouse button clicks
  58. if (e.button === 2) {
  59. needBtn = false;
  60. // Test if the current chat has restricted content saving
  61. if (document.querySelector('.chat.no-forwards')) {
  62. // Find the closest element containing message and peer IDs
  63. var closest = e.target.closest('[data-mid]');
  64. if (closest) {
  65. // Check if the element actually contains some media classes
  66. if (clArray.some(function (clName) {
  67. return closest.classList.contains(clName);
  68. })) {
  69. curMid = closest.dataset.mid;
  70. curPid = closest.dataset.peerId;
  71. needBtn = true;
  72. }
  73. }
  74. }
  75. }
  76. });
  77.  
  78. observer = new MutationObserver(function (mutList) {
  79. mutList.forEach(function (mut) {
  80. mut.addedNodes.forEach(function (anod) {
  81. // Check if context menu has been added to the DOM
  82. if (anod.id == 'bubble-contextmenu' && needBtn) {
  83. // Add the custom Download button and assign a click event
  84. anod.querySelector('.btn-menu-item').insertAdjacentHTML('beforebegin', btnHtml);
  85. anod.querySelector('#down-btn').addEventListener('click', function () {
  86. downloadMedia(curPid, curMid);
  87. });
  88. }
  89. });
  90. });
  91. });
  92.  
  93. // Observe when context menu is added to the DOM
  94. observer.observe(colCenter, {
  95. subtree: true, childList: true
  96. });
  97. }
  98. })();