Telegram web版本目前有Z和K两个版本,其中我比较喜欢K版本。使用web版本的好处是无论是哪个操作系统平台都能够随时访问,而不必在意客户端的区别。如果想要对客户端功能做出一些改变,也只需要一次改动,而不需要把每个版本的客户端都做相同修改。

这里我是用2024年4约28日的源代码进行分析:https://github.com/morethanwords/tweb
克隆、安装依赖、启动web服务均按照仓库中所描述的方法执行,没有做其他操作。

限制转发

web k限制转发的功能通过搜索forward可以找到,关键代码位置位于appMessagesManager.canForward函数。appMessagesManager.ts的8084行:

  public canForward(message: Message.message | Message.messageService) {
    return message?._ === 'message' && !(message as Message.message).pFlags.noforwards && !this.appPeersManager.noForwards(message.peerId);
  }

基本就知道限制转发的代码位置了。但是不要在这里做修改,因为转发功能最后还会在服务器校验,近改动客户端没有意义。

限制下载

那么来看点有意义的,找一下限制下载的代码。在contextMenu.ts里查找Download。在696行:

      icon: 'download',
      text: 'MediaViewer.Context.Download',
      onClick: () => ChatContextMenu.onDownloadClick(this.message, this.noForwards),
      verify: () => ChatContextMenu.canDownload(this.message, this.target, this.noForwards)

不过这里不清楚究竟是消息的右键菜单还是播放界面的菜单,继续查找其他的看看。

第二处是746行的下载选中的项目:

      icon: 'download',
      text: 'Message.Context.Selection.Download',
      onClick: () => ChatContextMenu.onDownloadClick(this.selectedMessages, this.noForwards),
      verify: () => this.selectedMessages && ChatContextMenu.canDownload(this.selectedMessages, undefined, this.noForwards),
      withSelection: true

857行的函数用于判断是否能够下载:

  public static canDownload(message: MyMessage | MyMessage[], withTarget?: HTMLElement, noForwards?: boolean): boolean {

1478行执行下载的方法里再次检查了能否下载:

  public static onDownloadClick(messages: MyMessage | MyMessage[], noForwards?: boolean): DownloadBlob | DownloadBlob[] {

至此contextMenu.ts里边的下载选项检查完毕,看起来应该确实是聊天消息的右键菜单而不是媒体查看播放界面的菜单。

继续检查播放界面的菜单。通过在浏览器控制台检查可以发现播放页面上方的下载图标是位于media-viewer-buttons这个CSS类里的。就以此为线索查找。在appMediaViewerBase.ts中,325行有绑定下载图标点击事件的代码:

  protected setListeners() {
    attachClickEvent(this.buttons.download, this.onDownloadClick);

后续appMediaViewerBase中没有更多值得关注的代码,再检查在不能下载的查看界面中按钮是为什么没有显示的,发现是给按钮添加了hide类。那么查找hide,然而一番查找下来没有收获。那么从import入手,也无果。考虑是外部调用AppMediaViewerBase的时候做了判断,查找外部调用,果然还有个appMediaViewer.ts,其中320行:

    const cantDownloadMessage = (isServiceMessage ? noForwards : cantForwardMessage) || !canSaveMessageMedia(message);
    const a: [(HTMLElement | ButtonMenuItemOptionsVerifiable)[], boolean][] = [
      [[this.buttons.forward, this.btnMenuForward], cantForwardMessage],
      [[this.buttons.download, this.btnMenuDownload], cantDownloadMessage],
      [[this.buttons.delete, this.btnMenuDelete], !(await this.managers.appMessagesManager.canDeleteMessage(message))]
    ];

    a.forEach(([buttons, hide]) => {
      buttons.forEach((button) => {
        if(button instanceof HTMLElement) {
          button.classList.toggle('hide', hide);
        } else {
          button.verify = () => !hide;
        }
      });
    });

这里判断了是否要显示下载图标,接着寻找在下载过程中是否有执行判断,而这个文件中涉及到buttons.download的仅此一处,需要回到base中查找。

appMediaViewerBase的325行:

  protected setListeners() {
    attachClickEvent(this.buttons.download, this.onDownloadClick);
    [this.buttons.close, this.buttons['mobile-close'], this.preloaderStreamable.preloader].forEach((el) => {
      attachClickEvent(el, this.close.bind(this));
    });

其中appMediaViewerBase.onDownloadClick没有实现,要转到appMediaViewer查看实现,在appMediaViewer的257行:

  onDownloadClick = () => {
    const {message} = this.target;
    const media = getMediaFromMessage(message, true);
    if(!media) return;
    appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
  };

检查发现内部确实不再检查能否下载,而是直接下载。

最后一个要检查的位置是视频播放界面的播放器右键菜单,经过查看video的eventListener,发现是上层带有media-viewer-whole类的一层的元素被加上了no-forwards类,那么查找no-forwards即可,在AppMediaViewer的337行:

    this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage);

那么确保cantDownloadMessage是自己想要的即可。

至此,Telegram Web K的限制转发和下载的分析就完成了。