美文网首页deno
Deno TypeScript 抖音小姐姐下载

Deno TypeScript 抖音小姐姐下载

作者: 坚果jimbowhy | 来源:发表于2021-04-21 20:54 被阅读0次

    Deno 是新一代基于 TypeScript 语言的编程平台,是 Node 平台之外的又一选择,它们都是由 Ryan Dahl 发起的项目,鉴于 Node 的一些不足,他决定放弃 Node.js,从头写一个替代品:

    Deno - A secure runtime for JavaScript and TypeScript.


    抖音小姐姐.jpg

    Deno 是一个简单又现代化而且安全的 JavaScript/TypeScript 运行时,基于 V8 引擎和 Rust(Tokio 异步编程框架),Deno 本身也是 Rust 的一个模块。

    • 初始即安全,除非明确准许,初始以沙盒状态运行(无文件、网络、环境变量访问权限);
    • 自身支持 TypeScript;
    • 运行时本体以单一二进制文件形式发布;
    • 拥有大量的自带工具,例如依赖检查(deno info)和代码格式化工具(deno fmt);
    • 拥有较为完备的官方标准库,确保能适配对应 Deno 版本运行;
    • Deno 最初由 Node.js 原作者 Ryan Dahl 于 2018 年 5 月在 JSConf.EU 首次提出。

    由于 TS 无法为 Deno runtime 生成高性能的代码,目前部分内部实现从 ts 变更为 js。

    但 Deno 并没有放弃 TypeScript,Deno 依然是一个安全的 TS/JS runtime,目前 Deno 彻底用 Rust 替代 C++/C,各语言比例大概是:

    • TypeScript:64.7%
    • Rust:31.9%
    • JavaScript:1.4%

    Deno VS Node

    Node Deno
    API 引用方式 模块导入 全局对象
    模块系统 CommonJS & 实验性 ES Module 全面 ES Module
    安全 无安全限制 默认安全
    TypeScript 通过第三方模块支持 ts-node 原生支持
    包管理 npm + node_modules 原生支持
    异步操作 回调 Promise
    包分发 中心化 npmjs.com 去中心化 import url
    入口 package.json 配置 import url 直接引入
    打包、测试、格式化 第三方如 eslint、gulp、webpack、babel 等 原生支持
    抖音小姐姐下载

    下载速度简直不要太快:

    wifi flow

    需要安装 Deno,再运行示范程序:

    deno run -A douyin.ts
    

    代码仓库为 Deno 演示,包含 demo/douyin.ts:https://github.com/jimboyeah/deno-demo

    此工具需要在抖音主界面上获取视频博主的分享链接,通过链接获取到视频列表后进行批量下载:

    http://v.douyin.com/ehSh5Cy
    https://www.iesdouyin.com/web/api/v2/user/info/?sec_uid=MS4wLjABAAAA06xUG37YAhRl8nWJ3vEG_CMMJZ47rnxLY96CAvUqoRg
    https://www.iesdouyin.com/web/api/v2/aweme/licke/?sec_uid=MS4wLjABAAAA06xUG37YAhRl8nWJ3vEG_CMMJZ47rnxLY96CAvUqoRg
    https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAA06xUG37YAhRl8nWJ3vEG_CMMJZ47rnxLY96CAvUqoRg&count=21&max_cursor=0&aid=1128&_signature=VHoupQAANAiyH7H6JvRmvVR6Lr&dytk=

    URL 签名 signature 随时间变化动态生成,可以在页面使用调式工具设置 fetch breakpoints,再根据调用栈定位到 init 方法:

    function init(config) {
      dytk = config.dytk;
      params.user_id = config.uid;
      params.sec_uid = _utils2.default.getUrlParam(window.location.href, "sec_uid");
    
      if (params.sec_uid != "") {
        delete params.user_id;
      }
    
      config.sec_uid = params.sec_uid;
      nonce = config.uid;
      signature = (0, _bytedAcrawler.sign)(nonce);
      // ...
    }
    

    根据打包机嵌入的信息找到 bytedAcrawler 的实现,其导出模块位置 vendor.a59687bc.js:1096。

    所谓模块,就是一个独立命名空间的闭包,在需要使用时就请求加载它。将模块提取出来,用它对 uid 进行处理就可以得到签名。

    模块提供的是混淆过的代码,参考 JavaScript Obfuscator Tool https://obfuscator.io

    由于 bytedAcrawler 提供的签名算法借用了浏览器对象 navigator 来保证算法运行环境为浏览器,否则尝试读取 userAgent 会导致运行出错,实现了运行环境安全。

    通过逆向,即像脚本引擎一样破解算法内部,只需给代码打一个补订即可解决,不过时间也是花了大半天:

    .replace(/return r$/,"this.navigator = {userAgent:''};return r")
    

    主程序:

    class Douyin {
      SavePath: string = "videos";
      userAgent: string = "userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
    
      downloadLikes(homeurl:string){
        this.downloadHome(homeurl, "like");
      }
      downloadPosts(homeurl:string){
        this.downloadHome(homeurl, "post");
      }
      async downloadHome(homeurl:string, type:"post"|"like"){
        let shareurl = await this.fetch_url(homeurl);
        let sec_uid = this.sec_uid(shareurl);
        let uid = this.uid(shareurl);
        // let uid = "59583160290";
        let sign = this.signature_module();
        let signature = sign(uid);
        let url = `https://www.iesdouyin.com/web/api/v2/aweme/${type}/?sec_uid=${sec_uid}&count=21&max_cursor=0&aid=1128&_signature=${signature}&dytk=`;
        
        let req = new Request(url);
        req.headers.append("userAgent", this.userAgent);
    
        let posts = await fetch(req).then(res => res.json() as Promise<Posts>);
        this.log(posts, url, sec_uid, uid, signature);
        for(let it of posts.aweme_list){
          this.downloadItem(it, homeurl, url);
        }
      }
      downloadList(list:string[]){
        Deno.mkdirSync(this.SavePath, { recursive: true });
        for (let url of list) {
          try{
            this.fetchItem(url);
          }catch(e){ console.error("Error when process item", url, e.message); }
        }
      }
      parsePosts(file:string){
        const buffer = Deno.readFileSync(file);
        const decoder = new TextDecoder("utf-8");
        let lines = decoder.decode(buffer).split('\n');
        for(let it of lines){
          if(it.indexOf("[last parse]")>=0){
            return this.log("tag found:", it);
          }
          let infourl = "";
          let match = /\[(\d+)\]/.exec(it);
          let id = match?match[1]:"";
          id && this.iteminfo(id).then(res => {
            infourl = res.url;
            return res.json();
          }).then((it: ItemInfo) => {
            this.downloadItem(it.item_list[0], "", infourl);
          }).catch(e => console.error(e));
        }
      }
    
      public fetchItem(shorturl: string) {
        fetch(shorturl).then(res => {
          let shareurl = res.url, infourl = "";
          this.log(shorturl, shareurl);
          let id = this.aweme_id(shareurl);
          id && this.iteminfo(id).then(res => {
            infourl = res.url;
            return res.json();
          }).then((it: ItemInfo) => {
            this.downloadItem(it.item_list[0], shorturl, infourl);
          }).catch(e => console.error(e));
          return res.text();
        }).then(function (this: any, text) {
        }).catch(e => console.error(e.message, shorturl));
      }
    
      private downloadItem(item: VideoInfo, shorturl: string, infourl: string) {
        let videourl = item.video.play_addr.url_list[0];
        videourl = videourl.replace("playwm", "play");
        let uid = item.author.unique_id || item.author.short_id;
        let aweme_id = item.aweme_id;
        let duration = item.video.duration;
        let coverurl = item.video.origin_cover.url_list[0];
        console.log(
          shorturl,
          item.author.nickname,
          uid,
          item.desc,
          infourl,
          videourl
        );
        this.get_cover(coverurl, `${this.SavePath}/${uid}-${aweme_id}-${duration}.jpg`);
        this.get_video(videourl, `${this.SavePath}/${uid}-${aweme_id}-${duration}.mp4`, false);
      }
    
      async fetch_url(shorturl:string) {
        return fetch(shorturl).then(res => res.url);
      }
    
      aweme_id(url: string): string {
        let reg = /share\/video\/(.+?)\//;
        let match = reg.exec(url);
        if(match) {
          return match[1];
        }
        return "";
      }
      sec_uid(url: string): string {
        let reg = /sec_uid=(.+?)&/;
        let match = reg.exec(url);
        if(match) {
          return match[1];
        }
        return "";
      }
      uid(url: string): string {
        let reg = /share\/user\/(\d+?)/;
        let match = reg.exec(url);
        if(match) {
          return match[1];
        }
        return "";
      }
      async iteminfo(id:string): Promise<Response> {
        let url = `https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${id}`;
        return await fetch(url);
      }
      get_cover(url: string, name:string) {
        fetch(url).then( res => {
          return res.arrayBuffer();
        }).then(ab => {
          let ua8 = new Uint8Array(ab);
          Deno.writeFile(name, ua8).then(val =>{});
        }).catch(e => console.error("Error when to download ", url));
      }
      get_video(url: string, name:string, autoplay = false) {
        fetch(url).then(res => {
          return res.arrayBuffer()
        }).then(ab =>{
          let ua8 = new Uint8Array(ab);
          Deno.writeFile(name, ua8).then(val => {
            if (!autoplay) return;
            let cmd =  ["cmd", "/c", "start", name];
            const p = Deno.run({
              cmd: cmd,
            }).status();
          });
        }).catch(e => console.error("Error when to download ", url))
      }
      log(...args:any) {
        console.log(...args);
      }
      
      signature_module() {
        let exports = {sign:(s:string):string => ""};
        let navigator = {userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"};
        // let module = {exports};
        Function(function(t){return'�e(e,a,r){�(b[e]||(b[e]=t("x,y","�x "+e+" y"�)(r,a)}�a(e,a,r){�(k[r]||(k[r]=t("x,y","�new x[y]("+Array(r+1).join(",x[�y]")�(1)+")"�)(e,a)}�r(e,a,r){�n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t�)s[n="$"+t]=r[n];for(t=0,b=s�=a�;t<b;t�)s[t]=a[t];�c(e,0,s)}�c(t,b,k){�u(e){v[x�]=e}�f�{�g=�,t�ing(b�g)}�l�{try{y=c(t,b,k)}catch(e){h=e,y=l}}for(�h,y,d,g,v=[],x=0;;)switch(g=�){case 1:u(!�)�4:�f��5:u(�(e){�a=0,r=e�;���{�c=a<r;�c&&u(e[a�]),c}}(���6:y=�,u(�(y��8:if(g=�,l��g,g=�,y===c)b+=g;else if(y!==l)�y�9:�c�10:u(s(���11:y=�,u(�+y)�12:for(y=f�,d=[],g=0;g<y�;g�)d[g]=y.charCodeAt(g)^g+y�;u(String.fromCharCode.apply(null,d��13:y=�,h=delete �[y]�14:���59:u((g=�)?(y=x,v.slice(x-=g,y�:[])�61:u(�[�])�62:g=�,k[0]=65599*k[0]+k[1].charCodeAt(g)>>>0�65:h=�,y=�,�[y]=h�66:u(e(t[b�],�,���67:y=�,d=�,u((g=�).x===c?r(g.y,y,k):g.apply(d,y��68:u(e((g=t[b�])<"<"?(b--,f�):g+g,�,���70:u(!1)�71:�n�72:�+f��73:u(parseInt(f�,36��75:if(�){b��case 74:g=�<<16>>16�g�76:u(k[�])�77:y=�,u(�[y])�78:g=�,u(a(v,x-=g+1,g��79:g=�,u(k["$"+g])�81:h=�,�[f�]=h�82:u(�[f�])�83:h=�,k[�]=h�84:�!0�85:�void 0�86:u(v[x-1])�88:h=�,y=�,�h,�y�89:u(��{�e�{�r(e.y,arguments,k)}�e.y=f�,e.x=c,e}�)�90:�null�91:�h�93:h=��0:��;default:u((g<<16>>16)-16)}}�n=this,t=n.Function,s=Object.keys||�(e){�a={},r=0;for(�c in e)a[r�]=c;�a�=r,a},b={},k={};�r'.replace(/[�-�]/g,function(m){return t[m.charCodeAt(0)&15]})}("v[x++]=�v[--x]�t.charCodeAt(b++)-32�function �return �))�++�.substr�var �.length�()�,b+=�;break;case �;break}".split("�")).replace(/return r$/,"this.navigator = {userAgent:''};return r"))()('gr$Daten Иb/s!l y͒yĹg,(lfi~ah`{mv,-n|jqewVxp{rvmmx,&eff�kx[!cs"l".Pq%widthl"@q&heightl"vr*getContextx$"2d[!cs#l#,*;?|u.|uc{uq$fontl#vr(fillTextx$$龘ฑภ경2<[#c}l#2q*shadowBlurl#1q-shadowOffsetXl#$$limeq+shadowColorl#vr#arcx88802[%c}l#vr&strokex[ c}l"v,)}eOmyoZB]mx[ cs!0s$l$Pb<k7l l!r&lengthb%^l$1+s$j�l  s#i$1ek1s$gr#tack4)zgr#tac$! +0o![#cj?o ]!l$b%s"o ]!l"l$b*b^0d#>>>s!0s%yA0s"l"l!r&lengthb<k+l"^l"1+s"j�l  s&l&z0l!$ +["cs\'(0l#i\'1ps9wxb&s() &{s)/s(gr&Stringr,fromCharCodes)0s*yWl ._b&s o!])l l Jb<k$.aj;l .Tb<k$.gj/l .^b<k&i"-4j!�+& s+yPo!]+s!l!l Hd>&l!l Bd>&+l!l <d>&+l!l 6d>&+l!l &+ s,y=o!o!]/q"13o!l q"10o!],l 2d>& s.{s-yMo!o!]0q"13o!]*Ld<l 4d#>>>b|s!o!l q"10o!],l!& s/yIo!o!].q"13o!],o!]*Jd<l 6d#>>>b|&o!]+l &+ s0l-l!&l-l!i\'1z141z4b/@d<l"b|&+l-l(l!b^&+l-l&zl\'g,)gk}ejo{�cm,)|yn~Lij~em["cl$b%@d<l&zl\'l $ +["cl$b%b|&+l-l%8d<@b|l!b^&+ q$sign ',[Object.defineProperty(exports,'__esModule',{value:!0})]);
        return exports.sign;
      }
    }
    
    if(Deno.args.length){
      let act = Deno.args[0];
      let dy = new Douyin();
      if("posts"===act){
        dy.downloadPosts(Deno.args[1])
      }else if("likes" === act){
        dy.downloadLikes(Deno.args[1])
      }else if("parse" === act){
        dy.parsePosts(Deno.args[1])
      }else{
        dy.downloadList(Deno.args);
      }
    }else{
      console.log(`
      download video from v.douying.com
      deno run -A douyin.ts parse posts.txt
      deno run -A douyin.ts https://v.douyin.com/eh1PpKh/ https://v.douyin.com/eh1Hs4L/
      deno run -A douyin.ts posts http://v.douyin.com/eh55UYC
      deno run -A douyin.ts likes http://v.douyin.com/ehSh5Cy
      `  );
    }
    

    Douyin 桌面看,使用 Deno Webview

    自由缩放 自动列表加载 无水印自动缓存 浏览阿婆主页

    相关文章

      网友评论

        本文标题:Deno TypeScript 抖音小姐姐下载

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