美文网首页
关于剪贴板的故事—起源于公众号后台的一次探寻

关于剪贴板的故事—起源于公众号后台的一次探寻

作者: SSSimonYang | 来源:发表于2020-08-10 19:25 被阅读0次

    整个事情的起源是这样的。

    六月底,我打算重新开始更我停了很久的公众号,因为域名到期和图片自动上传不够便利的原因,我弃用了之前的vscode+markdown preview enhanced插件+qiniu-upload-image插件的写文方案。同时,vscode写markdown的换行总有问题,每次都要到网页转化工具进行大量重调,十分不爽。

    在不停的搜索过后,我采用了来自KrisTM博客的Typora+PicGo的方案,解决了markdown编写和粘贴、拖拽图片自动上传(图片存储在Gitee仓库)的问题。但是转化问题还是出现了,博客里对于转化到公众号推文的部分是这样说的。

    打开HTML,复制网页上的所有内容,直接粘贴到微信公众号编辑框里即可。

    而我在实际操作中发现,不管是复制还是生成html,在粘贴到公众号后台时,均会出现如下情况。

    image-20200701133829365

    这是什么鬼,说好的直接粘贴就行,结果,就这,就这?猜测应该是公众号后台改版了,这个博客写于2020年3月,才6月底就不能用了,可怕。

    于是只能继续使用网页转化工具,Md2AllWeChat Format来进行markdown到公众号推文的转化。在网页上点击复制,然后到公众号后台粘贴,就有了内容。

    image-20200701134042534

    问题似乎已经解决,但是我的好奇心属实被勾起来了。为什么在网页转化工具上点击复制,粘贴到公众号后台就有样式,而在Typora上复制,或者从其他地方复制,粘贴后都是纯文本呢?

    对WeChat Format源码的研究

    在实践中,我发现,在网页上点击复制后,不管是粘贴到QQ、Wechat,还是Vscode、Pycharm,都呈现的是纯文本形式,只有复制到公众号后台时,才有样式。我顿时对两个网站的复制位置背后的行为产生了好奇,认为这里面肯定有玄学操作。

    image-20200701145700586

    为了探寻复制的奥秘,我找到了WeChat Format项目的源码,clone后进行查看。

    整个项目基于vue,我在写主vue项目的editor.js找到了比较核心的copyrefreshrenderWeChat等函数,在对应到主页面index.html之后,可以发现,点击复制运行的就是copycopy主要使用的是output区域的内容。

        copy: function () {
          var clipboardDiv = document.getElementById('output')
          clipboardDiv.focus();
          window.getSelection().removeAllRanges();  
          var range = document.createRange(); 
          range.setStartBefore(clipboardDiv.firstChild);
          range.setEndAfter(clipboardDiv.lastChild);
          window.getSelection().addRange(range);  
          try {
            if (document.execCommand('copy')) {
              this.$message({
                message: '已复制到剪贴板', type: 'success'
              })
            } else {
              this.$message({
                message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
              })
            }
          } catch (err) {
            this.$message({
              message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
            })
          }
        }
    

    其中document.execCommand('copy')是最主要的一行内容,搜索后得知,这一行实现了Copies the current selection to the clipboard。也就是说,第4至8行实现了window.getSelection()区域的清空,添加clipboardDiv区域的首子节点到尾子节点的所有内容到一个新的range,将这个range添加到window.getSelection()等操作。最后第10行完成复制。

    output区域的原始内容为空。

    <div id="output" v-html="output">
    

    在选项更改后触发的refresh函数中,output值得到更新,v-htmloutput的内容作为html展现,其值来自renderWeChat函数。

        fontChanged: function (fonts) {
          this.wxRenderer.setOptions({
            fonts: fonts
          })
          this.refresh()
        },
        sizeChanged: function(size){
          this.wxRenderer.setOptions({
            size: size
          })
          this.refresh()
        },
        themeChanged: function(themeName){
          var themeName = themeName;
          var themeObject = this.styleThemes[themeName];
          this.wxRenderer.setOptions({
            theme: themeObject
          })
          this.refresh()
        },
        refresh: function () {
          this.output = this.renderWeChat(this.editor.getValue())
        }
    

    refresh后,document.getElementById('output')也就有了内容。

    image-20200701140202999

    产生output值的renderWeChat函数,则使用了marked.js实现了从markdown到html的渲染,同时自定义了一个函数来根据样式进行渲染,之后添加脚注。

        renderWeChat: function (source) {
          var output = marked(source, { renderer: this.wxRenderer.getRenderer() })
          if (this.wxRenderer.hasFootnotes()) {
            output += this.wxRenderer.buildFootnotes()
          }
          return output
        }
    

    到这已经非常清楚了,送进剪贴板的内容是html,这个结果并不amazing,我原以为公众号后台定义了新的html标准,而这两个网站可以根据标准进行对应的渲染。但是,结果还是html。那为什么我从其他地方复制的html在粘贴到公众号后台后还是纯文本呢。问题,一定出在剪贴板身上。

    对剪贴板的研究

    对微软剪贴板的实现稍加搜索。

    image-20200701112427351

    官方解释称,在剪贴板可以放置超过一个对象,每个代表不同格式的同样数据。联想到剪贴板也可以复制图片、复制文件,那么大概率,html和text,在剪贴板中也是作为不同类型存储的。

    接下来,便是要找到一个接口,将剪贴板里的数据拿出来,看看是否和我想的一样。

    在搜索中,我发现pyqt可以与剪贴板进行交互,并且支持Html、Text、Image、Url等类型。Python如何获取Windows剪贴板内容并判断类型?-施Sugar的回答-知乎

    稍加修改后,写出如下代码。

    from PyQt5.QtWidgets import QApplication
    
    app = QApplication([])
    clipboard = app.clipboard()
    
    
    def on_clipboard_change():
        data = clipboard.mimeData()
        if data.hasHtml():
            print(f'html-{data.html()}')
        if data.hasText():
            print(f'text-{data.text()}')
        if data.hasUrls():
            print(f'urls-{data.urls()}')
        if data.hasImage():
            print(f'image-{data.imageData()}')
        if data.hasFormat():
            print(f'format-{data.formats()}')
    
    clipboard.dataChanged.connect(on_clipboard_change)
    app.exec()
    

    该函数检测五种类型的数据是否存在,存在的时候进行相应输出。

    运行后,当点击WeChat Format网页上的复制时,出现如下内容:

    html-<html>
    <body>
    <!--StartFragment--><h2 style="box-sizing: border-box; margin: 80px 10px 40px; padding: 0px; font-weight: normal; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; text-align: center; color: rgb(63, 63, 63); line-height: 1.5; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif; font-size: 22.4px;">99岁,生日快乐</h2><!--EndFragment-->
    </body>
    </html>
    text-99岁,生日快乐
    

    当选择复制一个文件夹时:

    text-file:///C:/sssimonyang/projects
    urls-[PyQt5.QtCore.QUrl('file:///C:/sssimonyang/projects')]
    

    复制图片时:

    text-file:///C:/Users/sssimonyang/Pictures/日用类/头像.jpg
    urls-[PyQt5.QtCore.QUrl('file:///C:/Users/sssimonyang/Pictures/日用类/头像.jpg')]
    image-<PyQt5.QtGui.QImage object at 0x000001C8D1944C88>
    

    而复制Typora中的html时:

    text-<!doctype html>
    <html>
    <head>
    <meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
    ------------------------
    

    很显然,在Typora中的复制只添加了剪贴板的text内容,html内容为空,所以在复制到公众号后台时呈现的也是text中的内容。

    那,如果我将Typora复制的text强行添加到剪贴板的html里会是什么情况呢。

    强行修改剪贴板

    首先试一下,强行添加html到剪贴板是否能够成功。

    我将在wechat-format点击复制的html写入wechat-format.html,然后用程序读取这个文件添加到剪贴板的html,同时,为了区分html和text,我在两者添加了显然不同的内容。注意,在程序运行前,复制一个无关内容更新掉剪贴板,同时程序运行后不要复制其他内容。

    from PyQt5.QtCore import QMimeData
    from PyQt5.QtWidgets import QApplication
    
    app = QApplication([])
    clipboard = QApplication.clipboard()
    
    with open('wechat-format.html', 'r', encoding='utf-8') as f:
        html = f.read()
    data = QMimeData()
    data.setHtml(html)
    data.setText('庆祝中国共产党成立九十九周年,初心不改,99如一')
    clipboard.setMimeData(data)
    
    app.exec()
    

    复制到公众号后台后:

    image-20200701121511540

    成功了!我第一次实现了自己添加的内容被公众号后台成功解析。

    下一步,很显然,把wechat-format.html替换成Typora导出的typora.html

    替换过后的运行结果:

    image-20200701122012516

    ???这就非常有意思了,居然粘贴的是text里的内容。

    两次运行的唯一区别就是html文件,让我们来看看两个html文件之间有什么区别。

    image-20200701122324941

    wechat-format.htmltypora.html的区别主要在于,typora.html多了第一行<!doctype html>,以及wechat-format.html多了的配对注释。

    让我们照葫芦画瓢抄一下.

    image-20200701122939933

    再次运行试试:

    image-20200701123014773

    成功输出了typora.html里的内容,但是没有样式,考虑到typora.html的样式定义主要在<head>中,而wechat-format.html的样式定义在各个标签中,公众号后台应该直接忽略了<head>

    稍微改一下typora.html看看效果。把<head>部分删掉,没用的class删掉,然后添加一个样式color:red;font-size:30px

    <html>
    <body>
    <!--StartFragment-->
    <div id='write'>
        <h1 style="color:red;font-size:30px"><a name="99岁生日快乐"></a><span>99岁,生日快乐</span>
        </h1>
    </div>
    </body>
    <!--EndFragment-->
    </html>
    

    运行,看看效果:

    image-20200701124432064

    果然改了html文件就好了。

    现在就很清楚了,公众号后台会首先读取html的内容,如果html内容不符合他的要求,那么他就读取text内容。

    那么这个要求,到底是什么呢,之前我们主要修改了两部分。把第一部分添加上试一下。

    <!doctype html>
    <html>
    <body>
    <div id='write'>
        <h1 style="color:red;font-size:30px"><a name="99岁生日快乐"></a><span>99岁,生日快乐</span>
        </h1>
    </div>
    </body>
    </html>
    
    image-20200701125126409

    不行,所以识别大概率第一个标签必须是<html>,我们把<html>撤掉试一下。

    <body>
    <div id='write'>
        <h1 style="color:red;font-size:30px"><a name="99岁生日快乐"></a><span>99岁,生日快乐</span>
        </h1>
    </div>
    </body>
    
    image-20200701125126409

    不行,加上。

    <html>
    <body>
    <div id='write'>
        <h1 style="color:red;font-size:30px"><a name="99岁生日快乐"></a><span>99岁,生日快乐</span>
        </h1>
    </div>
    </body>
    </html>
    
    image-20200701124432064

    OK了!

    公众号识别读取的是剪贴板中的html内容,如果html的开头不是<html>,那么它就会使用text中的内容,这也解释了之前为什么如何复制在粘贴后都是纯文本的问题。

    最后

    既然都搞了剪贴板,不如来测试下QQ、Wechat。

    运行之前的代码,然后粘贴。

    image-20200701142338769 image-20200701142425148

    what?QQ和Wechat居然不一样,QQ用的是text内容,Wechat用的是html,这就是宇宙大厂腾讯吗???

    结语

    这些研究花了我一晚上的时间,其结果实在是有趣。能够自由设定内容后,未来看有没有python写的markdown转化工具,也自己搞个公众号推文转化工具出来。

    今天是建党节,九十九年风雨兼程,生日快乐

    相关文章

      网友评论

          本文标题:关于剪贴板的故事—起源于公众号后台的一次探寻

          本文链接:https://www.haomeiwen.com/subject/crtsdktx.html